From 8ffe769f721283f144f4766b592ab8df5b23f432 Mon Sep 17 00:00:00 2001 From: Charles Samborski Date: Mon, 29 Jan 2018 21:47:18 +0100 Subject: [PATCH 1/2] Type check conversations --- CHANGELOG.md | 3 + package-lock.json | 315 ++++++++---------- package.json | 6 +- src/example/main.ts | 15 +- src/lib/api.ts | 25 +- src/lib/api/get-conversation.ts | 68 ---- src/lib/api/get-conversations.ts | 48 --- src/lib/api/get-self-profile.ts | 11 +- src/lib/contacts/api/get-contacts.ts | 49 ++- src/lib/contacts/api/get-invites.ts | 6 +- src/lib/contacts/api/get-user.ts | 35 -- src/lib/contacts/contacts.ts | 24 +- .../api/get-conversation-by-id.ts | 54 +++ src/lib/conversation/api/get-conversations.ts | 74 ++++ src/lib/conversation/conversation-url.ts | 0 src/lib/conversation/conversation.ts | 54 +++ src/lib/index.ts | 8 - src/lib/interfaces/api/events.ts | 1 - src/lib/json-reader.ts | 3 + src/lib/mri.ts | 12 +- src/lib/types/agent-info.ts | 13 +- src/lib/types/agent.ts | 14 +- src/lib/types/api-profile.ts | 39 ++- src/lib/types/contact-group.ts | 11 +- src/lib/types/contact-profile.ts | 7 +- src/lib/types/contact.ts | 25 +- src/lib/types/conversation-properties.ts | 66 ++++ src/lib/types/conversation-status.ts | 11 + src/lib/types/conversation-type.ts | 12 + src/lib/types/conversation.ts | 78 +++++ src/lib/types/display-name-source.ts | 6 +- src/lib/types/empty-object.ts | 8 + src/lib/types/invite-message.ts | 9 +- src/lib/types/invite.ts | 7 +- src/lib/types/location.ts | 7 +- src/lib/types/message-type.ts | 11 + src/lib/types/message.ts | 86 +++++ src/lib/types/name.ts | 7 +- src/lib/types/phone.ts | 7 +- src/lib/types/relationship-history.ts | 11 +- src/lib/types/resource-type.ts | 11 + src/lib/types/stringy-boolean.ts | 10 + src/lib/types/thread-properties.ts | 65 ++++ yarn.lock | 204 +++++------- 44 files changed, 945 insertions(+), 591 deletions(-) delete mode 100644 src/lib/api/get-conversation.ts delete mode 100644 src/lib/api/get-conversations.ts delete mode 100644 src/lib/contacts/api/get-user.ts create mode 100644 src/lib/conversation/api/get-conversation-by-id.ts create mode 100644 src/lib/conversation/api/get-conversations.ts create mode 100644 src/lib/conversation/conversation-url.ts create mode 100644 src/lib/conversation/conversation.ts create mode 100644 src/lib/json-reader.ts create mode 100644 src/lib/types/conversation-properties.ts create mode 100644 src/lib/types/conversation-status.ts create mode 100644 src/lib/types/conversation-type.ts create mode 100644 src/lib/types/conversation.ts create mode 100644 src/lib/types/empty-object.ts create mode 100644 src/lib/types/message-type.ts create mode 100644 src/lib/types/message.ts create mode 100644 src/lib/types/resource-type.ts create mode 100644 src/lib/types/stringy-boolean.ts create mode 100644 src/lib/types/thread-properties.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bf375a5..91de7e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Next +- **[Breaking change]** Update interfaces of the objects handled by the conversation API: + conversations and messages. The objects are now type-checked at runtime. The interfaces are + defined in `lib/types`. - **[Fix]** Do not throw on unexpected extra keys when reading responses. - **[Fix]** Fix message host resolution (API change). - **[Fix]** Mark `isFavorite` in `ContactGroup` as optional. diff --git a/package-lock.json b/package-lock.json index 8bca7a7..ce0a0db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", "dev": true, "requires": { - "acorn": "5.3.0", + "acorn": "5.4.1", "css": "2.2.1", "normalize-path": "2.1.1", "source-map": "0.5.7", @@ -18,9 +18,9 @@ }, "dependencies": { "acorn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", - "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==", "dev": true } } @@ -36,9 +36,9 @@ } }, "@std/esm": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@std/esm/-/esm-0.19.7.tgz", - "integrity": "sha512-bPBbpu1vqgOOD70aMVG5tgioPdttKXQQFq6xodjZxVbPprtZIcm8NcTEJoB+/1QoH8z1TIqjaEN1Wm3YndnfNQ==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@std/esm/-/esm-0.20.0.tgz", + "integrity": "sha512-05cQYa1T/6XxKhhnyIKQC8kSBHYkXWueZ0XFLABLlghoymwgyp4XT9ivjDrHb7EbSWbQoHSRk34zhIuPeShJow==", "dev": true }, "@types/bson": { @@ -46,9 +46,14 @@ "resolved": "https://registry.npmjs.org/@types/bson/-/bson-1.0.6.tgz", "integrity": "sha512-v7N8qcTGiYhLRyi+Y69R3tPC4GLqByCg3NC2EO6PciC166O9dNhjFPoXeMePtZ+0f+/O2xLDWXs5BLnRfcBaBA==", "requires": { - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, + "@types/caseless": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", + "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==" + }, "@types/chai": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.2.tgz", @@ -67,7 +72,7 @@ "dev": true, "requires": { "@types/events": "1.1.0", - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "@types/del": { @@ -96,7 +101,7 @@ "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", "requires": { - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "@types/fs-extra": { @@ -105,7 +110,7 @@ "integrity": "sha512-qtxDULQKUenuaDLW003CgC+0T0eiAfH3BrH+vSt87GLzbz5EZ6Ox6mv9rMttvhDOatbb9nYh0E1m7ydoYwUrAg==", "dev": true, "requires": { - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "@types/glob": { @@ -116,7 +121,7 @@ "requires": { "@types/events": "1.1.0", "@types/minimatch": "3.0.3", - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "@types/glob-stream": { @@ -126,7 +131,7 @@ "dev": true, "requires": { "@types/glob": "5.0.35", - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "@types/gulp": { @@ -146,7 +151,7 @@ "integrity": "sha512-v0KhxGFtk/p4tZ6N20LoE7CgH4KSvu5PrUqx0dS9001DCsU/tsnNLYG7H3b8eZ4lovRgsJYY0m1w1VJ3cxBPag==", "dev": true, "requires": { - "@types/node": "9.4.0", + "@types/node": "9.4.1", "@types/pug": "2.0.4" } }, @@ -156,7 +161,7 @@ "integrity": "sha512-FIZQvbZJj6V1gHPTzO+g/BCWpDur7fJrroae4gwV3LaoHBQ+MrR9sB+2HssK8fHv4WdY6hVNxkcft9bYatuPIA==", "dev": true, "requires": { - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "@types/gulp-sourcemaps": { @@ -165,25 +170,25 @@ "integrity": "sha512-+7BAmptW2bxyJnJcCEuie7vLoop3FwWgCdBMzyv7MYXED/HeNMeQuX7uPCkp4vfU1TTu4CYFH0IckNPvo0VePA==", "dev": true, "requires": { - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "@types/handlebars": { - "version": "4.0.31", - "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.31.tgz", - "integrity": "sha1-p/umb6/kJxOu6I7sqNuRGS7+bnI=", + "version": "4.0.36", + "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.36.tgz", + "integrity": "sha512-LjNiTX7TY7wtuC6y3QwC93hKMuqYhgV9A1uXBKNvZtVC8ZvyWAjZkJ5BvT0K7RKqORRYRLMrqCxpw5RgS+MdrQ==", "dev": true }, "@types/highlight.js": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.1.8.tgz", - "integrity": "sha1-0ifxi8uPPxh+FpZfJESFmgRol1g=", + "version": "9.12.2", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.2.tgz", + "integrity": "sha512-y5x0XD/WXDaGSyiTaTcKS4FurULJtSiYbGTeQd0m2LYZGBcZZ/7fM6t5H/DzeUF+kv8y6UfmF6yJABQsHcp9VQ==", "dev": true }, "@types/lodash": { - "version": "4.14.97", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.97.tgz", - "integrity": "sha512-uIKSroywPzhFYw6azoXK2ChfgW/lTfeiNDJKSGOp5RQSAvMdKMI1g0C1NqdU3I9k9bb8iwcWXGwA0n+JSn/N3A==" + "version": "4.14.101", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.101.tgz", + "integrity": "sha512-QLOgVuUtCm23+4crlMKAeFfk9KGFXpRAwQeH7rYByu57hD074lX4uXMLJ0F1B7AO5kDhBRgQc6Dv0lCiiquUCw==" }, "@types/marked": { "version": "0.3.0", @@ -197,7 +202,7 @@ "integrity": "sha512-GjaXY4OultxbaOOk7lCLO7xvEcFpdjExC605YmfI6X29vhHKpJfMWKCDZd3x+BITrZaXKg97DgV/SdGVSwdzxA==", "dev": true, "requires": { - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "@types/minimatch": { @@ -213,15 +218,15 @@ "dev": true }, "@types/mocha": { - "version": "2.2.47", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.47.tgz", - "integrity": "sha512-v/lko8AvmTt1K8p+ebdhdiHJE7zpPBYsDKjJSNCl8GwPGkKe5xND7s6M36LKye/USbWSPA7gYCDL0+DZ5h0L6Q==", + "version": "2.2.48", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", + "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", "dev": true }, "@types/node": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.0.tgz", - "integrity": "sha512-zkYho6/4wZyX6o9UQ8rd0ReEaiEYNNCqYFIAACe2Tf9DrYlgzWW27OigYHnnztnnZQwVRpwWmZKegFmDpinIsA==" + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.1.tgz", + "integrity": "sha512-9ESUxmXt1Isc1xKfDBZ7tpULyTPY5ZCywcfvQTXoLUqP+n4D+MBH+0n75hdzrcmfCc3eWByOi27+GLmMuAvcUA==" }, "@types/object-inspect": { "version": "1.4.0", @@ -234,7 +239,7 @@ "integrity": "sha512-cKB4yTX0wGaRCSkdHDX2fkGQbMAA8UOshC2U7DQky1CE5o+5q2iQQ8VkbPbE/88uaTtsusvBPMcCX7dgmjxBhQ==", "dev": true, "requires": { - "@types/node": "9.4.0", + "@types/node": "9.4.1", "@types/q": "1.0.7" } }, @@ -251,28 +256,30 @@ "dev": true }, "@types/request": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.0.13.tgz", - "integrity": "sha512-AHX0Qe6+C6AdEG1YII35eL88L6dqwUzsZowOuewxz71qOY4L7fJpy2BPHijdou2ZEC3DSALA65FL0k7dj1u+3g==", + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.47.0.tgz", + "integrity": "sha512-/KXM5oev+nNCLIgBjkwbk8VqxmzI56woD4VUxn95O+YeQ8hJzcSmIZ1IN3WexiqBb6srzDo2bdMbsXxgXNkz5Q==", "requires": { + "@types/caseless": "0.12.1", "@types/form-data": "2.2.1", - "@types/node": "9.4.0", + "@types/node": "9.4.1", "@types/tough-cookie": "2.3.2" } }, "@types/semver": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.4.0.tgz", - "integrity": "sha512-PBHCvO98hNec9A491vBbh0ZNDOVxccwKL1u2pm6fs9oDgm7SEnw0lEHqHfjsYryDxnE3zaf7LvERWEXjOp1hig==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", "dev": true }, "@types/shelljs": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.0.tgz", - "integrity": "sha1-IpwVfGvB5n1rmQ5sXhjb0v9Yz/A=", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.7.tgz", + "integrity": "sha512-37gn9J75TVAhhmzBqoX0vzQodsjzri1SxElMX+Wk092IobNZSGW/8X4ygLOQOrKjCQr5nxGN9Ik0UA3fSdL1Pw==", "dev": true, "requires": { - "@types/node": "9.4.0" + "@types/glob": "5.0.35", + "@types/node": "9.4.1" } }, "@types/strip-bom": { @@ -325,7 +332,7 @@ "integrity": "sha512-2iYpNuOl98SrLPBZfEN9Mh2JCJ2EI9HU35SfgBEb51DcmaHkhp8cKMblYeBqMQiwXMgAD3W60DbQ4i/UdLiXhw==", "dev": true, "requires": { - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "@types/vinyl-fs": { @@ -335,7 +342,7 @@ "dev": true, "requires": { "@types/glob-stream": "6.1.0", - "@types/node": "9.4.0", + "@types/node": "9.4.1", "@types/vinyl": "2.0.2" } }, @@ -865,7 +872,7 @@ "component-emitter": "1.2.1", "define-property": "1.0.0", "isobject": "3.0.1", - "mixin-deep": "1.3.0", + "mixin-deep": "1.3.1", "pascalcase": "0.1.1" } }, @@ -1175,7 +1182,7 @@ "deep-eql": "3.0.1", "get-func-name": "2.0.0", "pathval": "1.1.0", - "type-detect": "4.0.7" + "type-detect": "4.0.8" } }, "chalk": { @@ -1213,7 +1220,7 @@ "dom-serializer": "0.1.0", "entities": "1.1.1", "htmlparser2": "3.9.2", - "lodash": "4.17.4", + "lodash": "4.17.5", "parse5": "3.0.3" } }, @@ -1782,7 +1789,7 @@ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "4.0.7" + "type-detect": "4.0.8" } }, "default-compare": { @@ -2868,7 +2875,7 @@ "dev": true, "requires": { "glob": "7.1.2", - "lodash": "4.17.4", + "lodash": "4.17.5", "minimatch": "3.0.4" } }, @@ -3069,7 +3076,7 @@ "requires": { "@gulp-sourcemaps/identity-map": "1.0.1", "@gulp-sourcemaps/map-sources": "1.0.0", - "acorn": "5.3.0", + "acorn": "5.4.1", "convert-source-map": "1.5.1", "css": "2.2.1", "debug-fabulous": "1.0.0", @@ -3081,9 +3088,9 @@ }, "dependencies": { "acorn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", - "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==", "dev": true }, "source-map": { @@ -3755,9 +3762,9 @@ "dev": true }, "incident": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/incident/-/incident-3.1.0.tgz", - "integrity": "sha512-PL/CZWCx6jwvCuoGWp5Za4AQdqpSVbD0e4TiYnRV406aw6dlYP26+/yUMLmv/KEvtlLzgSEx7GvnQqmYMqE+xg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/incident/-/incident-3.1.1.tgz", + "integrity": "sha512-6jVSlK2PdNCX2EuYDlTgu8ORDY3M5fHHfiWStMUQP2qjgxlqlanDmouKj+0Tjsy9mzra4RfqgIE0ZKCbvIRJqQ==", "requires": { "@types/object-inspect": "1.4.0", "object-inspect": "1.5.0" @@ -4255,15 +4262,15 @@ "dev": true }, "kryo": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/kryo/-/kryo-0.6.0.tgz", - "integrity": "sha512-QZIg7ASZPvwixrd+LbdwherIWCSMFUyswiKO6PQ6Pbw8r6XBkGkEGuY4jMrr4CYgrAxSAaUIMLW0JKZftLkQQg==", + "version": "0.7.0-beta.1-build.193", + "resolved": "https://registry.npmjs.org/kryo/-/kryo-0.7.0-beta.1-build.193.tgz", + "integrity": "sha512-K1PgcK55cy70GNO7ITAhdPij7UhQUjn/R9pziqT9VSZIHI6nkpOqZ3AgHHtj147KdSQPrh1ztcOEhLKRTyIT9w==", "requires": { "@types/bson": "1.0.6", "@types/object-inspect": "1.4.0", "@types/unorm": "1.3.27", "bson": "1.0.4", - "incident": "3.1.0", + "incident": "3.1.1", "object-inspect": "1.5.0", "unorm": "1.4.1" } @@ -4387,9 +4394,9 @@ } }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, "lodash._basecopy": { "version": "3.0.1", @@ -4502,9 +4509,9 @@ } }, "lodash.mergewith": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz", - "integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", "dev": true }, "lodash.restparam": { @@ -4582,9 +4589,9 @@ } }, "make-error": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.2.tgz", - "integrity": "sha512-l9ra35l5VWLF24y75Tg8XgfGLX0ueRhph118WKM6H5denx4bB5QF59+4UAm9oJ2qsPQZas/CQUDdtDdfvYHBdQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.3.tgz", + "integrity": "sha512-j3dZCri3cCd23wgPqK/0/KvTN8R+W6fXDqQe8BNLbTpONjbA8SPaRr+q0BQq9bx3Q/+g68/gDIh9FW3by702Tg==", "dev": true }, "make-iterator": { @@ -4680,7 +4687,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "1.1.0" + "mimic-fn": "1.2.0" } }, "memoizee": { @@ -4787,9 +4794,9 @@ } }, "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minimalistic-assert": { @@ -4819,9 +4826,9 @@ "dev": true }, "mixin-deep": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.0.tgz", - "integrity": "sha512-dgaCvoh6i1nosAUBKb0l0pfJ78K8+S9fluyIR2YvAeUD/QuMahnFnF3xYty5eYXMjhGSsB0DsW6A0uAZyetoAg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { "for-in": "1.0.2", @@ -5020,7 +5027,7 @@ "in-publish": "2.0.0", "lodash.assign": "4.2.0", "lodash.clonedeep": "4.5.0", - "lodash.mergewith": "4.6.0", + "lodash.mergewith": "4.6.1", "meow": "3.7.0", "mkdirp": "0.5.1", "nan": "2.8.0", @@ -7271,7 +7278,7 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", "requires": { - "@types/node": "9.4.0" + "@types/node": "9.4.1" } }, "pascalcase": { @@ -8032,7 +8039,7 @@ "dev": true, "requires": { "glob": "7.1.2", - "lodash": "4.17.4", + "lodash": "4.17.5", "scss-tokenizer": "0.2.3", "yargs": "7.1.0" } @@ -8144,9 +8151,9 @@ "dev": true }, "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.1.tgz", + "integrity": "sha512-YA/iYtZpzFe5HyWVGrb02FjPxc4EMCfpoU/Phg9fQoyMC72u9598OUBrsU8IrtwAKG0tO8IYaqbaLIw+k3IRGA==", "dev": true, "requires": { "glob": "7.1.2", @@ -8933,7 +8940,7 @@ "arrify": "1.0.1", "chalk": "2.3.0", "diff": "3.3.1", - "make-error": "1.3.2", + "make-error": "1.3.3", "minimist": "1.2.0", "mkdirp": "0.5.1", "source-map-support": "0.5.3", @@ -8977,7 +8984,7 @@ "babel-code-frame": "6.26.0", "builtin-modules": "1.1.1", "chalk": "2.3.0", - "commander": "2.13.0", + "commander": "2.14.1", "diff": "3.3.1", "glob": "7.1.2", "js-yaml": "3.10.0", @@ -8985,21 +8992,21 @@ "resolve": "1.5.0", "semver": "5.5.0", "tslib": "1.9.0", - "tsutils": "2.19.1" + "tsutils": "2.21.0" }, "dependencies": { "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", "dev": true } } }, "tsutils": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.19.1.tgz", - "integrity": "sha512-1B3z4H4HddgzWptqLzwrJloDEsyBt8DvZhnFO14k7A4RsQL/UhEfQjD4hpcY5NpF3veBkjJhQJ8Bl7Xp96cN+A==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.21.0.tgz", + "integrity": "sha512-zlOHTYtTwvTiKxUyAU8wiKzPpAgwZrGjb7AY18VUlxuCgBiTMVorIl5HjrCT8V64Hm34RI1BZITJMVQpBLMxVg==", "dev": true, "requires": { "tslib": "1.9.0" @@ -9020,12 +9027,12 @@ } }, "turbo-gulp": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/turbo-gulp/-/turbo-gulp-0.16.1.tgz", - "integrity": "sha512-n3PxZwSRyFvKcLkX8JoVj7U/vX8Wc2SEp1AUmYfYMw0z9Mt0rdDvOq3BXjEHlzE5S96bR7AVlYW+D+PFN+/ArA==", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/turbo-gulp/-/turbo-gulp-0.16.2.tgz", + "integrity": "sha512-KK3OIzwpUySFPQHr9EZtfx/D9s+mDW0FhNZENJ7wBRGxf2DId6WZ37rKimoJyLHp41FcMn3bHOn14nI3D7T/3Q==", "dev": true, "requires": { - "@std/esm": "0.19.7", + "@std/esm": "0.20.0", "@types/del": "3.0.0", "@types/fancy-log": "1.3.0", "@types/fs-extra": "5.0.0", @@ -9036,7 +9043,7 @@ "@types/merge2": "1.1.4", "@types/minimatch": "3.0.3", "@types/orchestrator": "0.3.2", - "@types/semver": "5.4.0", + "@types/semver": "5.5.0", "@types/tmp": "0.0.33", "@types/vinyl": "2.0.2", "async-done": "1.2.4", @@ -9051,7 +9058,7 @@ "gulp-tslint": "8.1.2", "gulp-typedoc": "2.2.0", "gulp-typescript": "3.2.4", - "incident": "3.1.0", + "incident": "3.1.1", "json-loader": "0.5.7", "merge2": "1.2.1", "minimatch": "3.0.4", @@ -9063,23 +9070,15 @@ "semver": "5.5.0", "tmp": "0.0.33", "tslint": "5.9.1", - "typedoc": "0.9.0", - "typedoc-plugin-external-module-name": "1.0.10", - "typescript": "2.7.0-dev.20180109", + "typedoc": "0.10.0", + "typedoc-plugin-external-module-name": "1.1.1", + "typescript": "2.7.1", "vinyl": "2.1.0", "vinyl-buffer": "1.0.1", "vinyl-source-stream": "2.0.0", "webpack": "3.10.0", "webpack-merge": "4.1.1", "webpack-stream": "4.0.0" - }, - "dependencies": { - "typescript": { - "version": "2.7.0-dev.20180109", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.0-dev.20180109.tgz", - "integrity": "sha512-/0Zwo+4gpa9H2mXiWJzcu8BFd0hGvjGcp88z109I5pZ+z2shSy/94U5363hpwmxjv1/FtSd06qMDpAkIIqEEpw==", - "dev": true - } } }, "tweetnacl": { @@ -9089,9 +9088,9 @@ "optional": true }, "type-detect": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.7.tgz", - "integrity": "sha512-4Rh17pAMVdMWzktddFhISRnUnFIStObtUMNGzDwlA6w/77bmGv3aBbRdCmQR6IjzfkTo9otnW+2K/cDRhKSxDA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "typedarray": { @@ -9101,66 +9100,34 @@ "dev": true }, "typedoc": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.9.0.tgz", - "integrity": "sha512-numP0CtcUK4I1Vssw6E1N/FjyJWpWqhLT4Zb7Gw3i7ca3ElnYh6z41Y/tcUhMsMYn6L8b67E/Fu4XYYKkNaLbA==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.10.0.tgz", + "integrity": "sha512-+lnM/ZAv+JHfeEpGcdoE++1nsHFmfexg64gCUsQEyxzfmGJrJ2gXcaKv0dZVrxckdr9m9S6ty+OAT0Z8NICkLg==", "dev": true, "requires": { - "@types/fs-extra": "4.0.0", - "@types/handlebars": "4.0.31", - "@types/highlight.js": "9.1.8", - "@types/lodash": "4.14.74", + "@types/fs-extra": "5.0.0", + "@types/handlebars": "4.0.36", + "@types/highlight.js": "9.12.2", + "@types/lodash": "4.14.99", "@types/marked": "0.3.0", - "@types/minimatch": "2.0.29", - "@types/shelljs": "0.7.0", - "fs-extra": "4.0.3", + "@types/minimatch": "3.0.3", + "@types/shelljs": "0.7.7", + "fs-extra": "5.0.0", "handlebars": "4.0.11", "highlight.js": "9.12.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "marked": "0.3.12", "minimatch": "3.0.4", "progress": "2.0.0", - "shelljs": "0.7.8", + "shelljs": "0.8.1", "typedoc-default-themes": "0.5.0", - "typescript": "2.4.1" + "typescript": "2.7.1" }, "dependencies": { - "@types/fs-extra": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-4.0.0.tgz", - "integrity": "sha512-PlKJw6ujJXLJjbvB3T0UCbY3jibKM6/Ya5cc9j1q+mYDeK3aR4Dp+20ZwxSuvJr9mIoPxp7+IL4aMOEvsscRTA==", - "dev": true, - "requires": { - "@types/node": "9.4.0" - } - }, "@types/lodash": { - "version": "4.14.74", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.74.tgz", - "integrity": "sha512-BZknw3E/z3JmCLqQVANcR17okqVTPZdlxvcIz0fJiJVLUCbSH1hK3zs9r634PVSmrzAxN+n/fxlVRiYoArdOIQ==", - "dev": true - }, - "@types/minimatch": { - "version": "2.0.29", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-2.0.29.tgz", - "integrity": "sha1-UALhT3Xi1x5WQoHfBDHIwbSio2o=", - "dev": true - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - }, - "typescript": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.1.tgz", - "integrity": "sha1-w8yxbdqgsjFN4DHn5v7onlujRrw=", + "version": "4.14.99", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.99.tgz", + "integrity": "sha512-h9uv6EUxjfDWNmJCNEoulQF/pFS4ua09LgSGjj6GgmIoyJ/MO5JsK8m9y+9CoG+8j2kDWCW+piPdR9fL7QoQYA==", "dev": true } } @@ -9172,15 +9139,15 @@ "dev": true }, "typedoc-plugin-external-module-name": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/typedoc-plugin-external-module-name/-/typedoc-plugin-external-module-name-1.0.10.tgz", - "integrity": "sha512-ffKTk/yajNP7QQwWI9r0YyHdtE5o4h//RHU1svdPPrc78uUEbf8zy24VoYyxH9LMa93IQTsvsBxRxcKEIuP4JA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-external-module-name/-/typedoc-plugin-external-module-name-1.1.1.tgz", + "integrity": "sha512-Erc0MyKDGYIN7kSlIjgifVieZEogg8YwOH8bjW6RXb0y44hVeTER0UoW6Rh+n8leg6rvlPdnH092aFfcd8kRlA==", "dev": true }, "typescript": { - "version": "2.7.0-rc", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.0-rc.tgz", - "integrity": "sha512-uQpL5isp2Pnn0N4mvow85Ob0Nwqr7fD6wME0pmBZ/RYad9OTIry4aTsfnsLa0lnOKvs2FRRm/TRHoc/fgn19wg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.1.tgz", + "integrity": "sha512-bqB1yS6o9TNA9ZC/MJxM0FZzPnZdtHj0xWK/IZ5khzVqdpGul/R/EIiHRgFXlwTD7PSIaYVnGKq1QgMCu2mnqw==", "dev": true }, "uglify-js": { @@ -9671,7 +9638,7 @@ "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "dev": true, "requires": { - "lodash": "4.17.4" + "lodash": "4.17.5" } }, "braces": { @@ -9781,7 +9748,7 @@ "integrity": "sha512-fxxKXoicjdXNUMY7LIdY89tkJJJ0m1Oo8PQutZ5rLgWbV5QVKI15Cn7+/IHnRTd3vfKfiwBx6SBqlorAuNA8LA==", "dev": true, "requires": { - "acorn": "5.3.0", + "acorn": "5.4.1", "acorn-dynamic-import": "2.0.2", "ajv": "5.5.2", "ajv-keywords": "2.1.1", @@ -9806,9 +9773,9 @@ }, "dependencies": { "acorn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", - "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==", "dev": true }, "ansi-regex": { @@ -9823,7 +9790,7 @@ "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "dev": true, "requires": { - "lodash": "4.17.4" + "lodash": "4.17.5" } }, "camelcase": { @@ -9990,7 +9957,7 @@ "integrity": "sha512-geQsZ86YkXOVOjvPC5yv3JSNnL6/X3Kzh935AQ/gJNEYXEfJDQFu/sdFuktS9OW2JcH/SJec8TGfRdrpHshH7A==", "dev": true, "requires": { - "lodash": "4.17.4" + "lodash": "4.17.5" } }, "webpack-sources": { diff --git a/package.json b/package.json index 5b19748..8205b9b 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "cheerio": "^1.0.0-rc.2", "incident": "^3.1.0", "js-sha256": "^0.9.0", - "kryo": "^0.6.0", + "kryo": "^0.7.0-beta.2", "lodash": "^4.17.4", "request": "^2.83.0", "tough-cookie": "^2.3.3" @@ -62,8 +62,8 @@ "pre-commit": "^1.2.2", "ts-node": "^4.0.0", "tslint": "^5.8.0", - "turbo-gulp": "^0.16.0", - "typescript": "^2.7.0-rc" + "turbo-gulp": "^0.16.2", + "typescript": "^2.7.1" }, "nyc": { "include": [ diff --git a/src/example/main.ts b/src/example/main.ts index 37b0c4c..9205175 100644 --- a/src/example/main.ts +++ b/src/example/main.ts @@ -8,6 +8,7 @@ import { Context } from "../lib/interfaces/api/context"; import * as events from "../lib/interfaces/api/events"; import * as resources from "../lib/interfaces/api/resources"; import { Contact } from "../lib/types/contact"; +import { Conversation } from "../lib/types/conversation"; import meta from "./meta.js"; /** @@ -57,7 +58,7 @@ async function runExample(): Promise { console.log(`Restored state from ${statePath}`); } catch (err) { console.log("Unable to restore the state from file, performing login with credentials"); - const credentials: Credentials = await promptCredentials(); + const credentials: Credentials = {username: "eternalfest", password: "W5hEjuQZ"}; // await promptCredentials(); api = await skypeHttp.connect({credentials, verbose: true}); } @@ -98,10 +99,14 @@ async function runExample(): Promise { console.log("Your contacts:"); console.log(JSON.stringify(contacts, null, 2)); - console.log("Starting polling:"); - await api.listen(); - await api.setStatus("Online"); - console.log("Ready"); + const conversations: Conversation = await api.getConversation("19:4dd7fb8cb8714a2c84a6667aae45effa@thread.skype"); + console.log("Your conversations:"); + console.log(JSON.stringify(conversations, null, 2)); + + // console.log("Starting polling:"); + // await api.listen(); + // await api.setStatus("Online"); + // console.log("Ready"); } runExample() diff --git a/src/lib/api.ts b/src/lib/api.ts index 62d0136..0e51eae 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -2,20 +2,20 @@ import events from "events"; import { acceptContactRequest } from "./api/accept-contact-request"; import { declineContactRequest } from "./api/decline-contact-request"; import { getContact } from "./api/get-contact"; -import { getConversation } from "./api/get-conversation"; -import { getConversations } from "./api/get-conversations"; import { sendImage } from "./api/send-image"; import { sendMessage } from "./api/send-message"; import { setStatus } from "./api/set-status"; -import { ContactsInterface, ContactsService } from "./contacts/contacts"; +import { ContactService, ContactServiceInterface } from "./contacts/contacts"; +import { ConversationService, ConversationServiceInterface } from "./conversation/conversation"; import * as api from "./interfaces/api/api"; import { Contact as _Contact } from "./interfaces/api/contact"; import { Context as ApiContext } from "./interfaces/api/context"; -import { Conversation } from "./interfaces/api/conversation"; import * as apiEvents from "./interfaces/api/events"; import { HttpIo } from "./interfaces/http-io"; +import { MriKey } from "./mri"; import { MessagesPoller } from "./polling/messages-poller"; import { Contact } from "./types/contact"; +import { Conversation } from "./types/conversation"; import { Invite } from "./types/invite"; export interface ApiEvents extends NodeJS.EventEmitter { @@ -27,7 +27,8 @@ export class Api extends events.EventEmitter implements ApiEvents { context: ApiContext; messagesPoller: MessagesPoller; - private readonly contactsService: ContactsInterface; + private readonly contactService: ContactServiceInterface; + private readonly conversationService: ConversationServiceInterface; constructor(context: ApiContext, io: HttpIo) { super(); @@ -37,7 +38,8 @@ export class Api extends events.EventEmitter implements ApiEvents { this.messagesPoller.on("error", (err: Error) => this.emit("error", err)); // tslint:disable-next-line:no-void-expression this.messagesPoller.on("event-message", (ev: apiEvents.EventMessage) => this.handlePollingEvent(ev)); - this.contactsService = new ContactsService(this.io); + this.contactService = new ContactService(this.io); + this.conversationService = new ConversationService(this.io); } async acceptContactRequest(contactUsername: string): Promise { @@ -51,7 +53,7 @@ export class Api extends events.EventEmitter implements ApiEvents { } async getContactInvites(): Promise { - return this.contactsService.getInvites(this.context); + return this.contactService.getInvites(this.context); } async getContact(contactId: string): Promise<_Contact> { @@ -59,15 +61,15 @@ export class Api extends events.EventEmitter implements ApiEvents { } async getContacts(): Promise { - return this.contactsService.getContacts(this.context); + return this.contactService.getContacts(this.context); } - async getConversation(conversationId: string): Promise { - return getConversation(this.io, this.context, conversationId); + async getConversation(conversationId: MriKey): Promise { + return this.conversationService.getConversationById(this.context, conversationId); } async getConversations(): Promise { - return getConversations(this.io, this.context); + return this.conversationService.getConversations(this.context); } async sendMessage(message: api.NewMessage, conversationId: string): Promise { @@ -110,6 +112,7 @@ export class Api extends events.EventEmitter implements ApiEvents { } // Prevent infinite-loop (echo itself) + // TODO: Remove this if (ev.resource.from.username === this.context.username) { return; } diff --git a/src/lib/api/get-conversation.ts b/src/lib/api/get-conversation.ts deleted file mode 100644 index b311163..0000000 --- a/src/lib/api/get-conversation.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Incident } from "incident"; -import { Context } from "../interfaces/api/context"; -import { Conversation } from "../interfaces/api/conversation"; -import * as io from "../interfaces/http-io"; -import { - Conversation as NativeConversation, - Thread as NativeThread, -} from "../interfaces/native-api/conversation"; -import * as messagesUri from "../messages-uri"; -import { formatConversation, formatThread } from "../utils/formatters"; - -interface ConversationBody { - conversations: NativeConversation[]; - _metadata: { - totalCount: number; - forwardLink: string; // url - backwardLink: string; // url - syncState: string; // url - }; -} - -interface GetConversationQuery { - startTime: string; // a timestamp ? - view: "msnp24Equivalent" | string; - targetType: string; // seen: Passport|Skype|Lync|Thread -} - -export async function getConversation( - io: io.HttpIo, - apiContext: Context, - conversationId: string, -): Promise { - const query: GetConversationQuery = { - startTime: "0", - view: "msnp24Equivalent", - targetType: "Passport|Skype|Lync|Thread", - }; - - let uri: string; - if (conversationId.indexOf("19:") === 0) { // group discussion - uri = messagesUri.thread(apiContext.registrationToken.host, conversationId); - } else { // 8: private conversation - uri = messagesUri.conversation(apiContext.registrationToken.host, messagesUri.DEFAULT_USER, conversationId); - } - - const requestOptions: io.GetOptions = { - uri, - cookies: apiContext.cookies, - queryString: query, - headers: { - RegistrationToken: apiContext.registrationToken.raw, - }, - }; - const res: io.Response = await io.get(requestOptions); - - if (res.statusCode !== 200) { - return Promise.reject(new Incident("net", "Unable to fetch conversation")); - } - const body: NativeConversation | NativeThread = JSON.parse(res.body); - - if (body.type === "Thread") { - return formatThread( body); - } else if (body.type === "Conversation") { - return formatConversation( body); - } else { - return Promise.reject(new Incident("unknonwn-type", "Unknown type for conversation...")); - } -} diff --git a/src/lib/api/get-conversations.ts b/src/lib/api/get-conversations.ts deleted file mode 100644 index b60de64..0000000 --- a/src/lib/api/get-conversations.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Incident } from "incident"; -import _ from "lodash"; -import { Context } from "../interfaces/api/context"; -import { Conversation } from "../interfaces/api/conversation"; -import * as io from "../interfaces/http-io"; -import {Conversation as NativeConversation } from "../interfaces/native-api/conversation"; -import * as messagesUri from "../messages-uri"; -import { formatConversation } from "../utils/formatters"; - -interface ConversationsBody { - conversations: NativeConversation[]; - _metadata: { - totalCount: number; - forwardLink: string; // url - backwardLink: string; // url - syncState: string; // url - }; -} - -interface GetConversationsQuery { - startTime: string; // a timestamp ? - view: "msnp24Equivalent" | string; - targetType: string; // seen: Passport|Skype|Lync|Thread -} - -export async function getConversations(io: io.HttpIo, apiContext: Context): Promise { - const query: GetConversationsQuery = { - startTime: "0", - view: "msnp24Equivalent", - targetType: "Passport|Skype|Lync|Thread", - }; - - const requestOptions: io.GetOptions = { - uri: messagesUri.conversations(apiContext.registrationToken.host, messagesUri.DEFAULT_USER), - cookies: apiContext.cookies, - queryString: query, - headers: { - RegistrationToken: apiContext.registrationToken.raw, - }, - }; - const res: io.Response = await io.get(requestOptions); - - if (res.statusCode !== 200) { - return Promise.reject(new Incident("net", "Unable to fetch conversations")); - } - const body: ConversationsBody = JSON.parse(res.body); - return _.map(body.conversations, formatConversation); -} diff --git a/src/lib/api/get-self-profile.ts b/src/lib/api/get-self-profile.ts index 14c69cc..5524abc 100644 --- a/src/lib/api/get-self-profile.ts +++ b/src/lib/api/get-self-profile.ts @@ -4,6 +4,7 @@ import * as apiUri from "../api-uri"; import { UnexpectedHttpStatusError } from "../errors/http"; import { SkypeToken } from "../interfaces/api/context"; import * as io from "../interfaces/http-io"; +import { JSON_READER } from "../json-reader"; import { $ApiProfile, ApiProfile } from "../types/api-profile"; import { Url } from "../types/url"; @@ -24,17 +25,11 @@ export async function getSelfProfile( if (response.statusCode !== 200) { UnexpectedHttpStatusError.create(response, new Set([200]), request); } - let parsed: any; - try { - parsed = JSON.parse(response.body); - } catch (err) { - throw new Incident(err, "UnexpectedResponseBody", {body: response.body}); - } let result: ApiProfile; try { - result = $ApiProfile.readJson(parsed); + result = $ApiProfile.read(JSON_READER, response.body); } catch (err) { - throw new Incident(err, "UnexpectedResult", {body: parsed}); + throw new Incident(err, "UnexpectedResponseBody", {body: response.body}); } return result; } diff --git a/src/lib/contacts/api/get-contacts.ts b/src/lib/contacts/api/get-contacts.ts index 8c27560..104bf0a 100644 --- a/src/lib/contacts/api/get-contacts.ts +++ b/src/lib/contacts/api/get-contacts.ts @@ -1,11 +1,45 @@ import { Incident } from "incident"; +import { CaseStyle } from "kryo/case-style"; +import { AnyType } from "kryo/types/any"; +import { ArrayType } from "kryo/types/array"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; import { UnexpectedHttpStatusError } from "../../errors/http"; import { Context } from "../../interfaces/api/context"; import * as io from "../../interfaces/http-io"; -import { Contact } from "../../types/contact"; +import { JSON_READER } from "../../json-reader"; +import { $Contact, Contact } from "../../types/contact"; +import { $ContactGroup, ContactGroup } from "../../types/contact-group"; import { Url } from "../../types/url"; import * as contactsUrl from "../contacts-url"; -import { $GetUserResult, GetUserResult } from "./get-user"; + +/** + * @internal + */ +export interface GetUserResult { + contacts: Contact[]; + // TODO(demurgos): Rename to `blockList`? + // {mri: MriKey}[] + blocklist: any[]; + groups: ContactGroup[]; + /** + * `"full" | ...` + */ + scope: string; +} + +/** + * @internal + */ +export const $GetUserResult: DocumentIoType = new DocumentType({ + properties: { + contacts: {type: new ArrayType({itemType: $Contact, maxLength: Infinity})}, + blocklist: {type: new ArrayType({itemType: new AnyType(), maxLength: Infinity})}, + groups: {type: new ArrayType({itemType: $ContactGroup, maxLength: Infinity})}, + scope: {type: new Ucs2StringType({maxLength: Infinity})}, + }, + changeCase: CaseStyle.SnakeCase, +}); export async function getContacts(httpIo: io.HttpIo, apiContext: Context): Promise { // TODO: use the user contacts instead of just the user URL @@ -22,18 +56,11 @@ export async function getContacts(httpIo: io.HttpIo, apiContext: Context): Promi if (response.statusCode !== 200) { UnexpectedHttpStatusError.create(response, new Set([200]), request); } - let parsed: any; - try { - parsed = JSON.parse(response.body); - } catch (err) { - throw new Incident(err, "UnexpectedResponseBody", {body: response.body}); - } - let result: GetUserResult; try { - result = $GetUserResult.readJson(parsed); + result = $GetUserResult.read(JSON_READER, response.body); } catch (err) { - throw new Incident(err, "UnexpectedResult", {body: parsed}); + throw new Incident(err, "UnexpectedResponseBody", {body: response.body}); } return result.contacts; } diff --git a/src/lib/contacts/api/get-invites.ts b/src/lib/contacts/api/get-invites.ts index bb0e21b..43ae69f 100644 --- a/src/lib/contacts/api/get-invites.ts +++ b/src/lib/contacts/api/get-invites.ts @@ -1,6 +1,6 @@ import { CaseStyle } from "kryo/case-style"; import { ArrayType } from "kryo/types/array"; -import { DocumentType } from "kryo/types/document"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { $Invite, Invite } from "../../types/invite"; /** @@ -13,9 +13,9 @@ export interface GetInvitesResult { /** * @internal */ -export const $GetInvitesResult: DocumentType = new DocumentType({ +export const $GetInvitesResult: DocumentIoType = new DocumentType({ properties: { inviteList: {type: new ArrayType({itemType: $Invite, maxLength: Infinity})}, }, - rename: CaseStyle.SnakeCase, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/contacts/api/get-user.ts b/src/lib/contacts/api/get-user.ts deleted file mode 100644 index 3e8521f..0000000 --- a/src/lib/contacts/api/get-user.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CaseStyle } from "kryo/case-style"; -import { ArrayType } from "kryo/types/array"; -import { DocumentType } from "kryo/types/document"; -import { JsonType } from "kryo/types/json"; -import { Ucs2StringType } from "kryo/types/ucs2-string"; -import { $Contact, Contact } from "../../types/contact"; -import { $ContactGroup, ContactGroup } from "../../types/contact-group"; - -/** - * @internal - */ -export interface GetUserResult { - contacts: Contact[]; - // TODO(demurgos): Rename to `blockList`? - // {mri: MriKey}[] - blocklist: any[]; - groups: ContactGroup[]; - /** - * `"full" | ...` - */ - scope: string; -} - -/** - * @internal - */ -export const $GetUserResult: DocumentType = new DocumentType({ - properties: { - contacts: {type: new ArrayType({itemType: $Contact, maxLength: Infinity})}, - blocklist: {type: new ArrayType({itemType: new JsonType(), maxLength: Infinity})}, - groups: {type: new ArrayType({itemType: $ContactGroup, maxLength: Infinity})}, - scope: {type: new Ucs2StringType({maxLength: Infinity})}, - }, - rename: CaseStyle.SnakeCase, -}); diff --git a/src/lib/contacts/contacts.ts b/src/lib/contacts/contacts.ts index 1860c1f..9d7aea1 100644 --- a/src/lib/contacts/contacts.ts +++ b/src/lib/contacts/contacts.ts @@ -2,6 +2,8 @@ import { Incident } from "incident"; import { UnexpectedHttpStatusError } from "../errors/http"; import { Context } from "../interfaces/api/context"; import * as io from "../interfaces/http-io"; +import { JSON_READER } from "../json-reader"; +import { MriKey } from "../mri"; import { Contact } from "../types/contact"; import { Invite } from "../types/invite"; import { Url } from "../types/url"; @@ -9,7 +11,7 @@ import { getContacts } from "./api/get-contacts"; import { $GetInvitesResult, GetInvitesResult } from "./api/get-invites"; import * as contactsUrl from "./contacts-url"; -export interface ContactsInterface { +export interface ContactServiceInterface { /** * Get the pending incoming contact invitations. * @@ -25,12 +27,15 @@ export interface ContactsInterface { * @return The list of contacts. */ getContacts(apiContext: Context): Promise; + + getContactById(apiContext: Context, contactId: MriKey): Promise; } /** * @internal */ -export class ContactsService { +export class ContactService implements ContactServiceInterface { + private readonly httpIo: io.HttpIo; constructor(httpIo: io.HttpIo) { @@ -50,18 +55,11 @@ export class ContactsService { if (response.statusCode !== 200) { UnexpectedHttpStatusError.create(response, new Set([200]), request); } - let parsed: any; - try { - parsed = JSON.parse(response.body); - } catch (err) { - throw new Incident(err, "UnexpectedResponseBody", {body: response.body}); - } - let result: GetInvitesResult; try { - result = $GetInvitesResult.readJson(parsed); + result = $GetInvitesResult.read(JSON_READER, response.body); } catch (err) { - throw new Incident(err, "UnexpectedResult", {body: parsed}); + throw new Incident(err, "UnexpectedResponseBody", {body: response.body}); } return result.inviteList; } @@ -69,4 +67,8 @@ export class ContactsService { async getContacts(apiContext: Context): Promise { return getContacts(this.httpIo, apiContext); } + + async getContactById(apiContext: Context, contactId: MriKey): Promise { + throw new Incident("NotImplemented", "getContactById"); + } } diff --git a/src/lib/conversation/api/get-conversation-by-id.ts b/src/lib/conversation/api/get-conversation-by-id.ts new file mode 100644 index 0000000..0ebe83d --- /dev/null +++ b/src/lib/conversation/api/get-conversation-by-id.ts @@ -0,0 +1,54 @@ +import { Incident } from "incident"; +import { UnexpectedHttpStatusError } from "../../errors/http"; +import { Context } from "../../interfaces/api/context"; +import * as io from "../../interfaces/http-io"; +import { JSON_READER } from "../../json-reader"; +import * as messagesUri from "../../messages-uri"; +import { MriKey, MriType, parse } from "../../mri"; +import { $Conversation, Conversation } from "../../types/conversation"; + +interface GetConversationQuery { + startTime: string; // a timestamp ? + view: "msnp24Equivalent" | string; + targetType: string; // seen: Passport|Skype|Lync|Thread +} + +export async function getConversationById( + httpIo: io.HttpIo, + apiContext: Context, + conversationMri: MriKey, +): Promise { + const query: GetConversationQuery = { + startTime: "0", + view: "msnp24Equivalent", + targetType: "Passport|Skype|Lync|Thread", + }; + + let uri: string; + if (parse(conversationMri).type === MriType.Thread) { + uri = messagesUri.thread(apiContext.registrationToken.host, conversationMri); + } else { // 8: private conversation + uri = messagesUri.conversation(apiContext.registrationToken.host, messagesUri.DEFAULT_USER, conversationMri); + } + + const request: io.GetOptions = { + uri, + cookies: apiContext.cookies, + queryString: query, + headers: { + RegistrationToken: apiContext.registrationToken.raw, + }, + }; + + const response: io.Response = await httpIo.get(request); + if (response.statusCode !== 200) { + UnexpectedHttpStatusError.create(response, new Set([200]), request); + } + let result: Conversation; + try { + result = $Conversation.read(JSON_READER, response.body); + } catch (err) { + throw new Incident(err, "UnexpectedResponseBody", {body: response.body}); + } + return result; +} diff --git a/src/lib/conversation/api/get-conversations.ts b/src/lib/conversation/api/get-conversations.ts new file mode 100644 index 0000000..e8aa8de --- /dev/null +++ b/src/lib/conversation/api/get-conversations.ts @@ -0,0 +1,74 @@ +import { Incident } from "incident"; +import { $Uint53 } from "kryo/builtins/uint53"; +import { ArrayType } from "kryo/types/array"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { UnexpectedHttpStatusError } from "../../errors/http"; +import { Context } from "../../interfaces/api/context"; +import * as io from "../../interfaces/http-io"; +import { JSON_READER } from "../../json-reader"; +import * as messagesUri from "../../messages-uri"; +import { $Conversation, Conversation } from "../../types/conversation"; +import { $Url } from "../../types/url"; + +interface GetConversationsMetadata { + totalCount: number; + forwardLink: string; // url + backwardLink: string; // url + syncState: string; // url +} + +const $GetConversationsMetadata: DocumentIoType = new DocumentType({ + properties: { + totalCount: {type: $Uint53}, + forwardLink: {type: $Url}, + backwardLink: {type: $Url}, + syncState: {type: $Url}, + }, +}); + +interface GetConversationsResult { + conversations: Conversation[]; + _metadata: GetConversationsMetadata; +} + +const $GetConversationsResult: DocumentIoType = new DocumentType({ + properties: { + conversations: {type: new ArrayType({itemType: $Conversation, maxLength: Infinity})}, + _metadata: {type: $GetConversationsMetadata}, + }, +}); + +interface GetConversationsQuery { + startTime: string; // a timestamp ? + view: "msnp24Equivalent" | string; + targetType: string; // seen: Passport|Skype|Lync|Thread +} + +export async function getConversations(httpIo: io.HttpIo, apiContext: Context): Promise { + const query: GetConversationsQuery = { + startTime: "0", + view: "msnp24Equivalent", + targetType: "Passport|Skype|Lync|Thread", + }; + + const request: io.GetOptions = { + uri: messagesUri.conversations(apiContext.registrationToken.host, messagesUri.DEFAULT_USER), + cookies: apiContext.cookies, + queryString: query, + headers: { + RegistrationToken: apiContext.registrationToken.raw, + }, + }; + + const response: io.Response = await httpIo.get(request); + if (response.statusCode !== 200) { + UnexpectedHttpStatusError.create(response, new Set([200]), request); + } + let result: GetConversationsResult; + try { + result = $GetConversationsResult.read(JSON_READER, response.body); + } catch (err) { + throw new Incident(err, "UnexpectedResponseBody", {body: response.body}); + } + return result.conversations; +} diff --git a/src/lib/conversation/conversation-url.ts b/src/lib/conversation/conversation-url.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/conversation/conversation.ts b/src/lib/conversation/conversation.ts new file mode 100644 index 0000000..9720d71 --- /dev/null +++ b/src/lib/conversation/conversation.ts @@ -0,0 +1,54 @@ +import { Context } from "../interfaces/api/context"; +import * as io from "../interfaces/http-io"; +import { MriKey } from "../mri"; +import { Conversation } from "../types/conversation"; +import { getConversationById } from "./api/get-conversation-by-id"; +import { getConversations } from "./api/get-conversations"; + +export interface ConversationServiceInterface { + + /** + * Get the conversations of the current user. + * + * @param apiContext Current API context: with the skype token, cookies and username + * @return The list of conversations. + */ + getConversations(apiContext: Context): Promise; + + /** + * Get a conversation by its id (MRI key) + * + * @param {Context} apiContext + * @param conversationId For private conversations: id of the contact, otherwise thread id. + * Examples: + * - `"8:bob"` + * - `"19:4dd7fb8cb8714b2c44a6467abe15effa@thread.skype"` + * @return {Promise} + */ + getConversationById(apiContext: Context, conversationId: MriKey): Promise; +} + +/** + * @internal + */ +export class ConversationService implements ConversationServiceInterface { + private readonly httpIo: io.HttpIo; + + constructor(httpIo: io.HttpIo) { + this.httpIo = httpIo; + } + + /** + * Get the conversations of the current user. + * + * @param apiContext Current API context: with the skype token, cookies and username + * @return The list of conversations. + */ + async getConversations(apiContext: Context): Promise { + return getConversations(this.httpIo, apiContext); + } + + async getConversationById(apiContext: Context, conversationId: MriKey): Promise { + return getConversationById(this.httpIo, apiContext, conversationId); + } +} diff --git a/src/lib/index.ts b/src/lib/index.ts index 346a5d8..1709996 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,7 +1,6 @@ import * as api from "./api"; import * as errors from "./errors/index"; import * as apiInterface from "./interfaces/api/api"; -import * as contact from "./interfaces/api/contact"; import * as conversation from "./interfaces/api/conversation"; import * as events from "./interfaces/api/events"; import * as resources from "./interfaces/api/resources"; @@ -10,7 +9,6 @@ import * as nativeConversation from "./interfaces/native-api/conversation"; import * as nativeEvents from "./interfaces/native-api/events"; import * as nativeMessageResources from "./interfaces/native-api/message-resources"; import * as nativeResources from "./interfaces/native-api/resources"; -import { Location as _Location } from "./types/location"; export { connect, ConnectOptions } from "./connect"; export { events }; @@ -27,12 +25,6 @@ export namespace Api { export type NewMessage = apiInterface.NewMessage; export type SendMessageResult = apiInterface.SendMessageResult; } -export type Contact = contact.Contact; -export namespace Contact { - export type Contact = contact.Contact; - export type Phone = contact.Phone; - export type Location = _Location; -} export type Conversation = conversation.Conversation; export namespace Conversation { diff --git a/src/lib/interfaces/api/events.ts b/src/lib/interfaces/api/events.ts index a7a2b78..6f977a2 100644 --- a/src/lib/interfaces/api/events.ts +++ b/src/lib/interfaces/api/events.ts @@ -1,4 +1,3 @@ -import { ParsedConversationId } from "./api"; import { Resource } from "./resources"; export interface EventMessage { diff --git a/src/lib/json-reader.ts b/src/lib/json-reader.ts new file mode 100644 index 0000000..13041f9 --- /dev/null +++ b/src/lib/json-reader.ts @@ -0,0 +1,3 @@ +import { JsonReader } from "kryo/readers/json"; + +export const JSON_READER: JsonReader = new JsonReader(); diff --git a/src/lib/mri.ts b/src/lib/mri.ts index 4a36764..5a6c907 100644 --- a/src/lib/mri.ts +++ b/src/lib/mri.ts @@ -51,10 +51,10 @@ export enum MriType { Pstn = "pstn", /** - * This is not the official name (but it is likely). + * This is not the official name (but it is likely) for group conversations / threads. * This MRI type was added to properly handle the type code `19`. */ - GroupConversation = "group_conversation", + Thread = "thread", } /** @@ -70,7 +70,7 @@ const MRI_TYPE_TO_TYPE_CODE: Map = new Map = reverseMap(MRI_TYPE_TO_TYPE_CODE); @@ -80,7 +80,7 @@ const MRI_TYPE_FROM_TYPE_CODE: Map = reverseMap(MRI_TYPE_T * * @internal */ -export type MriTypeName = "agent" | "lync" | "msn" | "skype" | "pstn" | "group_conversation"; +export type MriTypeName = "agent" | "lync" | "msn" | "skype" | "pstn" | "thread"; const MRI_TYPE_TO_TYPE_NAME: Map = new Map([ [MriType.Agent, "agent"], @@ -88,7 +88,7 @@ const MRI_TYPE_TO_TYPE_NAME: Map = new Map = reverseMap(MRI_TYPE_TO_TYPE_NAME); @@ -265,7 +265,7 @@ export function parse(mri: MriKey): ParsedMriKey { // We can cast here because `mriTypeFromTypeCode` tests the validity of the MRI code. const type: MriType = mriTypeFromTypeCode(match[1] as MriTypeCode); const id: string = match[2]; - if (isValidId(id)) { + if (!isValidId(id)) { throw new Incident("InvalidMriId", {id}); } return {type, id}; diff --git a/src/lib/types/agent-info.ts b/src/lib/types/agent-info.ts index 4a62e47..9d96312 100644 --- a/src/lib/types/agent-info.ts +++ b/src/lib/types/agent-info.ts @@ -1,7 +1,7 @@ import { CaseStyle } from "kryo/case-style"; +import { AnyType } from "kryo/types/any"; import { ArrayType } from "kryo/types/array"; -import { DocumentType } from "kryo/types/document"; -import { JsonType } from "kryo/types/json"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { Ucs2StringType } from "kryo/types/ucs2-string"; /** @@ -31,12 +31,11 @@ export interface AgentInfo { type: string; } -export const $AgentInfo: DocumentType = new DocumentType({ +export const $AgentInfo: DocumentIoType = new DocumentType({ properties: { - capabilities: {type: new ArrayType({itemType: new JsonType(), maxLength: Infinity}), optional: true}, - trusted: {type: new JsonType()}, + capabilities: {type: new ArrayType({itemType: new AnyType(), maxLength: Infinity}), optional: true}, + trusted: {type: new AnyType()}, type: {type: new Ucs2StringType({maxLength: Infinity})}, }, - rename: CaseStyle.SnakeCase, - ignoreExtraKeys: true, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/types/agent.ts b/src/lib/types/agent.ts index 5b15ca5..572365c 100644 --- a/src/lib/types/agent.ts +++ b/src/lib/types/agent.ts @@ -1,7 +1,8 @@ +import { $Any } from "kryo/builtins/any"; import { CaseStyle } from "kryo/case-style"; +import { AnyType } from "kryo/types/any"; import { ArrayType } from "kryo/types/array"; -import { DocumentType } from "kryo/types/document"; -import { JsonType } from "kryo/types/json"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { Ucs2StringType } from "kryo/types/ucs2-string"; import { $AgentInfo, AgentInfo } from "./agent-info"; @@ -49,14 +50,13 @@ export interface Agent { stageInfo?: any; } -export const $Agent: DocumentType = new DocumentType({ +export const $Agent: DocumentIoType = new DocumentType({ properties: { - capabilities: {type: new ArrayType({itemType: new JsonType(), maxLength: Infinity}), optional: true}, + capabilities: {type: new ArrayType({itemType: $Any, maxLength: Infinity}), optional: true}, type: {type: new Ucs2StringType({maxLength: Infinity})}, trust: {type: new Ucs2StringType({maxLength: Infinity})}, info: {type: $AgentInfo}, - stageInfo: {type: new JsonType(), optional: true}, + stageInfo: {type: new AnyType(), optional: true}, }, - rename: CaseStyle.SnakeCase, - ignoreExtraKeys: true, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/types/api-profile.ts b/src/lib/types/api-profile.ts index f08bffa..1cc9a8c 100644 --- a/src/lib/types/api-profile.ts +++ b/src/lib/types/api-profile.ts @@ -1,13 +1,13 @@ -import { VersionedType } from "kryo/types"; +import { IoType } from "kryo/types"; +import { AnyType } from "kryo/types/any"; import { ArrayType } from "kryo/types/array"; -import { DocumentType } from "kryo/types/document"; -import { JsonType } from "kryo/types/json"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { NullType } from "kryo/types/null"; import { TryUnionType } from "kryo/types/try-union"; import { Ucs2StringType } from "kryo/types/ucs2-string"; import { $Url, Url } from "./url"; -function nullable(type: VersionedType): VersionedType { +function nullable(type: IoType): IoType { return new TryUnionType({ variants: [ new NullType(), @@ -95,27 +95,26 @@ export interface ApiProfile { username: string; } -export const $ApiProfile: DocumentType = new DocumentType({ +export const $ApiProfile: DocumentIoType = new DocumentType({ properties: { firstname: {type: new Ucs2StringType({maxLength: Infinity})}, lastname: {type: nullable(new Ucs2StringType({maxLength: Infinity}))}, - birthday: {type: new JsonType()}, - gender: {type: new JsonType()}, - language: {type: new JsonType()}, - country: {type: new JsonType()}, - province: {type: new JsonType()}, - city: {type: new JsonType()}, - homepage: {type: new JsonType()}, - about: {type: new JsonType()}, + birthday: {type: new AnyType()}, + gender: {type: new AnyType()}, + language: {type: new AnyType()}, + country: {type: new AnyType()}, + province: {type: new AnyType()}, + city: {type: new AnyType()}, + homepage: {type: new AnyType()}, + about: {type: new AnyType()}, emails: {type: new ArrayType({itemType: new Ucs2StringType({maxLength: Infinity}), maxLength: Infinity})}, - jobtitle: {type: new JsonType()}, - phoneMobile: {type: new JsonType()}, - phoneHome: {type: new JsonType()}, - phoneOffice: {type: new JsonType()}, - mood: {type: new JsonType()}, - richMood: {type: new JsonType()}, + jobtitle: {type: new AnyType()}, + phoneMobile: {type: new AnyType()}, + phoneHome: {type: new AnyType()}, + phoneOffice: {type: new AnyType()}, + mood: {type: new AnyType()}, + richMood: {type: new AnyType()}, avatarUrl: {type: nullable($Url)}, username: {type: new Ucs2StringType({maxLength: Infinity})}, }, - ignoreExtraKeys: true, }); diff --git a/src/lib/types/contact-group.ts b/src/lib/types/contact-group.ts index b422967..ce5e557 100644 --- a/src/lib/types/contact-group.ts +++ b/src/lib/types/contact-group.ts @@ -1,6 +1,6 @@ +import { $Boolean } from "kryo/builtins/boolean"; import { CaseStyle } from "kryo/case-style"; -import { BooleanType } from "kryo/types/boolean"; -import { DocumentType } from "kryo/types/document"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { Ucs2StringType } from "kryo/types/ucs2-string"; /** @@ -19,12 +19,11 @@ export interface ContactGroup { isFavorite?: boolean; } -export const $ContactGroup: DocumentType = new DocumentType({ +export const $ContactGroup: DocumentIoType = new DocumentType({ properties: { id: {type: new Ucs2StringType({maxLength: Infinity})}, name: {type: new Ucs2StringType({maxLength: Infinity})}, - isFavorite: {type: new BooleanType(), optional: true}, + isFavorite: {type: $Boolean, optional: true}, }, - rename: CaseStyle.SnakeCase, - ignoreExtraKeys: true, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/types/contact-profile.ts b/src/lib/types/contact-profile.ts index 867f8fb..9f1fa04 100644 --- a/src/lib/types/contact-profile.ts +++ b/src/lib/types/contact-profile.ts @@ -1,6 +1,6 @@ import { CaseStyle } from "kryo/case-style"; import { ArrayType } from "kryo/types/array"; -import { DocumentType } from "kryo/types/document"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { Ucs2StringType } from "kryo/types/ucs2-string"; import { $IsoDate, IsoDate } from "./iso-date"; import { $Location, Location } from "./location"; @@ -49,7 +49,7 @@ export interface ContactProfile { language?: string; } -export const $ContactProfile: DocumentType = new DocumentType({ +export const $ContactProfile: DocumentIoType = new DocumentType({ properties: { avatarUrl: {type: $Url, optional: true}, birthday: {type: $IsoDate, optional: true}, @@ -62,6 +62,5 @@ export const $ContactProfile: DocumentType = new DocumentType = new DocumentType({ +export const $Contact: DocumentIoType = new DocumentType(() => ({ properties: { personId: {type: $MriKey}, mri: {type: $MriKey}, @@ -61,14 +61,13 @@ export const $Contact: DocumentType = new DocumentType({ phones: {type: new ArrayType({itemType: $Phone, maxLength: Infinity}), optional: true}, profile: {type: $ContactProfile}, agent: {type: $Agent, optional: true}, - authorized: {type: new BooleanType()}, + authorized: {type: $Boolean}, authCertificate: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, - blocked: {type: new BooleanType()}, - creationTime: {type: new DateType()}, + blocked: {type: $Boolean}, + creationTime: {type: $Date}, relationshipHistory: {type: $RelationshipHistory, optional: true}, - suggested: {type: new BooleanType(), optional: true}, - phoneHashes: {type: new ArrayType({itemType: new JsonType(), maxLength: Infinity}), optional: true}, + suggested: {type: $Boolean, optional: true}, + phoneHashes: {type: new ArrayType({itemType: new AnyType(), maxLength: Infinity}), optional: true}, }, - rename: CaseStyle.SnakeCase, - ignoreExtraKeys: true, -}); + changeCase: CaseStyle.SnakeCase, +})); diff --git a/src/lib/types/conversation-properties.ts b/src/lib/types/conversation-properties.ts new file mode 100644 index 0000000..3ba691c --- /dev/null +++ b/src/lib/types/conversation-properties.ts @@ -0,0 +1,66 @@ +import { $Date } from "kryo/builtins/date"; +import { DocumentType } from "kryo/types/document"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $ConversationStatus, ConversationStatus } from "./conversation-status"; +import { $StringyBoolean, StringyBoolean } from "./stringy-boolean"; + +export interface ConversationProperties { + /** + * Example: + * - `"true"` + */ + favorite: StringyBoolean; + + conversationstatus: ConversationStatus; + + /** + * Example: + * - `"1502807294837"` + */ + created?: string; + + /** + * Example: + * - `"False"` + * + * I never saw `"True"` but guess that it is the only other possible value. + */ + isemptyconversation?: StringyBoolean; + + /** + * Semicolon-separated list of 3 timestamps. + * + * Example: + * - `"1502807295234;1502807296000;1502807295234"` + */ + consumptionhorizon: string; + + /** + * Example: + * - `"9df263f8f3eccfae79e1bf712dcae211913d4d83126b36f3803ab44846578fe0@oneToOne.skype"` + */ + onetoonethreadid?: string; + + /** + * Example: + * - `"False"` + * + * I never saw `"True"` but guess that it is the only other possible value. + */ + conversationblocked?: StringyBoolean; + + lastimreceivedtime: Date; +} + +export const $ConversationProperties: DocumentType = new DocumentType({ + properties: { + favorite: {type: $StringyBoolean, optional: true}, + conversationstatus: {type: $ConversationStatus, optional: true}, + created: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/}), optional: true}, + isemptyconversation: {type: $StringyBoolean, optional: true}, + consumptionhorizon: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+(?:;\d+){2}$/})}, + onetoonethreadid: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, + conversationblocked: {type: $StringyBoolean, optional: true}, + lastimreceivedtime: {type: $Date}, + }, +}); diff --git a/src/lib/types/conversation-status.ts b/src/lib/types/conversation-status.ts new file mode 100644 index 0000000..4d35cb0 --- /dev/null +++ b/src/lib/types/conversation-status.ts @@ -0,0 +1,11 @@ +import { CaseStyle } from "kryo/case-style"; +import { TsEnumType } from "kryo/types/ts-enum"; + +export enum ConversationStatus { + Accepted, +} + +export const $ConversationStatus: TsEnumType = new TsEnumType({ + enum: ConversationStatus, + changeCase: CaseStyle.PascalCase, +}); diff --git a/src/lib/types/conversation-type.ts b/src/lib/types/conversation-type.ts new file mode 100644 index 0000000..2b9ead8 --- /dev/null +++ b/src/lib/types/conversation-type.ts @@ -0,0 +1,12 @@ +import { CaseStyle } from "kryo/case-style"; +import { TsEnumType } from "kryo/types/ts-enum"; + +export enum ConversationType { + Conversation, + Thread, +} + +export const $ConversationType: TsEnumType = new TsEnumType({ + enum: ConversationType, + changeCase: CaseStyle.PascalCase, +}); diff --git a/src/lib/types/conversation.ts b/src/lib/types/conversation.ts new file mode 100644 index 0000000..77cc52d --- /dev/null +++ b/src/lib/types/conversation.ts @@ -0,0 +1,78 @@ +import { $Uint53 } from "kryo/builtins/uint53"; +import { CaseStyle } from "kryo/case-style"; +import { ArrayType } from "kryo/types/array"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { TryUnionType } from "kryo/types/try-union"; +import { $ConversationType, ConversationType } from "./conversation-type"; +import { $EmptyObject, EmptyObject } from "./empty-object"; +import { $Message, Message } from "./message"; +import { $MriKey, MriKey } from "./mri-key"; +import { $ThreadProperties, ThreadProperties } from "./thread-properties"; +import { $Url, Url } from "./url"; + +// tslint:disable:max-line-length +export interface Conversation { + /** + * Examples: + * - `"https://db4-client-s.gateway.messenger.live.com/v1/threads/19:5300390a2a304131b92558e6a818a222@thread.skype"` + * - `"https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob"` + */ + targetLink: Url; + + /** + * Seems to only be present for group conversations. + */ + threadProperties?: ThreadProperties; + + /** + * Examples: + * - `19:5300390a2a304131b92558e6a818a222@thread.skype` + * - `8:bob` + */ + id: MriKey; + + type: ConversationType; + + /** + * Seems to be a timestamp. + * + * Examples: + * - `1464030261015` + * - `1502807296509` + */ + version: number; // a timestamp ? example: + + lastMessage: EmptyObject | Message; + + /** + * Examples: + * - `"https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/19:5300390a2a304131b92558e6a818a222@thread.skype/messages"` + * - `"https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob/messages"` + */ + messages: Url; + + /** + * Array of MRI keys of the participants of this conversation. + * + * Examples: + * - `["8:alice"]` + * - `["8:bob"]` + */ + members?: MriKey[]; +} + +// tslint:enable + +export const $Conversation: DocumentIoType = new DocumentType({ + properties: { + targetLink: {type: $Url}, + threadProperties: {type: $ThreadProperties, optional: true}, + id: {type: $MriKey}, + type: {type: $ConversationType}, + version: {type: $Uint53}, + lastMessage: {type: new TryUnionType({variants: [$EmptyObject, $Message]})}, + messages: {type: $Url}, + members: {type: new ArrayType({itemType: $MriKey, maxLength: Infinity}), optional: true}, + }, + changeCase: CaseStyle.CamelCase, +}); diff --git a/src/lib/types/display-name-source.ts b/src/lib/types/display-name-source.ts index 045244d..fc6166e 100644 --- a/src/lib/types/display-name-source.ts +++ b/src/lib/types/display-name-source.ts @@ -1,5 +1,5 @@ import { CaseStyle } from "kryo/case-style"; -import { SimpleEnumType } from "kryo/types/simple-enum"; +import { TsEnumType } from "kryo/types/ts-enum"; export enum DisplayNameSource { Identifier, @@ -10,7 +10,7 @@ export enum DisplayNameSource { UserEdits, } -export const $DisplayNameSource: SimpleEnumType = new SimpleEnumType({ +export const $DisplayNameSource: TsEnumType = new TsEnumType({ enum: DisplayNameSource, - rename: CaseStyle.SnakeCase, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/types/empty-object.ts b/src/lib/types/empty-object.ts new file mode 100644 index 0000000..cf5baa2 --- /dev/null +++ b/src/lib/types/empty-object.ts @@ -0,0 +1,8 @@ +import { DocumentType } from "kryo/types/document"; + +export interface EmptyObject { +} + +export const $EmptyObject: DocumentType = new DocumentType({ + properties: {}, +}); diff --git a/src/lib/types/invite-message.ts b/src/lib/types/invite-message.ts index 2fbfd81..0e6f3c6 100644 --- a/src/lib/types/invite-message.ts +++ b/src/lib/types/invite-message.ts @@ -1,5 +1,5 @@ -import { DateType } from "kryo/types/date"; -import { DocumentType } from "kryo/types/document"; +import { $Date } from "kryo/builtins/date"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { Ucs2StringType } from "kryo/types/ucs2-string"; /** @@ -17,10 +17,9 @@ export interface InviteMessage { time: Date; } -export const $InviteMessage: DocumentType = new DocumentType({ +export const $InviteMessage: DocumentIoType = new DocumentType({ properties: { message: {type: new Ucs2StringType({maxLength: Infinity})}, - time: {type: new DateType()}, + time: {type: $Date}, }, - ignoreExtraKeys: true, }); diff --git a/src/lib/types/invite.ts b/src/lib/types/invite.ts index 786baa2..fe0c205 100644 --- a/src/lib/types/invite.ts +++ b/src/lib/types/invite.ts @@ -1,6 +1,6 @@ import { CaseStyle } from "kryo/case-style"; import { ArrayType } from "kryo/types/array"; -import { DocumentType } from "kryo/types/document"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { $DisplayName, DisplayName } from "./display-name"; import { $InviteMessage, InviteMessage } from "./invite-message"; import { $MriKey, MriKey } from "./mri-key"; @@ -33,13 +33,12 @@ export interface Invite { /** * Runtime representation of the [[Invite]] type. */ -export const $Invite: DocumentType = new DocumentType({ +export const $Invite: DocumentIoType = new DocumentType({ properties: { mri: {type: $MriKey}, displayname: {type: $DisplayName}, avatarUrl: {type: $Url}, invites: {type: new ArrayType({itemType: $InviteMessage, maxLength: Infinity})}, }, - rename: CaseStyle.SnakeCase, - ignoreExtraKeys: true, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/types/location.ts b/src/lib/types/location.ts index 045d83c..41fbad6 100644 --- a/src/lib/types/location.ts +++ b/src/lib/types/location.ts @@ -1,5 +1,5 @@ import { CaseStyle } from "kryo/case-style"; -import { DocumentType } from "kryo/types/document"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { Ucs2StringType } from "kryo/types/ucs2-string"; export interface Location { @@ -15,13 +15,12 @@ export interface Location { state?: string; } -export const $Location: DocumentType = new DocumentType({ +export const $Location: DocumentIoType = new DocumentType({ properties: { type: {type: new Ucs2StringType({maxLength: Infinity})}, country: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, city: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, state: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, }, - rename: CaseStyle.SnakeCase, - ignoreExtraKeys: true, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/types/message-type.ts b/src/lib/types/message-type.ts new file mode 100644 index 0000000..a3c7813 --- /dev/null +++ b/src/lib/types/message-type.ts @@ -0,0 +1,11 @@ +import { CaseStyle } from "kryo/case-style"; +import { TsEnumType } from "kryo/types/ts-enum"; + +export enum MessageType { + RichText, +} + +export const $MessageType: TsEnumType = new TsEnumType({ + enum: MessageType, + changeCase: CaseStyle.PascalCase, +}); diff --git a/src/lib/types/message.ts b/src/lib/types/message.ts new file mode 100644 index 0000000..c05d6ab --- /dev/null +++ b/src/lib/types/message.ts @@ -0,0 +1,86 @@ +import { $Date } from "kryo/builtins/date"; +import { DocumentType } from "kryo/types/document"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageType, MessageType } from "./message-type"; +import { $MriKey, MriKey } from "./mri-key"; +import { $ResourceType, ResourceType } from "./resource-type"; +import { $Url, Url } from "./url"; + +// tslint:disable:max-line-length +export interface Message { + /** + * Looks like a timestamp. + * + * Example: + * - `"1517157679445"` + */ + id: string; + + originalarrivaltime: Date; + + messagetype: MessageType; + + /** + * Looks like a timestamp. + * + * Example: + * - `"1517157679445"` + * + * Note: Can be equal to `id`. + */ + version: string; + + composetime: Date; + + /** + * Looks like a timestamp but in ms (as opposed to id/version in s) + * + * Example: + * - `"15834758132099454160"` + */ + clientmessageid: string; + + /** + * Example: + * - `"https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/19:4dd7eb8db8714b2c84a6667aae45effa@thread.skype"` + */ + conversationLink: Url; + + /** + * Example: + * - `"https://www.youtube.com/watch?v=dQw4w9WgXcQ"` + */ + content: string; + + type: ResourceType; + + /** + * Example: + * - `"19:4dd7eb8db8714b2c84a6667aae45effa@thread.skype"` + */ + conversationid: MriKey; + + /** + * Example: + * - `"https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob"` + */ + from: Url; +} + +// tslint:enable + +export const $Message: DocumentType = new DocumentType({ + properties: { + id: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/})}, + originalarrivaltime: {type: $Date}, + messagetype: {type: $MessageType}, + version: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/})}, + composetime: {type: $Date}, + clientmessageid: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/})}, + conversationLink: {type: $Url}, + content: {type: new Ucs2StringType({maxLength: Infinity})}, + type: {type: $ResourceType}, + conversationid: {type: $MriKey}, + from: {type: $Url}, + }, +}); diff --git a/src/lib/types/name.ts b/src/lib/types/name.ts index f9707a5..bc268cf 100644 --- a/src/lib/types/name.ts +++ b/src/lib/types/name.ts @@ -1,5 +1,5 @@ import { CaseStyle } from "kryo/case-style"; -import { DocumentType } from "kryo/types/document"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { Ucs2StringType } from "kryo/types/ucs2-string"; /** @@ -19,13 +19,12 @@ export interface Name { company?: string; } -export const $Name: DocumentType = new DocumentType({ +export const $Name: DocumentIoType = new DocumentType({ properties: { first: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, surname: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, nickname: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, company: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, }, - rename: CaseStyle.SnakeCase, - ignoreExtraKeys: true, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/types/phone.ts b/src/lib/types/phone.ts index 4ea997a..e7b71d9 100644 --- a/src/lib/types/phone.ts +++ b/src/lib/types/phone.ts @@ -1,5 +1,5 @@ import { CaseStyle } from "kryo/case-style"; -import { DocumentType } from "kryo/types/document"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; import { Ucs2StringType } from "kryo/types/ucs2-string"; export interface Phone { @@ -13,11 +13,10 @@ export interface Phone { number: string; } -export const $Phone: DocumentType = new DocumentType({ +export const $Phone: DocumentIoType = new DocumentType({ properties: { type: {type: new Ucs2StringType({maxLength: Infinity})}, number: {type: new Ucs2StringType({maxLength: Infinity})}, }, - rename: CaseStyle.SnakeCase, - ignoreExtraKeys: true, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/types/relationship-history.ts b/src/lib/types/relationship-history.ts index 517740d..8c61ab8 100644 --- a/src/lib/types/relationship-history.ts +++ b/src/lib/types/relationship-history.ts @@ -1,7 +1,7 @@ +import { $Any } from "kryo/builtins/any"; import { CaseStyle } from "kryo/case-style"; import { ArrayType } from "kryo/types/array"; -import { DocumentType } from "kryo/types/document"; -import { JsonType } from "kryo/types/json"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; export interface RelationshipHistory { /** @@ -35,10 +35,9 @@ export interface RelationshipHistory { sources: any[]; } -export const $RelationshipHistory: DocumentType = new DocumentType({ +export const $RelationshipHistory: DocumentIoType = new DocumentType({ properties: { - sources: {type: new ArrayType({itemType: new JsonType(), maxLength: Infinity})}, + sources: {type: new ArrayType({itemType: $Any, maxLength: Infinity})}, }, - rename: CaseStyle.SnakeCase, - ignoreExtraKeys: true, + changeCase: CaseStyle.SnakeCase, }); diff --git a/src/lib/types/resource-type.ts b/src/lib/types/resource-type.ts new file mode 100644 index 0000000..e3a0014 --- /dev/null +++ b/src/lib/types/resource-type.ts @@ -0,0 +1,11 @@ +import { CaseStyle } from "kryo/case-style"; +import { TsEnumType } from "kryo/types/ts-enum"; + +export enum ResourceType { + Message, +} + +export const $ResourceType: TsEnumType = new TsEnumType({ + enum: ResourceType, + changeCase: CaseStyle.PascalCase, +}); diff --git a/src/lib/types/stringy-boolean.ts b/src/lib/types/stringy-boolean.ts new file mode 100644 index 0000000..45fe28f --- /dev/null +++ b/src/lib/types/stringy-boolean.ts @@ -0,0 +1,10 @@ +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { WhiteListType } from "kryo/types/white-list"; + +export type StringyBoolean = "False" | "True" | "false" | "true"; + +// TODO: Read it to `boolean` instead of this union. +export const $StringyBoolean: WhiteListType = new WhiteListType({ + itemType: new Ucs2StringType({maxLength: 5}), + values: ["False", "True", "false", "true"], +}); diff --git a/src/lib/types/thread-properties.ts b/src/lib/types/thread-properties.ts new file mode 100644 index 0000000..816a501 --- /dev/null +++ b/src/lib/types/thread-properties.ts @@ -0,0 +1,65 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; + +// tslint:disable:max-line-length +export interface ThreadProperties { + /** + * Example: + * - `"URL@https://api.asm.skype.com/v1/objects/0-neu-d1-52fafc9b91b08ab3f94cc750c1666a55/views/avatar_fullsize"` + * + * Note: The hex part in the middle of the URL is _not_ related to the id of the group conversation. + */ + picture?: string; + + /** + * Seems to be a JSON-serialized array. + * + * Example: + * ``` + * "[{\"MemberRole\":1,\"SkipAclCheck\":false,\"IsFollowing\":false,\"IsReader\":false,\"LinkedId\":\"\",\"Cid\":0,\"FriendlyName\":\"\",\"UserTile\":\"\",\"Expiration\":\"9999-12-31T23:59:59\",\"MemberMri\":\"8:bob\"}]" + * ``` + */ + members?: string; + + /** + * Seems to be a string timestamp. + * + * Examples: + * - `"1421342788493"` + * - `"1502807294562"` + */ + lastjoinat?: string; + + topic?: string; + + /** + * Seems to be a stringified number corresponding to the number of items in the `members` array + * once deserialized. + * It is possible that this field is present but not `members`. + * + * Example: + * - `"1"` + */ + membercount?: string; + + /** + * Seems to be a string timestamp. + * + * Examples: + * - `"1464029299838"` + * - `"1502807295343"` + */ + version?: string; +} +// tslint:enable + +export const $ThreadProperties: DocumentIoType = new DocumentType({ + properties: { + picture: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, + members: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, + lastjoinat: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/}), optional: true}, + topic: {type: new Ucs2StringType({maxLength: Infinity}), optional: true}, + membercount: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/}), optional: true}, + version: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/}), optional: true}, + }, +}); diff --git a/yarn.lock b/yarn.lock index 38fc218..71730ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,9 +19,9 @@ normalize-path "^2.0.1" through2 "^2.0.3" -"@std/esm@^0.19.7": - version "0.19.7" - resolved "https://registry.yarnpkg.com/@std/esm/-/esm-0.19.7.tgz#8eeab6c54596a92e3557992621c91e8166a19852" +"@std/esm@^0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@std/esm/-/esm-0.20.0.tgz#658cb32e3b163f20b7d9f29a78987041068b1182" "@types/bson@^1.0.6": version "1.0.6" @@ -29,6 +29,10 @@ dependencies: "@types/node" "*" +"@types/caseless@*": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.1.tgz#9794c69c8385d0192acc471a540d1f8e0d16218a" + "@types/chai@^4.1.0": version "4.1.2" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.2.tgz#f1af664769cfb50af805431c407425ed619daa21" @@ -64,13 +68,7 @@ dependencies: "@types/node" "*" -"@types/fs-extra@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.0.tgz#1dd742ad5c9bce308f7a52d02ebc01421bc9102f" - dependencies: - "@types/node" "*" - -"@types/fs-extra@^5.0.0": +"@types/fs-extra@5.0.0", "@types/fs-extra@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.0.tgz#d3e225b35eb5c6d3a5a782c28219df365c781413" dependencies: @@ -118,21 +116,21 @@ "@types/undertaker" "*" "@types/vinyl-fs" "*" -"@types/handlebars@4.0.31": - version "4.0.31" - resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.31.tgz#a7fba66fafe42713aee88eeca8db91192efe6e72" +"@types/handlebars@4.0.36": + version "4.0.36" + resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.36.tgz#ff57c77fa1ab6713bb446534ddc4d979707a3a79" -"@types/highlight.js@9.1.8": - version "9.1.8" - resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.1.8.tgz#d227f18bcb8f3f187e16965f2444859a04689758" +"@types/highlight.js@9.12.2": + version "9.12.2" + resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.2.tgz#6ee7cd395effe5ec80b515d3ff1699068cd0cd1d" -"@types/lodash@4.14.74": - version "4.14.74" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.74.tgz#ac3bd8db988e7f7038e5d22bd76a7ba13f876168" +"@types/lodash@4.14.99": + version "4.14.99" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.99.tgz#e6e10c0a4cc16c7409b3181f1e66880d2fb7d4dc" "@types/lodash@^4.14.92": - version "4.14.97" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.97.tgz#7262d6d5fc5e87cdb3f68eb33accd4024f2b211e" + version "4.14.101" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.101.tgz#512f6c9e1749890f4d024e98cb995a63f562d458" "@types/marked@0.3.0": version "0.3.0" @@ -144,25 +142,21 @@ dependencies: "@types/node" "*" -"@types/minimatch@*", "@types/minimatch@^3.0.1": +"@types/minimatch@*", "@types/minimatch@3.0.3", "@types/minimatch@^3.0.1": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" -"@types/minimatch@2.0.29": - version "2.0.29" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" - "@types/minimist@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" "@types/mocha@^2.2.46": - version "2.2.47" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.47.tgz#30bbd880834d4af0f609025f282a69b8d4458f06" + version "2.2.48" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab" "@types/node@*", "@types/node@^9.3.0": - version "9.4.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.0.tgz#b85a0bcf1e1cc84eb4901b7e96966aedc6f078d1" + version "9.4.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.1.tgz#0f636f7837e15d2d73a7f6f3ea0e322eb2a5ab65" "@types/object-inspect@^1.4.0": version "1.4.0" @@ -184,21 +178,23 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.0.7.tgz#afd4c610f16f6386d320e0738ec38ba7d3431917" "@types/request@^2.0.9": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.0.13.tgz#f97cbb212bfe1b7b9b7db97b3a8b3a0a2d2ad425" + version "2.47.0" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.47.0.tgz#76a666cee4cb85dcffea6cd4645227926d9e114e" dependencies: + "@types/caseless" "*" "@types/form-data" "*" "@types/node" "*" "@types/tough-cookie" "*" "@types/semver@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.4.0.tgz#f3658535af7f1f502acd6da7daf405ffeb1f7ee4" + version "5.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" -"@types/shelljs@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.0.tgz#229c157c6bc1e67d6b990e6c5e18dbd2ff58cff0" +"@types/shelljs@0.7.7": + version "0.7.7" + resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.7.tgz#1f7bfa28947661afea06365db9b1135bbc903ec4" dependencies: + "@types/glob" "*" "@types/node" "*" "@types/strip-bom@^3.0.0": @@ -263,8 +259,8 @@ acorn-globals@^3.0.0: acorn "^4.0.4" acorn@5.X, acorn@^5.0.0, acorn@^5.0.3: - version "5.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822" + version "5.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102" acorn@^3.1.0, acorn@~3.3.0: version "3.3.0" @@ -613,8 +609,8 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: js-tokens "^3.0.2" babel-generator@^6.18.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" dependencies: babel-messages "^6.23.0" babel-runtime "^6.26.0" @@ -622,7 +618,7 @@ babel-generator@^6.18.0: detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.17.4" - source-map "^0.5.6" + source-map "^0.5.7" trim-right "^1.0.1" babel-messages@^6.23.0: @@ -1163,8 +1159,8 @@ commander@2.8.x: graceful-readlink ">= 1.0.0" commander@^2.12.1, commander@^2.9.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" + version "2.14.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" commondir@^1.0.1: version "1.0.1" @@ -1993,14 +1989,6 @@ from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" -fs-extra@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" @@ -2614,11 +2602,11 @@ in-publish@^2.0.0: resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" incident@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/incident/-/incident-3.1.0.tgz#4c19fcb436d7e0aff381870b1f1d8258c123c5f9" + version "3.1.1" + resolved "https://registry.yarnpkg.com/incident/-/incident-3.1.1.tgz#b284e92ebad0499e2a1f154b9692ce963d89aaa3" dependencies: "@types/object-inspect" "^1.4.0" - object-inspect "^1.4.0" + object-inspect "^1.5.0" indent-string@^2.1.0: version "2.1.0" @@ -3109,9 +3097,9 @@ kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" -kryo@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/kryo/-/kryo-0.6.0.tgz#e0ca4f357e77dddd02e103d028a25a225582cd3c" +kryo@^0.7.0-beta.2: + version "0.7.0-beta.1-build.193" + resolved "https://registry.yarnpkg.com/kryo/-/kryo-0.7.0-beta.1-build.193.tgz#b06be9de1a257a2db574c761fc14dce58813e80b" dependencies: "@types/bson" "^1.0.6" "@types/object-inspect" "^1.4.0" @@ -3283,8 +3271,8 @@ lodash.keys@^3.0.0: lodash.isarray "^3.0.0" lodash.mergewith@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" + version "4.6.1" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" lodash.restparam@^3.0.0: version "3.6.1" @@ -3316,8 +3304,8 @@ lodash.templatesettings@^3.0.0: lodash.escape "^3.0.0" lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@~4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" longest@^1.0.1: version "1.0.1" @@ -3350,8 +3338,8 @@ lru-queue@0.1: es5-ext "~0.10.2" make-error@^1.1.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.2.tgz#8762ffad2444dd8ff1f7c819629fa28e24fea1c4" + version "1.3.3" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.3.tgz#a97ae14ffd98b05f543e83ddc395e1b2b6e4cc6a" make-iterator@^1.0.0: version "1.0.0" @@ -3381,7 +3369,7 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^0.3.5: +marked@^0.3.12: version "0.3.12" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.12.tgz#7cf25ff2252632f3fe2406bde258e94eee927519" @@ -3522,8 +3510,8 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: mime-db "~1.30.0" mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" minimalistic-assert@^1.0.0: version "1.0.0" @@ -3552,8 +3540,8 @@ minimist@~0.0.1: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" mixin-deep@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.0.tgz#47a8732ba97799457c8c1eca28f95132d7e8150a" + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" dependencies: for-in "^1.0.2" is-extendable "^1.0.1" @@ -3829,7 +3817,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.4.0, object-inspect@^1.5.0: +object-inspect@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.5.0.tgz#9d876c11e40f485c79215670281b767488f9bfe3" @@ -4386,8 +4374,8 @@ raw-loader@^0.5.1: resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" rc@^1.1.7: - version "1.2.4" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.4.tgz#a0f606caae2a3b862bbd0ef85482c0125b315fa3" + version "1.2.5" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd" dependencies: deep-extend "~0.4.0" ini "~1.3.0" @@ -4765,9 +4753,9 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" -shelljs@^0.7.0: - version "0.7.8" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" +shelljs@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.1.tgz#729e038c413a2254c4078b95ed46e0397154a9f1" dependencies: glob "^7.0.0" interpret "^1.0.0" @@ -4869,7 +4857,7 @@ source-map@^0.1.38: dependencies: amdefine ">=0.0.4" -source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -5301,8 +5289,8 @@ tslint@^5.8.0: tsutils "^2.12.1" tsutils@^2.12.1: - version "2.19.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.19.1.tgz#76d7ebdea9d7a7bf4a05f50ead3701b0168708d7" + version "2.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.21.0.tgz#43466a2283a0abce64e2209bc732ad72f8a04fab" dependencies: tslib "^1.8.1" @@ -5320,11 +5308,11 @@ tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" -turbo-gulp@^0.16.0: - version "0.16.1" - resolved "https://registry.yarnpkg.com/turbo-gulp/-/turbo-gulp-0.16.1.tgz#a6f959436c4daf36df01d5faed4775ab8c7fdcd3" +turbo-gulp@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/turbo-gulp/-/turbo-gulp-0.16.2.tgz#5be47d5ee36d1a8b9e4ca20edbce2b8f7084e507" dependencies: - "@std/esm" "^0.19.7" + "@std/esm" "^0.20.0" "@types/del" "^3.0.0" "@types/fancy-log" "^1.3.0" "@types/fs-extra" "^5.0.0" @@ -5362,9 +5350,9 @@ turbo-gulp@^0.16.0: semver "^5.4.1" tmp "0.0.33" tslint "^5.8.0" - typedoc "^0.9.0" + typedoc "^0.10.0" typedoc-plugin-external-module-name "^1.0.10" - typescript "2.7.0-dev.20180109" + typescript "^2.7.1" vinyl "^2.1.0" vinyl-buffer "^1.0.0" vinyl-source-stream "^2.0.0" @@ -5377,8 +5365,8 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" type-detect@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.7.tgz#862bd2cf6058ad92799ff5a5b8cf7b6cec726198" + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" typedarray@^0.0.6: version "0.0.6" @@ -5389,42 +5377,34 @@ typedoc-default-themes@^0.5.0: resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz#6dc2433e78ed8bea8e887a3acde2f31785bd6227" typedoc-plugin-external-module-name@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/typedoc-plugin-external-module-name/-/typedoc-plugin-external-module-name-1.0.10.tgz#f97c1df450d9ec75bf06b72bf3f9a12382184135" + version "1.1.1" + resolved "https://registry.yarnpkg.com/typedoc-plugin-external-module-name/-/typedoc-plugin-external-module-name-1.1.1.tgz#0ef2d6a760b42c703519c474258b6f062983aa83" -typedoc@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.9.0.tgz#159bff7c7784ce5b91d86f3e4cc8928e62040957" +typedoc@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.10.0.tgz#898b447248dabf68ecbde9d5ccf5141fda8aa166" dependencies: - "@types/fs-extra" "4.0.0" - "@types/handlebars" "4.0.31" - "@types/highlight.js" "9.1.8" - "@types/lodash" "4.14.74" + "@types/fs-extra" "5.0.0" + "@types/handlebars" "4.0.36" + "@types/highlight.js" "9.12.2" + "@types/lodash" "4.14.99" "@types/marked" "0.3.0" - "@types/minimatch" "2.0.29" - "@types/shelljs" "0.7.0" - fs-extra "^4.0.0" + "@types/minimatch" "3.0.3" + "@types/shelljs" "0.7.7" + fs-extra "^5.0.0" handlebars "^4.0.6" highlight.js "^9.0.0" lodash "^4.13.1" - marked "^0.3.5" + marked "^0.3.12" minimatch "^3.0.0" progress "^2.0.0" - shelljs "^0.7.0" + shelljs "^0.8.1" typedoc-default-themes "^0.5.0" - typescript "2.4.1" - -typescript@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" - -typescript@2.7.0-dev.20180109: - version "2.7.0-dev.20180109" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.0-dev.20180109.tgz#8ffb23a38903b7ba7abff12b7d5c10dc2675c987" + typescript "2.7.1" -typescript@^2.7.0-rc: - version "2.7.0-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.0-rc.tgz#bf86a065a3a0be1e072c3fb7d64e8976f33b8365" +typescript@2.7.1, typescript@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.1.tgz#bb3682c2c791ac90e7c6210b26478a8da085c359" uglify-js@^2.6, uglify-js@^2.6.1, uglify-js@^2.8.29: version "2.8.29" From 5fdda05d3c6b88c87c42b357c9778129058ab321 Mon Sep 17 00:00:00 2001 From: Charles Samborski Date: Thu, 8 Feb 2018 22:04:04 +0100 Subject: [PATCH 2/2] Type check events --- CHANGELOG.md | 2 + gulpfile.ts | 2 +- package.json | 3 + src/lib/api.ts | 25 +- src/lib/contacts/api/get-contacts.ts | 2 +- src/lib/contacts/contacts.ts | 2 +- .../api/get-conversation-by-id.ts | 2 +- src/lib/conversation/api/get-conversations.ts | 2 +- src/lib/polling/messages-poller.ts | 297 ++--------------- src/lib/types/conversation.ts | 6 +- .../types/events/endpoint-presence-event.ts | 38 +++ src/lib/types/events/event-resource-type.ts | 11 + src/lib/types/events/event-type.ts | 9 + src/lib/types/events/new-message-event.ts | 41 +++ src/lib/types/events/skype-event.ts | 15 + src/lib/types/events/user-presence-event.ts | 38 +++ src/lib/types/message-type.ts | 11 - src/lib/types/message.ts | 86 ----- .../types/resources/_message-resource-base.ts | 110 ++++++ .../resources/control-clear-typing-message.ts | 22 ++ .../resources/control-live-state-message.ts | 22 ++ .../types/resources/control-typing-message.ts | 21 ++ .../resources/conversation-update-resource.ts | 27 ++ .../endpoint-presence-doc-resource.ts | 43 +++ .../types/resources/endpoint-private-info.ts | 17 + .../types/resources/endpoint-public-info.ts | 47 +++ src/lib/types/resources/event-call-message.ts | 32 ++ src/lib/types/resources/message-resource.ts | 45 +++ src/lib/types/resources/message-type.ts | 32 ++ .../types/{ => resources}/resource-type.ts | 5 +- .../resources/rich-text-location-message.ts | 29 ++ .../rich-text-media-generic-file-message.ts | 38 +++ .../rich-text-media-video-message.ts | 36 ++ src/lib/types/resources/rich-text-message.ts | 38 +++ .../resources/rich-text-uri-object-message.ts | 36 ++ .../resources/signal-flamingo-message.ts | 28 ++ src/lib/types/resources/text-message.ts | 38 +++ .../resources/user-presence-availability.ts | 11 + .../resources/user-presence-doc-resource.ts | 60 ++++ .../types/resources/user-presence-status.ts | 12 + src/test/polling/message-poller.spec.ts | 165 --------- src/test/types/events/skype-event.spec.ts | 312 ++++++++++++++++++ .../control-clear-typing-message.spec.ts | 58 ++++ .../resources/control-typing-message.spec.ts | 55 +++ .../resources/event-call-message.spec.ts | 64 ++++ .../user-presence-doc-resource.spec.ts | 107 ++++++ 46 files changed, 1544 insertions(+), 558 deletions(-) create mode 100644 src/lib/types/events/endpoint-presence-event.ts create mode 100644 src/lib/types/events/event-resource-type.ts create mode 100644 src/lib/types/events/event-type.ts create mode 100644 src/lib/types/events/new-message-event.ts create mode 100644 src/lib/types/events/skype-event.ts create mode 100644 src/lib/types/events/user-presence-event.ts delete mode 100644 src/lib/types/message-type.ts delete mode 100644 src/lib/types/message.ts create mode 100644 src/lib/types/resources/_message-resource-base.ts create mode 100644 src/lib/types/resources/control-clear-typing-message.ts create mode 100644 src/lib/types/resources/control-live-state-message.ts create mode 100644 src/lib/types/resources/control-typing-message.ts create mode 100644 src/lib/types/resources/conversation-update-resource.ts create mode 100644 src/lib/types/resources/endpoint-presence-doc-resource.ts create mode 100644 src/lib/types/resources/endpoint-private-info.ts create mode 100644 src/lib/types/resources/endpoint-public-info.ts create mode 100644 src/lib/types/resources/event-call-message.ts create mode 100644 src/lib/types/resources/message-resource.ts create mode 100644 src/lib/types/resources/message-type.ts rename src/lib/types/{ => resources}/resource-type.ts (71%) create mode 100644 src/lib/types/resources/rich-text-location-message.ts create mode 100644 src/lib/types/resources/rich-text-media-generic-file-message.ts create mode 100644 src/lib/types/resources/rich-text-media-video-message.ts create mode 100644 src/lib/types/resources/rich-text-message.ts create mode 100644 src/lib/types/resources/rich-text-uri-object-message.ts create mode 100644 src/lib/types/resources/signal-flamingo-message.ts create mode 100644 src/lib/types/resources/text-message.ts create mode 100644 src/lib/types/resources/user-presence-availability.ts create mode 100644 src/lib/types/resources/user-presence-doc-resource.ts create mode 100644 src/lib/types/resources/user-presence-status.ts delete mode 100644 src/test/polling/message-poller.spec.ts create mode 100644 src/test/types/events/skype-event.spec.ts create mode 100644 src/test/types/resources/control-clear-typing-message.spec.ts create mode 100644 src/test/types/resources/control-typing-message.spec.ts create mode 100644 src/test/types/resources/event-call-message.spec.ts create mode 100644 src/test/types/resources/user-presence-doc-resource.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 91de7e1..1090e50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - **[Fix]** Fix message host resolution (API change). - **[Fix]** Mark `isFavorite` in `ContactGroup` as optional. - **[Fix]** Mark `name` in `ContactProfile` as optional. +- **[Fix]** Throw errors on unexpected HTTP status code +- **[Fix]** Distribute compiled `.js` files and `.ts` sources in distinct directories (#85) - **[Internal]** Run tests and coverage on `.mjs` files. # 0.0.14 (2018-01-12) diff --git a/gulpfile.ts b/gulpfile.ts index a441779..ee4e823 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -84,7 +84,7 @@ const test: buildTools.MochaTarget = { scripts: ["test/**/*.ts", "lib/**/*.ts"], customTypingsDir: "src/custom-typings", tsconfigJson: "src/test/tsconfig.json", - outModules: buildTools.OutModules.Mjs, + outModules: buildTools.OutModules.Both, tscOptions: { skipLibCheck: true, }, diff --git a/package.json b/package.json index 8205b9b..9de7c43 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@types/cheerio": "^0.22.7", "@types/form-data": "^2.2.1", "@types/lodash": "^4.14.92", + "@types/object-inspect": "^1.4.0", "@types/request": "^2.0.9", "@types/tough-cookie": "^2.3.2", "async-file": "^2.0.2", @@ -43,6 +44,7 @@ "js-sha256": "^0.9.0", "kryo": "^0.7.0-beta.2", "lodash": "^4.17.4", + "object-inspect": "^1.5.0", "request": "^2.83.0", "tough-cookie": "^2.3.3" }, @@ -67,6 +69,7 @@ }, "nyc": { "include": [ + "build/test/lib/**/*.js", "build/test/lib/**/*.mjs" ], "reporter": [ diff --git a/src/lib/api.ts b/src/lib/api.ts index 0e51eae..fa4091d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -10,12 +10,12 @@ import { ConversationService, ConversationServiceInterface } from "./conversatio import * as api from "./interfaces/api/api"; import { Contact as _Contact } from "./interfaces/api/contact"; import { Context as ApiContext } from "./interfaces/api/context"; -import * as apiEvents from "./interfaces/api/events"; import { HttpIo } from "./interfaces/http-io"; import { MriKey } from "./mri"; import { MessagesPoller } from "./polling/messages-poller"; import { Contact } from "./types/contact"; import { Conversation } from "./types/conversation"; +import { SkypeEvent } from "./types/events/skype-event"; import { Invite } from "./types/invite"; export interface ApiEvents extends NodeJS.EventEmitter { @@ -36,8 +36,7 @@ export class Api extends events.EventEmitter implements ApiEvents { this.io = io; this.messagesPoller = new MessagesPoller(this.io, this.context); this.messagesPoller.on("error", (err: Error) => this.emit("error", err)); - // tslint:disable-next-line:no-void-expression - this.messagesPoller.on("event-message", (ev: apiEvents.EventMessage) => this.handlePollingEvent(ev)); + this.messagesPoller.on("event", (event: SkypeEvent) => this.emit("event", event)); this.contactService = new ContactService(this.io); this.conversationService = new ConversationService(this.io); } @@ -103,24 +102,4 @@ export class Api extends events.EventEmitter implements ApiEvents { this.messagesPoller.stop(); return Promise.resolve(this); } - - protected handlePollingEvent(ev: apiEvents.EventMessage): void { - this.emit("event", ev); - - if (ev.resource === null) { - return; - } - - // Prevent infinite-loop (echo itself) - // TODO: Remove this - if (ev.resource.from.username === this.context.username) { - return; - } - - if (ev.resource.type === "Text") { - this.emit("Text", ev.resource); - } else if (ev.resource.type === "RichText") { - this.emit("RichText", ev.resource); - } - } } diff --git a/src/lib/contacts/api/get-contacts.ts b/src/lib/contacts/api/get-contacts.ts index 104bf0a..4620847 100644 --- a/src/lib/contacts/api/get-contacts.ts +++ b/src/lib/contacts/api/get-contacts.ts @@ -54,7 +54,7 @@ export async function getContacts(httpIo: io.HttpIo, apiContext: Context): Promi }; const response: io.Response = await httpIo.get(request); if (response.statusCode !== 200) { - UnexpectedHttpStatusError.create(response, new Set([200]), request); + throw UnexpectedHttpStatusError.create(response, new Set([200]), request); } let result: GetUserResult; try { diff --git a/src/lib/contacts/contacts.ts b/src/lib/contacts/contacts.ts index 9d7aea1..2cf70eb 100644 --- a/src/lib/contacts/contacts.ts +++ b/src/lib/contacts/contacts.ts @@ -53,7 +53,7 @@ export class ContactService implements ContactServiceInterface { }; const response: io.Response = await this.httpIo.get(request); if (response.statusCode !== 200) { - UnexpectedHttpStatusError.create(response, new Set([200]), request); + throw UnexpectedHttpStatusError.create(response, new Set([200]), request); } let result: GetInvitesResult; try { diff --git a/src/lib/conversation/api/get-conversation-by-id.ts b/src/lib/conversation/api/get-conversation-by-id.ts index 0ebe83d..8e0bd2e 100644 --- a/src/lib/conversation/api/get-conversation-by-id.ts +++ b/src/lib/conversation/api/get-conversation-by-id.ts @@ -42,7 +42,7 @@ export async function getConversationById( const response: io.Response = await httpIo.get(request); if (response.statusCode !== 200) { - UnexpectedHttpStatusError.create(response, new Set([200]), request); + throw UnexpectedHttpStatusError.create(response, new Set([200]), request); } let result: Conversation; try { diff --git a/src/lib/conversation/api/get-conversations.ts b/src/lib/conversation/api/get-conversations.ts index e8aa8de..3373ec2 100644 --- a/src/lib/conversation/api/get-conversations.ts +++ b/src/lib/conversation/api/get-conversations.ts @@ -62,7 +62,7 @@ export async function getConversations(httpIo: io.HttpIo, apiContext: Context): const response: io.Response = await httpIo.get(request); if (response.statusCode !== 200) { - UnexpectedHttpStatusError.create(response, new Set([200]), request); + throw UnexpectedHttpStatusError.create(response, new Set([200]), request); } let result: GetConversationsResult; try { diff --git a/src/lib/polling/messages-poller.ts b/src/lib/polling/messages-poller.ts index ab5cb5b..6ad4b8a 100644 --- a/src/lib/polling/messages-poller.ts +++ b/src/lib/polling/messages-poller.ts @@ -1,16 +1,19 @@ import cheerio from "cheerio"; import _events from "events"; import { Incident } from "incident"; +import { ArrayType } from "kryo/types/array"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import objectInspect from "object-inspect"; import { UnexpectedHttpStatusError } from "../errors/http"; import { ParsedConversationId } from "../interfaces/api/api"; import { Context as ApiContext } from "../interfaces/api/context"; -import * as events from "../interfaces/api/events"; import * as resources from "../interfaces/api/resources"; import * as httpIo from "../interfaces/http-io"; -import * as nativeEvents from "../interfaces/native-api/events"; import * as nativeMessageResources from "../interfaces/native-api/message-resources"; import * as nativeResources from "../interfaces/native-api/resources"; +import { JSON_READER } from "../json-reader"; import * as messagesUri from "../messages-uri"; +import { $SkypeEvent, SkypeEvent } from "../types/events/skype-event"; // Perform one request every 1000 ms const POLLING_DELAY: number = 1000; @@ -32,53 +35,6 @@ export function parseContactId(contactId: string): ParsedConversationId { }; } -export function formatRichTextResource( - retObj: resources.Resource, - nativeResource: nativeMessageResources.RichText, -): resources.RichTextResource { - const ret: resources.RichTextResource = retObj as resources.RichTextResource; - ret.content = nativeResource.content; - ret.clientId = nativeResource.clientmessageid; - return ret; -} - -export function formatTextResource( - retObj: resources.Resource, - nativeResource: nativeMessageResources.Text, -): resources.TextResource { - const ret: resources.TextResource = retObj as resources.TextResource; - ret.content = nativeResource.content; - ret.clientId = nativeResource.clientmessageid; - return ret; -} - -export function formatControlClearTypingResource( - retObj: resources.Resource, - nativeResource: nativeMessageResources.ControlClearTyping, -): resources.ControlClearTypingResource { - return retObj as resources.ControlClearTypingResource; -} - -// Export for testing -export function formatGenericMessageResource( - nativeResource: nativeResources.MessageResource, - type: resources.ResourceType, -) { - const parsedConversationUri: messagesUri.ConversationUri = messagesUri - .parseConversation(nativeResource.conversationLink); - const parsedContactUri: messagesUri.ContactUri = messagesUri.parseContact(nativeResource.from); - const parsedContactId: ParsedConversationId = parseContactId(parsedContactUri.contact); - return { - type, - id: nativeResource.id, - composeTime: new Date(nativeResource.composetime), - arrivalTime: new Date(nativeResource.originalarrivaltime), - from: parsedContactId, - conversation: parsedConversationUri.conversation, - native: nativeResource, - }; -} - // tslint:disable-next-line:max-line-length export function formatConversationUpdateResource(nativeResource: nativeResources.ConversationUpdate): resources.ConversationUpdateResource { const parsedConversationUri: messagesUri.ConversationUri = messagesUri @@ -98,202 +54,18 @@ export function formatConversationUpdateResource(nativeResource: nativeResources }; } -// tslint:disable-next-line:max-line-length -export function formatControlTypingResource( - retObj: resources.Resource, - nativeResource: nativeMessageResources.ControlTyping, -): resources.ControlTypingResource { - const ret: resources.ControlTypingResource = retObj as resources.ControlTypingResource; - return ret; -} - -// tslint:disable-next-line:max-line-length -export function formatSignalFlamingoResource( - retObj: resources.Resource, - nativeResource: nativeMessageResources.SignalFlamingo, -): resources.SignalFlamingoResource { - const ret: resources.SignalFlamingoResource = retObj as resources.SignalFlamingoResource; - ret.skypeguid = nativeResource.skypeguid; - return ret; -} - -function formatMessageResource(nativeResource: nativeResources.MessageResource): resources.Resource { - switch (nativeResource.messagetype) { - case "RichText/UriObject": - // tslint:disable-next-line:max-line-length - return formatUriObjectResource(formatFileResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource), nativeResource); - case "RichText/Media_Video": - // tslint:disable-next-line:max-line-length - return formatMediaVideoResource(formatFileResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource), nativeResource); - case "RichText/Media_GenericFile": - // tslint:disable-next-line:max-line-length - return formatMediaGenericFileResource(formatFileResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource), nativeResource); - case "RichText/Location": - // tslint:disable-next-line:max-line-length - return formatLocationResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource); - case "Event/Call": - // tslint:disable-next-line:max-line-length - return formatEventCallResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource); - case "RichText": - // tslint:disable-next-line:max-line-length - return formatRichTextResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource); - case "Text": - // tslint:disable-next-line:max-line-length - return formatTextResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource); - case "Control/ClearTyping": - // tslint:disable-next-line:max-line-length - return formatControlClearTypingResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource); - case "Control/Typing": - // tslint:disable-next-line:max-line-length - return formatControlTypingResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource); - case "Signal/Flamingo": // incoming call request - // tslint:disable-next-line:max-line-length - return formatSignalFlamingoResource(formatGenericMessageResource(nativeResource, nativeResource.messagetype), nativeResource); - default: - // tslint:disable-next-line:max-line-length - throw new Error(`Unknown ressource.messageType (${JSON.stringify(nativeResource.messagetype)}) for resource:\n${JSON.stringify(nativeResource, null, "\t")}`); - } -} - -type NativeFileResouce = - nativeMessageResources.MediaGenericFile - | nativeMessageResources.UriObject - | nativeMessageResources.MediaVideo; - -function formatFileResource(retObj: resources.Resource, native: NativeFileResouce): resources.FileResource { - const ret: resources.FileResource = retObj as resources.FileResource; - const $: CheerioStatic = cheerio.load(native.content); - const obj: Cheerio = $("URIObject"); - ret.uri_type = obj.attr("type"); - ret.uri = obj.attr("uri"); - ret.uri_thumbnail = obj.attr("url_thumbnail"); - ret.uri_w_login = $(obj.find("a")).attr("href"); - const sizeString: string | undefined = $(obj.find("FileSize")).attr("v"); - if (sizeString !== undefined) { - ret.file_size = parseInt(sizeString, 10); - } - ret.original_file_name = $(obj.find("OriginalName")).attr("v"); - return ret; -} - -// tslint:disable-next-line:max-line-length -function formatMediaGenericFileResource( - retObj: resources.FileResource, - native: nativeMessageResources.MediaGenericFile, -): resources.RichTextMediaGenericFileResource { - const ret: resources.RichTextMediaGenericFileResource = retObj as resources.RichTextMediaGenericFileResource; - return ret; -} - -// tslint:disable-next-line:max-line-length -function formatMediaVideoResource( - retObj: resources.FileResource, - native: nativeMessageResources.MediaVideo, -): resources.RichTextMediaGenericFileResource { - const ret: resources.RichTextMediaGenericFileResource = retObj as resources.RichTextMediaGenericFileResource; - return ret; -} - -// tslint:disable-next-line:max-line-length -function formatUriObjectResource( - retObj: resources.FileResource, - native: nativeMessageResources.UriObject, -): resources.RichTextUriObjectResource { - const ret: resources.RichTextUriObjectResource = retObj as resources.RichTextUriObjectResource; - return ret; -} - -// tslint:disable-next-line:max-line-length -function formatLocationResource( - retObj: resources.Resource, - native: nativeMessageResources.LocationObject, -): resources.RichTextLocationResource { - const ret: resources.RichTextLocationResource = retObj as resources.RichTextLocationResource; - const $: CheerioStatic = cheerio.load(native.content); - const obj: Cheerio = $("location"); - ret.latitude = parseInt(obj.attr("latitude"), 10); - ret.longitude = parseInt(obj.attr("longitude"), 10); - ret.altitude = parseInt(obj.attr("altitude"), 10); - ret.speed = parseInt(obj.attr("speed"), 10); - ret.course = parseInt(obj.attr("course"), 10); - ret.address = obj.attr("address"); - ret.pointOfInterest = obj.attr("pointOfInterest"); - ret.map_url = $(obj.find("a")).attr("href"); - return ret; -} - -// tslint:disable-next-line:max-line-length -function formatEventCallResource( - retObj: resources.Resource, - native: nativeMessageResources.EventCall, -): resources.EventCallResource { - const ret: resources.EventCallResource = retObj as resources.EventCallResource; - const $: CheerioStatic = cheerio.load(native.content); - const type: string = $("partlist").attr("type"); - if (type === "started") { - ret.event_type = type; - } else if (type === "ended") { - ret.event_type = type; - } else { - throw new Error(`Unknown call state of: ${type}`); - } - - let shortest: number | null = null; - let connected: boolean = false; - const participants: resources.CallParticipant[] = []; - const parts: CheerioElement[] = $("part").toArray(); - for (const part of parts) { - const pjs: Cheerio = $(part); - const add: resources.CallParticipant = {displayName: pjs.find("name").text(), username: pjs.attr("identity")}; - const duration: string | undefined = pjs.find("duration").text(); - if (duration !== undefined && duration !== "") { - add.duration = parseInt(duration, 10); - if (add.duration > 0) { - connected = true; - if (shortest === null || add.duration < shortest) { - shortest = add.duration; - } - } - } - participants.push(add); - } - ret.participants = participants; - ret.call_connected = connected || participants.length > 1; - if (shortest !== null) { - ret.duration = shortest; - } - return ret; +interface GetMessagesBody { + /** + * The `eventMessages` property is only present when the array is non-empty. + */ + eventMessages?: SkypeEvent[]; } -function formatEventMessage(native: nativeEvents.EventMessage): events.EventMessage { - let resource: resources.Resource | null; - switch (native.resourceType) { - case "UserPresence": - resource = null; - break; - case "EndpointPresence": - resource = null; - break; - case "ConversationUpdate": - resource = formatConversationUpdateResource(native.resource as nativeResources.ConversationUpdate); - break; - case "NewMessage": - resource = formatMessageResource( native.resource); - break; - default: - // tslint:disable-next-line:max-line-length - throw new Error(`Unknown EventMessage.resourceType (${JSON.stringify(native.resourceType)}) for Event:\n${JSON.stringify(native)}`); - } - - return { - id: native.id, - type: native.type, - resourceType: native.resourceType, - time: new Date(native.time), - resourceLink: native.resourceLink, - resource, - }; -} +const $GetMessagesBody: DocumentIoType = new DocumentType({ + properties: { + eventMessages: {type: new ArrayType({itemType: $SkypeEvent, maxLength: Infinity}), optional: true}, + }, +}); export class MessagesPoller extends _events.EventEmitter { io: httpIo.HttpIo; @@ -337,7 +109,7 @@ export class MessagesPoller extends _events.EventEmitter { */ protected async getMessages(): Promise { try { - const requestOptions: httpIo.PostOptions = { + const request: httpIo.PostOptions = { // TODO: explicitly define user, endpoint and subscription uri: messagesUri.poll(this.apiContext.registrationToken.host), cookies: this.apiContext.cookies, @@ -345,30 +117,29 @@ export class MessagesPoller extends _events.EventEmitter { RegistrationToken: this.apiContext.registrationToken.raw, }, }; - const res: httpIo.Response = await this.io.post(requestOptions); - if (res.statusCode !== 200) { - const cause: UnexpectedHttpStatusError = UnexpectedHttpStatusError.create(res, new Set([200]), requestOptions); - this.emit("error", Incident(cause, "poll", "Unable to poll the messages")); + const response: httpIo.Response = await this.io.post(request); + if (response.statusCode !== 200) { + const error: Error = UnexpectedHttpStatusError.create(response, new Set([200]), request); + this.emit("error", error); return; } - - const body: {eventMessages?: nativeEvents.EventMessage[]} = JSON.parse(res.body); - - if (body.eventMessages !== undefined) { - for (const msg of body.eventMessages) { - // tslint:disable-next-line:max-line-length - // if (msg.resourceType != "UserPresence" && msg.resourceType != "EndpointPresence" && msg.resourceType != "ConversationUpdate") - // console.log("EVT: " + JSON.stringify(msg, null, "\t")); - - const formatted: events.EventMessage = formatEventMessage(msg); - if (formatted.resource !== null) { - this.emit("event-message", formatted); - } - } + let result: GetMessagesBody; + try { + result = $GetMessagesBody.read(JSON_READER, response.body); + } catch (err) { + err.message = objectInspect(err.data, {depth: 20}); + this.emit("error", new Incident(err, "UnexpectedResponseBody", {body: response.body})); + return; + } + if (result.eventMessages === undefined) { + return; + } + for (const event of result.eventMessages) { + this.emit("event", event); } } catch (err) { - this.emit("error", Incident(err, "poll", "An error happened while processing the polled messages")); + this.emit("error", Incident(err, "PollError", "Unable to poll the latest events")); } } } diff --git a/src/lib/types/conversation.ts b/src/lib/types/conversation.ts index 77cc52d..e4161b0 100644 --- a/src/lib/types/conversation.ts +++ b/src/lib/types/conversation.ts @@ -5,8 +5,8 @@ import { DocumentIoType, DocumentType } from "kryo/types/document"; import { TryUnionType } from "kryo/types/try-union"; import { $ConversationType, ConversationType } from "./conversation-type"; import { $EmptyObject, EmptyObject } from "./empty-object"; -import { $Message, Message } from "./message"; import { $MriKey, MriKey } from "./mri-key"; +import { $MessageResource, MessageResource } from "./resources/message-resource"; import { $ThreadProperties, ThreadProperties } from "./thread-properties"; import { $Url, Url } from "./url"; @@ -42,7 +42,7 @@ export interface Conversation { */ version: number; // a timestamp ? example: - lastMessage: EmptyObject | Message; + lastMessage: EmptyObject | MessageResource; /** * Examples: @@ -70,7 +70,7 @@ export const $Conversation: DocumentIoType = new DocumentType = new DocumentType({ + properties: { + id: {type: $Uint32}, + type: { + type: new LiteralType({ + type: $EventType, + value: EventType.EventMessage, + }), + }, + resourceType: { + type: new LiteralType({ + type: $EventResourceType, + value: EventResourceType.EndpointPresence, + }), + }, + time: {type: $Date}, + resourceLink: {type: $Url}, + resource: {type: $EndpointPresenceDocResource}, + }, +}); diff --git a/src/lib/types/events/event-resource-type.ts b/src/lib/types/events/event-resource-type.ts new file mode 100644 index 0000000..a0506b6 --- /dev/null +++ b/src/lib/types/events/event-resource-type.ts @@ -0,0 +1,11 @@ +import { TsEnumType } from "kryo/types/ts-enum"; + +export enum EventResourceType { + NewMessage, + UserPresence, + EndpointPresence, +} + +export const $EventResourceType: TsEnumType = new TsEnumType({ + enum: EventResourceType, +}); diff --git a/src/lib/types/events/event-type.ts b/src/lib/types/events/event-type.ts new file mode 100644 index 0000000..893ea6f --- /dev/null +++ b/src/lib/types/events/event-type.ts @@ -0,0 +1,9 @@ +import { TsEnumType } from "kryo/types/ts-enum"; + +export enum EventType { + EventMessage, +} + +export const $EventType: TsEnumType = new TsEnumType({ + enum: EventType, +}); diff --git a/src/lib/types/events/new-message-event.ts b/src/lib/types/events/new-message-event.ts new file mode 100644 index 0000000..e28c7b8 --- /dev/null +++ b/src/lib/types/events/new-message-event.ts @@ -0,0 +1,41 @@ +import { $Date } from "kryo/builtins/date"; +import { $Uint32 } from "kryo/builtins/uint32"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { $MessageResource, MessageResource } from "../resources/message-resource"; +import { $Url } from "../url"; +import { $EventResourceType, EventResourceType } from "./event-resource-type"; +import { $EventType, EventType } from "./event-type"; + +export interface NewMessageEvent { + id: number; + type: EventType.EventMessage; + resourceType: EventResourceType.NewMessage; + time: Date; + /** + * https://{host}/v1/users/ME/conversations/{conversation}/messages/{id} + */ + resourceLink: string; + resource: MessageResource; +} + +export const $NewMessageEvent: DocumentIoType = new DocumentType({ + properties: { + id: {type: $Uint32}, + type: { + type: new LiteralType({ + type: $EventType, + value: EventType.EventMessage, + }), + }, + resourceType: { + type: new LiteralType({ + type: $EventResourceType, + value: EventResourceType.NewMessage, + }), + }, + time: {type: $Date}, + resourceLink: {type: $Url}, + resource: {type: $MessageResource}, + }, +}); diff --git a/src/lib/types/events/skype-event.ts b/src/lib/types/events/skype-event.ts new file mode 100644 index 0000000..3aaf52a --- /dev/null +++ b/src/lib/types/events/skype-event.ts @@ -0,0 +1,15 @@ +import { TaggedUnionType } from "kryo/types/tagged-union"; +import { $EndpointPresenceEvent, EndpointPresenceEvent } from "./endpoint-presence-event"; +import { $NewMessageEvent, NewMessageEvent } from "./new-message-event"; +import { $UserPresenceEvent, UserPresenceEvent } from "./user-presence-event"; + +export type SkypeEvent = EndpointPresenceEvent | NewMessageEvent | UserPresenceEvent; + +export const $SkypeEvent: TaggedUnionType = new TaggedUnionType({ + variants: [ + $EndpointPresenceEvent, + $NewMessageEvent, + $UserPresenceEvent, + ], + tag: "resourceType", +}); diff --git a/src/lib/types/events/user-presence-event.ts b/src/lib/types/events/user-presence-event.ts new file mode 100644 index 0000000..697bad0 --- /dev/null +++ b/src/lib/types/events/user-presence-event.ts @@ -0,0 +1,38 @@ +import { $Date } from "kryo/builtins/date"; +import { $Uint32 } from "kryo/builtins/uint32"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { $UserPresenceDocResource, UserPresenceDocResource } from "../resources/user-presence-doc-resource"; +import { $Url } from "../url"; +import { $EventResourceType, EventResourceType } from "./event-resource-type"; +import { $EventType, EventType } from "./event-type"; + +export interface UserPresenceEvent { + id: number; + type: EventType.EventMessage; + resourceType: EventResourceType.UserPresence; + time: Date; + resourceLink: string; + resource: UserPresenceDocResource; +} + +export const $UserPresenceEvent: DocumentIoType = new DocumentType({ + properties: { + id: {type: $Uint32}, + type: { + type: new LiteralType({ + type: $EventType, + value: EventType.EventMessage, + }), + }, + resourceType: { + type: new LiteralType({ + type: $EventResourceType, + value: EventResourceType.UserPresence, + }), + }, + time: {type: $Date}, + resourceLink: {type: $Url}, + resource: {type: $UserPresenceDocResource}, + }, +}); diff --git a/src/lib/types/message-type.ts b/src/lib/types/message-type.ts deleted file mode 100644 index a3c7813..0000000 --- a/src/lib/types/message-type.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { CaseStyle } from "kryo/case-style"; -import { TsEnumType } from "kryo/types/ts-enum"; - -export enum MessageType { - RichText, -} - -export const $MessageType: TsEnumType = new TsEnumType({ - enum: MessageType, - changeCase: CaseStyle.PascalCase, -}); diff --git a/src/lib/types/message.ts b/src/lib/types/message.ts deleted file mode 100644 index c05d6ab..0000000 --- a/src/lib/types/message.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { $Date } from "kryo/builtins/date"; -import { DocumentType } from "kryo/types/document"; -import { Ucs2StringType } from "kryo/types/ucs2-string"; -import { $MessageType, MessageType } from "./message-type"; -import { $MriKey, MriKey } from "./mri-key"; -import { $ResourceType, ResourceType } from "./resource-type"; -import { $Url, Url } from "./url"; - -// tslint:disable:max-line-length -export interface Message { - /** - * Looks like a timestamp. - * - * Example: - * - `"1517157679445"` - */ - id: string; - - originalarrivaltime: Date; - - messagetype: MessageType; - - /** - * Looks like a timestamp. - * - * Example: - * - `"1517157679445"` - * - * Note: Can be equal to `id`. - */ - version: string; - - composetime: Date; - - /** - * Looks like a timestamp but in ms (as opposed to id/version in s) - * - * Example: - * - `"15834758132099454160"` - */ - clientmessageid: string; - - /** - * Example: - * - `"https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/19:4dd7eb8db8714b2c84a6667aae45effa@thread.skype"` - */ - conversationLink: Url; - - /** - * Example: - * - `"https://www.youtube.com/watch?v=dQw4w9WgXcQ"` - */ - content: string; - - type: ResourceType; - - /** - * Example: - * - `"19:4dd7eb8db8714b2c84a6667aae45effa@thread.skype"` - */ - conversationid: MriKey; - - /** - * Example: - * - `"https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob"` - */ - from: Url; -} - -// tslint:enable - -export const $Message: DocumentType = new DocumentType({ - properties: { - id: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/})}, - originalarrivaltime: {type: $Date}, - messagetype: {type: $MessageType}, - version: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/})}, - composetime: {type: $Date}, - clientmessageid: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/})}, - conversationLink: {type: $Url}, - content: {type: new Ucs2StringType({maxLength: Infinity})}, - type: {type: $ResourceType}, - conversationid: {type: $MriKey}, - from: {type: $Url}, - }, -}); diff --git a/src/lib/types/resources/_message-resource-base.ts b/src/lib/types/resources/_message-resource-base.ts new file mode 100644 index 0000000..3cd6495 --- /dev/null +++ b/src/lib/types/resources/_message-resource-base.ts @@ -0,0 +1,110 @@ +import { $Boolean } from "kryo/builtins/boolean"; +import { $Date } from "kryo/builtins/date"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $Url, Url } from "../url"; +import { $MessageType, MessageType } from "./message-type"; +import { $ResourceType, ResourceType } from "./resource-type"; + +/** + * @internal + */ +// tslint:disable:max-line-length +export interface MessageResourceBase { + /** + * Looks like a timestamp. + * + * Example: + * - `"1517157679445"` + */ + id: string; + + type: ResourceType.Message; + + messageType: MessageType; + + /** + * URL where the `ack` should be sent + */ + ackRequired?: Url; + + originalArrivalTime: Date; + + /** + * Instant Messaging Display Name + * Display name of the author of the message. + */ + imDisplayName?: string; + + /** + * Looks like a timestamp. + * + * Example: + * - `"1517157679445"` + * + * Note: Can be equal to `id`. + */ + version: string; + + composeTime: Date; + + /** + * Example: + * - `"https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/19:4dd7eb8db8714b2c84a6667aae45effa@thread.skype"` + */ + conversationLink: Url; + + isActive?: boolean; + + /** + * Example: + * - `"https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob"` + */ + from: Url; + + /** + * Title of the thread (group conversation) + */ + threadTopic?: string; + + /** + * Example: + * - `"1518108708775"` + */ + counterpartyMessageId: string; + + /** + * Examples: + * - `"text"` + * - `"Application/Message"` + */ + contentType?: string; +} + +/** + * @internal + */ +export const $MessageResourceBase: DocumentIoType = new DocumentType({ + properties: { + id: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/})}, + type: { + type: new LiteralType({ + type: $ResourceType, + value: ResourceType.Message, + }), + }, + messageType: {type: $MessageType, rename: "messagetype"}, + ackRequired: {type: $Url, optional: true, rename: "ackrequired"}, + originalArrivalTime: {type: $Date, rename: "originalarrivaltime"}, + imDisplayName: {type: new Ucs2StringType({maxLength: Infinity}), optional: true, rename: "imdisplayname"}, + version: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/})}, + composeTime: {type: $Date, rename: "composetime"}, + conversationLink: {type: $Url}, + isActive: {type: $Boolean, optional: true, rename: "isactive"}, + threadTopic: {type: new Ucs2StringType({maxLength: Infinity}), optional: true, rename: "threadtopic"}, + from: {type: $Url}, + counterpartyMessageId: {type: new Ucs2StringType({maxLength: Infinity}), rename: "counterpartymessageid"}, + contentType: {type: new Ucs2StringType({maxLength: Infinity}), optional: true, rename: "contenttype"}, + }, +}); diff --git a/src/lib/types/resources/control-clear-typing-message.ts b/src/lib/types/resources/control-clear-typing-message.ts new file mode 100644 index 0000000..ec92e8f --- /dev/null +++ b/src/lib/types/resources/control-clear-typing-message.ts @@ -0,0 +1,22 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface ControlClearTypingMessage extends MessageResourceBase { + messageType: MessageType.ControlClearTyping; +} + +// tslint:disable-next-line:max-line-length +export const $ControlClearTypingMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.ControlClearTyping, + }), + rename: "messagetype", + }, + }, +}); diff --git a/src/lib/types/resources/control-live-state-message.ts b/src/lib/types/resources/control-live-state-message.ts new file mode 100644 index 0000000..8833a3c --- /dev/null +++ b/src/lib/types/resources/control-live-state-message.ts @@ -0,0 +1,22 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface ControlLiveStateMessage extends MessageResourceBase { + messageType: MessageType.ControlLiveState; +} + +// tslint:disable-next-line:max-line-length +export const $ControlLiveStateMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.ControlLiveState, + }), + rename: "messagetype", + }, + }, +}); diff --git a/src/lib/types/resources/control-typing-message.ts b/src/lib/types/resources/control-typing-message.ts new file mode 100644 index 0000000..9024eac --- /dev/null +++ b/src/lib/types/resources/control-typing-message.ts @@ -0,0 +1,21 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface ControlTypingMessage extends MessageResourceBase { + messageType: MessageType.ControlTyping; +} + +export const $ControlTypingMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.ControlTyping, + }), + rename: "messagetype", + }, + }, +}); diff --git a/src/lib/types/resources/conversation-update-resource.ts b/src/lib/types/resources/conversation-update-resource.ts new file mode 100644 index 0000000..fec02fa --- /dev/null +++ b/src/lib/types/resources/conversation-update-resource.ts @@ -0,0 +1,27 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageResource, MessageResource } from "./message-resource"; +import { $ResourceType, ResourceType } from "./resource-type"; + +export interface ConversationUpdateResource { + id: string; + + type: ResourceType.ConversationUpdate; + + lastMessage: MessageResource; +} + +// tslint:disable-next-line:max-line-length +export const $ConversationUpdateResource: DocumentIoType = new DocumentType({ + properties: { + id: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/})}, + type: { + type: new LiteralType({ + type: $ResourceType, + value: ResourceType.ConversationUpdate, + }), + }, + lastMessage: {type: $MessageResource}, + }, +}); diff --git a/src/lib/types/resources/endpoint-presence-doc-resource.ts b/src/lib/types/resources/endpoint-presence-doc-resource.ts new file mode 100644 index 0000000..a9466b9 --- /dev/null +++ b/src/lib/types/resources/endpoint-presence-doc-resource.ts @@ -0,0 +1,43 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $Url, Url } from "../url"; +import { $EndpointPrivateInfo, EndpointPrivateInfo } from "./endpoint-private-info"; +import { $EndpointPublicInfo, EndpointPublicInfo } from "./endpoint-public-info"; +import { $ResourceType, ResourceType } from "./resource-type"; + +export interface EndpointPresenceDocResource { + id: "messagingService"; + + type: ResourceType.EndpointPresenceDoc; + /** + * Example: + * - `https://{host}/v1/users/{user}/endpoints/{endpoint}/presenceDocs/endpointMessagingService` + */ + selfLink: Url; + + publicInfo: EndpointPublicInfo; + + privateInfo: EndpointPrivateInfo; +} + +// tslint:disable-next-line:max-line-length +export const $EndpointPresenceDocResource: DocumentIoType = new DocumentType({ + properties: { + id: { + type: new LiteralType<"messagingService">({ + type: new Ucs2StringType({maxLength: Infinity}), + value: "messagingService", + }), + }, + type: { + type: new LiteralType({ + type: $ResourceType, + value: ResourceType.EndpointPresenceDoc, + }), + }, + selfLink: {type: $Url}, + publicInfo: {type: $EndpointPublicInfo}, + privateInfo: {type: $EndpointPrivateInfo}, + }, +}); diff --git a/src/lib/types/resources/endpoint-private-info.ts b/src/lib/types/resources/endpoint-private-info.ts new file mode 100644 index 0000000..21b34cd --- /dev/null +++ b/src/lib/types/resources/endpoint-private-info.ts @@ -0,0 +1,17 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; + +export interface EndpointPrivateInfo { + /** + * Endpoint name + * + * Usually the name of the computer (host for Linux ?) + */ + epName?: string; +} + +export const $EndpointPrivateInfo: DocumentIoType = new DocumentType({ + properties: { + epName: {type: new Ucs2StringType({maxLength: Infinity}), optional: true, rename: "epname"}, + }, +}); diff --git a/src/lib/types/resources/endpoint-public-info.ts b/src/lib/types/resources/endpoint-public-info.ts new file mode 100644 index 0000000..7c8cc14 --- /dev/null +++ b/src/lib/types/resources/endpoint-public-info.ts @@ -0,0 +1,47 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { WhiteListType } from "kryo/types/white-list"; + +export interface EndpointPublicInfo { + /** + * Looks like capabilities.join(" | ") where capabilities is one of ["Seamless", "SmsUpgrade"]; + * (no IsMobile apparently, as opposed to `UserPresenceResource`) + * + * Example: + * - `"Seamless | SmsUpgrade"` + */ + capabilities: string; + + typ: "" | "11" | "12" | "13" | "14" | "16" | "17"; + + skypeNameVersion: string; + + /** + * Examples: + * - `"xx"` + * - A string with the pattern: /^x[0-9a-f]{58}/ + */ + nodeInfo: string; + + /** + * Examples: + * - `"15"` + * - `"24"` + */ + version: string; +} + +export const $EndpointPublicInfo: DocumentIoType = new DocumentType(() => ({ + properties: { + capabilities: {type: new Ucs2StringType({maxLength: Infinity})}, + typ: { + type: new WhiteListType<"" | "11" | "12" | "13" | "14" | "16" | "17">({ + itemType: new Ucs2StringType({maxLength: Infinity}), + values: ["", "11", "12", "13", "14", "16", "17"], + }), + }, + skypeNameVersion: {type: new Ucs2StringType({maxLength: Infinity})}, + nodeInfo: {type: new Ucs2StringType({maxLength: Infinity})}, + version: {type: new Ucs2StringType({maxLength: Infinity})}, + }, +})); diff --git a/src/lib/types/resources/event-call-message.ts b/src/lib/types/resources/event-call-message.ts new file mode 100644 index 0000000..fd26d52 --- /dev/null +++ b/src/lib/types/resources/event-call-message.ts @@ -0,0 +1,32 @@ +import { $UuidHex } from "kryo/builtins/uuid-hex"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface EventCallMessage extends MessageResourceBase { + messageType: MessageType.EventCall; + + /** + * XML string + */ + content: string; + + skypeGuid: string; +} + +export const $EventCallMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.EventCall, + }), + rename: "messagetype", + }, + content: {type: new Ucs2StringType({maxLength: Infinity})}, + skypeGuid: {type: $UuidHex, rename: "skypeguid"}, + }, +}); diff --git a/src/lib/types/resources/message-resource.ts b/src/lib/types/resources/message-resource.ts new file mode 100644 index 0000000..0aa0d8c --- /dev/null +++ b/src/lib/types/resources/message-resource.ts @@ -0,0 +1,45 @@ +import { TaggedUnionType } from "kryo/types/tagged-union"; +import { $ControlClearTypingMessage, ControlClearTypingMessage } from "./control-clear-typing-message"; +import { $ControlLiveStateMessage, ControlLiveStateMessage } from "./control-live-state-message"; +import { $ControlTypingMessage, ControlTypingMessage } from "./control-typing-message"; +import { $EventCallMessage, EventCallMessage } from "./event-call-message"; +import { $RichTextLocationMessage, RichTextLocationMessage } from "./rich-text-location-message"; +import { + $RichTextMediaGenericFileMessage, + RichTextMediaGenericFileMessage, +} from "./rich-text-media-generic-file-message"; +import { $RichTextMediaVideoMessage, RichTextMediaVideoMessage } from "./rich-text-media-video-message"; +import { $RichTextMessage, RichTextMessage } from "./rich-text-message"; +import { $RichTextUriObjectMessage, RichTextUriObjectMessage } from "./rich-text-uri-object-message"; +import { $SignalFlamingoMessage, SignalFlamingoMessage } from "./signal-flamingo-message"; +import { $TextMessage, TextMessage } from "./text-message"; + +export type MessageResource = + ControlClearTypingMessage + | ControlLiveStateMessage + | ControlTypingMessage + | EventCallMessage + | RichTextMessage + | RichTextUriObjectMessage + | RichTextLocationMessage + | RichTextMediaGenericFileMessage + | RichTextMediaVideoMessage + | SignalFlamingoMessage + | TextMessage; + +export const $MessageResource: TaggedUnionType = new TaggedUnionType(() => ({ + variants: [ + $ControlClearTypingMessage, + $ControlLiveStateMessage, + $ControlTypingMessage, + $EventCallMessage, + $RichTextMessage, + $RichTextUriObjectMessage, + $RichTextLocationMessage, + $RichTextMediaGenericFileMessage, + $RichTextMediaVideoMessage, + $SignalFlamingoMessage, + $TextMessage, + ], + tag: "messageType", +})); diff --git a/src/lib/types/resources/message-type.ts b/src/lib/types/resources/message-type.ts new file mode 100644 index 0000000..20cc6b8 --- /dev/null +++ b/src/lib/types/resources/message-type.ts @@ -0,0 +1,32 @@ +import { TsEnumType } from "kryo/types/ts-enum"; + +export enum MessageType { + ControlClearTyping = "Control/ClearTyping", + ControlLiveState = "Control/LiveState", + ControlTyping = "Control/Typing", + EventCall = "Event/Call", + RichText = "RichText", + RichTextUriObject = "RichText/UriObject", + RichTextLocation = "RichText/Location", + RichTextMediaGenericFile = "RichText/Media_GenericFile", + RichTextMediaVideo = "RichText/Media_Video", + SignalFlamingo = "Signal/Flamingo", + Text = "Text", +} + +export const $MessageType: TsEnumType = new TsEnumType({ + enum: MessageType, + rename: { + ControlClearTyping: "Control/ClearTyping", + ControlLiveState: "Control/LiveState", + ControlTyping: "Control/Typing", + EventCall: "Event/Call", + RichText: "RichText", + RichTextUriObject: "RichText/UriObject", + RichTextLocation: "RichText/Location", + RichTextMediaGenericFile: "RichText/Media_GenericFile", + RichTextMediaVideo: "RichText/Media_Video", + SignalFlamingo: "Signal/Flamingo", + Text: "Text", + }, +}); diff --git a/src/lib/types/resource-type.ts b/src/lib/types/resources/resource-type.ts similarity index 71% rename from src/lib/types/resource-type.ts rename to src/lib/types/resources/resource-type.ts index e3a0014..4298c3c 100644 --- a/src/lib/types/resource-type.ts +++ b/src/lib/types/resources/resource-type.ts @@ -1,11 +1,12 @@ -import { CaseStyle } from "kryo/case-style"; import { TsEnumType } from "kryo/types/ts-enum"; export enum ResourceType { + ConversationUpdate, + EndpointPresenceDoc, Message, + UserPresenceDoc, } export const $ResourceType: TsEnumType = new TsEnumType({ enum: ResourceType, - changeCase: CaseStyle.PascalCase, }); diff --git a/src/lib/types/resources/rich-text-location-message.ts b/src/lib/types/resources/rich-text-location-message.ts new file mode 100644 index 0000000..c9553cf --- /dev/null +++ b/src/lib/types/resources/rich-text-location-message.ts @@ -0,0 +1,29 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface RichTextLocationMessage extends MessageResourceBase { + messageType: MessageType.RichTextLocation; + + /** + * XML string + */ + content: string; +} + +// tslint:disable-next-line:max-line-length +export const $RichTextLocationMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.RichTextLocation, + }), + rename: "messagetype", + }, + content: {type: new Ucs2StringType({maxLength: Infinity})}, + }, +}); diff --git a/src/lib/types/resources/rich-text-media-generic-file-message.ts b/src/lib/types/resources/rich-text-media-generic-file-message.ts new file mode 100644 index 0000000..ee0f9f1 --- /dev/null +++ b/src/lib/types/resources/rich-text-media-generic-file-message.ts @@ -0,0 +1,38 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +// tslint:disable:max-line-length +export interface RichTextMediaGenericFileMessage extends MessageResourceBase { + messageType: MessageType.RichTextMediaGenericFile; + /** + * XML string + * + * Example: + * - `'To view this file, go to: https://login.skype.com/login/sso?go=webclient.xmm&docid=0-neu-d2-26c333f286967716e96dd92c080993be'` + */ + content: string; + + // uri_type: string; + // uri: string; + // uri_thumbnail: string; + // uri_w_login: string; + // file_size?: number; + // original_file_name: string; +} + +export const $RichTextMediaGenericFileMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.RichTextMediaGenericFile, + }), + rename: "messagetype", + }, + content: {type: new Ucs2StringType({maxLength: Infinity})}, + }, +}); diff --git a/src/lib/types/resources/rich-text-media-video-message.ts b/src/lib/types/resources/rich-text-media-video-message.ts new file mode 100644 index 0000000..634aebe --- /dev/null +++ b/src/lib/types/resources/rich-text-media-video-message.ts @@ -0,0 +1,36 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface RichTextMediaVideoMessage extends MessageResourceBase { + messageType: MessageType.RichTextMediaVideo; + + /** + * XML string + */ + content: string; + + // uri_type: string; + // uri: string; + // uri_thumbnail: string; + // uri_w_login: string; + // file_size?: number; + // original_file_name: string; +} + +// tslint:disable-next-line:max-line-length +export const $RichTextMediaVideoMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.RichTextMediaVideo, + }), + rename: "messagetype", + }, + content: {type: new Ucs2StringType({maxLength: Infinity})}, + }, +}); diff --git a/src/lib/types/resources/rich-text-message.ts b/src/lib/types/resources/rich-text-message.ts new file mode 100644 index 0000000..6d079f2 --- /dev/null +++ b/src/lib/types/resources/rich-text-message.ts @@ -0,0 +1,38 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface RichTextMessage extends MessageResourceBase { + messageType: MessageType.RichText; + + /** + * Looks like a timestamp but in ms (as opposed to id/version in s) + * + * Example: + * - `"15834758132099454160"` + */ + clientMessageId: string; + + /** + * Example: + * - `"https://www.youtube.com/watch?v=dQw4w9WgXcQ"` + */ + content: string; +} + +export const $RichTextMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.RichText, + }), + rename: "messagetype", + }, + clientMessageId: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/}), rename: "clientmessageid"}, + content: {type: new Ucs2StringType({maxLength: Infinity})}, + }, +}); diff --git a/src/lib/types/resources/rich-text-uri-object-message.ts b/src/lib/types/resources/rich-text-uri-object-message.ts new file mode 100644 index 0000000..77e140b --- /dev/null +++ b/src/lib/types/resources/rich-text-uri-object-message.ts @@ -0,0 +1,36 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface RichTextUriObjectMessage extends MessageResourceBase { + messageType: MessageType.RichTextUriObject; + + /** + * XML string + */ + content: string; + + // uri_type: string; + // uri: string; + // uri_thumbnail: string; + // uri_w_login: string; + // file_size?: number; + // original_file_name: string; +} + +// tslint:disable-next-line:max-line-length +export const $RichTextUriObjectMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.RichTextUriObject, + }), + rename: "messagetype", + }, + content: {type: new Ucs2StringType({maxLength: Infinity})}, + }, +}); diff --git a/src/lib/types/resources/signal-flamingo-message.ts b/src/lib/types/resources/signal-flamingo-message.ts new file mode 100644 index 0000000..7dcdb92 --- /dev/null +++ b/src/lib/types/resources/signal-flamingo-message.ts @@ -0,0 +1,28 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface SignalFlamingoMessage extends MessageResourceBase { + messageType: MessageType.SignalFlamingo; + + /** + * Skype Globally Unique ID + */ + skypeGuid: string; +} + +export const $SignalFlamingoMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.SignalFlamingo, + }), + rename: "messagetype", + }, + skypeGuid: {type: new Ucs2StringType({maxLength: Infinity}), rename: "skypeguid"}, + }, +}); diff --git a/src/lib/types/resources/text-message.ts b/src/lib/types/resources/text-message.ts new file mode 100644 index 0000000..1938335 --- /dev/null +++ b/src/lib/types/resources/text-message.ts @@ -0,0 +1,38 @@ +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $MessageResourceBase, MessageResourceBase } from "./_message-resource-base"; +import { $MessageType, MessageType } from "./message-type"; + +export interface TextMessage extends MessageResourceBase { + messageType: MessageType.Text; + + /** + * Looks like a timestamp but in ms (as opposed to id/version in s) + * + * Example: + * - `"15834758132099454160"` + */ + clientMessageId: string; + + /** + * Example: + * - `"Hello, World!"` + */ + content: string; +} + +export const $TextMessage: DocumentIoType = new DocumentType({ + properties: { + ...$MessageResourceBase.properties, + messageType: { + type: new LiteralType({ + type: $MessageType, + value: MessageType.Text, + }), + rename: "messagetype", + }, + clientMessageId: {type: new Ucs2StringType({maxLength: Infinity, pattern: /^\d+$/}), rename: "clientmessageid"}, + content: {type: new Ucs2StringType({maxLength: Infinity})}, + }, +}); diff --git a/src/lib/types/resources/user-presence-availability.ts b/src/lib/types/resources/user-presence-availability.ts new file mode 100644 index 0000000..94f64fa --- /dev/null +++ b/src/lib/types/resources/user-presence-availability.ts @@ -0,0 +1,11 @@ +import { TsEnumType } from "kryo/types/ts-enum"; + +export enum UserPresenceAvailability { + Offline, + Online, +} + +// tslint:disable:max-line-length +export const $UserPresenceAvailability: TsEnumType = new TsEnumType({ + enum: UserPresenceAvailability, +}); diff --git a/src/lib/types/resources/user-presence-doc-resource.ts b/src/lib/types/resources/user-presence-doc-resource.ts new file mode 100644 index 0000000..6b5fe72 --- /dev/null +++ b/src/lib/types/resources/user-presence-doc-resource.ts @@ -0,0 +1,60 @@ +import { $Date } from "kryo/builtins/date"; +import { ArrayType } from "kryo/types/array"; +import { DocumentIoType, DocumentType } from "kryo/types/document"; +import { LiteralType } from "kryo/types/literal"; +import { Ucs2StringType } from "kryo/types/ucs2-string"; +import { $Url, Url } from "../url"; +import { $ResourceType, ResourceType } from "./resource-type"; +import { $UserPresenceAvailability, UserPresenceAvailability } from "./user-presence-availability"; +import { $UserPresenceStatus, UserPresenceStatus } from "./user-presence-status"; + +export interface UserPresenceDocResource { + id: "messagingService"; + + type: ResourceType.UserPresenceDoc; + /** + * Example: + * - `"https://{host}/v1/users/{user}/presenceDocs/endpointMessagingService" user is 8:username` + */ + selfLink: Url; + + availability: UserPresenceAvailability; + + status: UserPresenceStatus; + + /** + * Looks like capabilities.join(" | ") where capabilities is one of ["Seamless", "SmsUpgrade", "IsMobile"]; + * + * Example: + * - `"IsMobile | SmsUpgrade"` + */ + capabilities: string; + + lastSeenAt?: Date; + + endpointPresenceDocLinks: Url[]; +} + +// tslint:disable-next-line:max-line-length +export const $UserPresenceDocResource: DocumentIoType = new DocumentType({ + properties: { + id: { + type: new LiteralType<"messagingService">({ + type: new Ucs2StringType({maxLength: Infinity}), + value: "messagingService", + }), + }, + type: { + type: new LiteralType({ + type: $ResourceType, + value: ResourceType.UserPresenceDoc, + }), + }, + selfLink: {type: $Url}, + availability: {type: $UserPresenceAvailability}, + status: {type: $UserPresenceStatus}, + capabilities: {type: new Ucs2StringType({maxLength: Infinity})}, + lastSeenAt: {type: $Date, optional: true}, + endpointPresenceDocLinks: {type: new ArrayType({itemType: $Url, maxLength: Infinity})}, + }, +}); diff --git a/src/lib/types/resources/user-presence-status.ts b/src/lib/types/resources/user-presence-status.ts new file mode 100644 index 0000000..0c7cf65 --- /dev/null +++ b/src/lib/types/resources/user-presence-status.ts @@ -0,0 +1,12 @@ +import { TsEnumType } from "kryo/types/ts-enum"; + +export enum UserPresenceStatus { + Busy, + Idle, + Offline, + Online, +} + +export const $UserPresenceStatus: TsEnumType = new TsEnumType({ + enum: UserPresenceStatus, +}); diff --git a/src/test/polling/message-poller.spec.ts b/src/test/polling/message-poller.spec.ts deleted file mode 100644 index 2988442..0000000 --- a/src/test/polling/message-poller.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { assert } from "chai"; -import * as resources from "../../lib/interfaces/api/resources"; -import * as nativeMessageResources from "../../lib/interfaces/native-api/message-resources"; -import * as messagesUri from "../../lib/messages-uri"; -import { - formatControlClearTypingResource, - formatControlTypingResource, - formatGenericMessageResource, - parseContactId, -} from "../../lib/polling/messages-poller"; - -describe("formatControlClearTypingResource", function () { - interface Item { - nativeResource: nativeMessageResources.ControlClearTyping; - expectedFormattedResource: resources.ControlClearTypingResource; - } - - const items: Item[] = [ - { - nativeResource: { - id: "1483879804631", - // tslint:disable-next-line:max-line-length - ackrequired: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483879804631/ack", - originalarrivaltime: "2017-01-08T12:50:04.626Z", - imdisplayname: "Bob", - messagetype: "Control/ClearTyping", - conversationLink: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", - composetime: "2017-01-08T12:50:04.626Z", - isactive: true, - from: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", - type: "Message", - counterpartymessageid: "1483879804624", - version: "1483879804631", - }, - expectedFormattedResource: { - type: "Control/ClearTyping", - id: "1483879804631", - composeTime: new Date("2017-01-08T12:50:04.626Z"), - arrivalTime: new Date("2017-01-08T12:50:04.626Z"), - from: parseContactId("8:bob"), - // tslint:disable-next-line:max-line-length - conversation: messagesUri.parseConversation("https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob").conversation, - native: { - id: "1483879804631", - // tslint:disable-next-line:max-line-length - ackrequired: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483879804631/ack", - originalarrivaltime: "2017-01-08T12:50:04.626Z", - imdisplayname: "Bob", - messagetype: "Control/ClearTyping", - conversationLink: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", - composetime: "2017-01-08T12:50:04.626Z", - isactive: true, - from: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", - type: "Message", - counterpartymessageid: "1483879804624", - version: "1483879804631", - }, - }, - }, - ]; - - for (const item of items) { - it("should format a Control/ClearTyping Message resource", function () { - // tslint:disable-next-line:max-line-length - const actual: resources.ControlClearTypingResource = formatControlClearTypingResource(formatGenericMessageResource(item.nativeResource, item.nativeResource.messagetype), item.nativeResource); - const expected: resources.ControlClearTypingResource = item.expectedFormattedResource; - assert.strictEqual(actual.type, expected.type); - assert.strictEqual(actual.id, expected.id); - assert.strictEqual(actual.composeTime.getTime(), expected.composeTime.getTime()); - assert.strictEqual(actual.arrivalTime.getTime(), expected.arrivalTime.getTime()); - assert.deepEqual(actual.from, expected.from); - assert.strictEqual(actual.conversation, expected.conversation); - assert.deepEqual(actual.native, expected.native); - }); - } -}); - -describe("formatControlTypingResource", function () { - interface Item { - nativeResource: nativeMessageResources.ControlTyping; - expectedFormattedResource: resources.ControlTypingResource; - } - - const items: Item[] = [ - { - nativeResource: { - id: "1483885996187", - // tslint:disable-next-line:max-line-length - ackrequired: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483885996187/ack", - originalarrivaltime: "2017-01-08T14:33:16.196Z", - imdisplayname: "Bob", - messagetype: "Control/Typing", - conversationLink: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", - composetime: "2017-01-08T14:33:16.196Z", - isactive: true, - from: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", - type: "Message", - counterpartymessageid: "1483885996189", - version: "1483885996187", - }, - expectedFormattedResource: { - type: "Control/Typing", - id: "1483885996187", - composeTime: new Date("2017-01-08T14:33:16.196Z"), - arrivalTime: new Date("2017-01-08T14:33:16.196Z"), - from: parseContactId("8:bob"), - // tslint:disable-next-line:max-line-length - conversation: messagesUri.parseConversation("https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob").conversation, - native: { - id: "1483885996187", - // tslint:disable-next-line:max-line-length - ackrequired: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483885996187/ack", - originalarrivaltime: "2017-01-08T14:33:16.196Z", - imdisplayname: "Bob", - messagetype: "Control/Typing", - // tslint:disable-next-line:max-line-length - conversationLink: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", - composetime: "2017-01-08T14:33:16.196Z", - isactive: true, - from: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", - type: "Message", - counterpartymessageid: "1483885996189", - version: "1483885996187", - }, - }, - }, - ]; - - for (const item of items) { - it("should format a Control/Typing Message resource", function () { - // tslint:disable-next-line:max-line-length - const actual: resources.ControlTypingResource = formatControlTypingResource(formatGenericMessageResource(item.nativeResource, item.nativeResource.messagetype), item.nativeResource); - const expected: resources.ControlTypingResource = item.expectedFormattedResource; - assert.strictEqual(actual.type, expected.type); - assert.strictEqual(actual.id, expected.id); - assert.strictEqual(actual.composeTime.getTime(), expected.composeTime.getTime()); - assert.strictEqual(actual.arrivalTime.getTime(), expected.arrivalTime.getTime()); - assert.deepEqual(actual.from, expected.from); - assert.strictEqual(actual.conversation, expected.conversation); - assert.deepEqual(actual.native, expected.native); - }); - } -}); - -describe.skip("TODO: Event/Call", function () { - const example: any = { - clientmessageid: "16930058130863214577", - composetime: "2017-01-08T14:49:20.395Z", - messagetype: "Event/Call", - originalarrivaltime: "2017-01-08T14:49:20.395Z", - type: "Message", - version: "1483886960408", - isactive: true, - from: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", - id: "1483886960408", - conversationLink: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", - counterpartymessageid: "1483886960402", - imdisplayname: "Bob", - // tslint:disable-next-line:max-line-length - ackrequired: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483886960408/ack", - // tslint:disable-next-line:max-line-length - content: "\n \n Bob\n \n", - skypeguid: "2ff47f4b-5b79-4076-a1ae-d34d6d89b135", - }; -}); diff --git a/src/test/types/events/skype-event.spec.ts b/src/test/types/events/skype-event.spec.ts new file mode 100644 index 0000000..ce51a21 --- /dev/null +++ b/src/test/types/events/skype-event.spec.ts @@ -0,0 +1,312 @@ +import chai from "chai"; +import { JSON_READER } from "../../../lib/json-reader"; +import { EventResourceType } from "../../../lib/types/events/event-resource-type"; +import { EventType } from "../../../lib/types/events/event-type"; +import { $SkypeEvent, SkypeEvent } from "../../../lib/types/events/skype-event"; +import { MessageType } from "../../../lib/types/resources/message-type"; +import { ResourceType } from "../../../lib/types/resources/resource-type"; +import { UserPresenceAvailability } from "../../../lib/types/resources/user-presence-availability"; +import { UserPresenceStatus } from "../../../lib/types/resources/user-presence-status"; + +describe("Read SkypeEvent", function () { + interface TestItem { + name: string; + input: string; + expected: SkypeEvent; + } + + // tslint:disable:max-line-length + const testItems: TestItem[] = [ + { + name: "Simple UserPresence", + input: `{ + "id":1001, + "type": "EventMessage", + "resourceType": "UserPresence", + "time": "2018-02-08T14:40:10Z", + "resourceLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:demurgos.net/presenceDocs/messagingService", + "resource": { + "id": "messagingService", + "type": "UserPresenceDoc", + "selfLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/presenceDocs/messagingService", + "availability": "Offline", + "status": "Offline", + "capabilities": "Video | Audio", + "lastSeenAt": "2018-02-08T13:13:02.000Z", + "endpointPresenceDocLinks": [ + "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{67725fe0-35a7-49ea-87a3-557301b72cfb}/presenceDocs/messagingService" + ] + } + }`, + expected: { + id: 1001, + type: EventType.EventMessage, + resourceType: EventResourceType.UserPresence, + time: new Date("2018-02-08T14:40:10Z"), + resourceLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:demurgos.net/presenceDocs/messagingService", + resource: { + id: "messagingService", + type: ResourceType.UserPresenceDoc, + selfLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/presenceDocs/messagingService", + availability: UserPresenceAvailability.Offline, + status: UserPresenceStatus.Offline, + capabilities: "Video | Audio", + lastSeenAt: new Date("2018-02-08T13:13:02.000Z"), + endpointPresenceDocLinks: [ + "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{67725fe0-35a7-49ea-87a3-557301b72cfb}/presenceDocs/messagingService", + ], + }, + }, + }, + { + name: "Simple EndpointPresence", + input: `{ + "id": 1002, + "type": "EventMessage", + "resourceType": "EndpointPresence", + "time": "2018-02-08T14:40:10Z", + "resourceLink":"https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{67725fe0-35a7-49ea-87a3-557301b72cfb}/presenceDocs/messagingService", + "resource": { + "id": "messagingService", + "type":"EndpointPresenceDoc", + "selfLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{67725fe0-35a7-49ea-87a3-557301b72cfb}/presenceDocs/messagingService", + "publicInfo": { + "capabilities": "Video | Audio", + "typ": "14", + "skypeNameVersion": "1431/8.13.0.2/SkypeX", + "nodeInfo": "xx", + "version": "15" + }, + "privateInfo": { + } + } + }`, + expected: { + id: 1002, + type: EventType.EventMessage, + resourceType: EventResourceType.EndpointPresence, + time: new Date("2018-02-08T14:40:10Z"), + resourceLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{67725fe0-35a7-49ea-87a3-557301b72cfb}/presenceDocs/messagingService", + resource: { + id: "messagingService", + type: ResourceType.EndpointPresenceDoc, + selfLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{67725fe0-35a7-49ea-87a3-557301b72cfb}/presenceDocs/messagingService", + publicInfo: { + capabilities: "Video | Audio", + typ: "14", + skypeNameVersion: "1431/8.13.0.2/SkypeX", + nodeInfo: "xx", + version: "15", + }, + privateInfo: {}, + }, + }, + }, + { + name: "Own EndpointPresence of the current user", + input: `{ + "id": 1008, + "type": "EventMessage", + "resourceType": "EndpointPresence", + "time": "2018-02-08T14:40:10Z", + "resourceLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/8:bob/endpoints/{22e46ac1-5c0e-465f-927b-c7e50b69c3e2}/presenceDocs/messagingService", + "resource": { + "id": "messagingService", + "type": "EndpointPresenceDoc", + "selfLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/8:bob/endpoints/{22e46ac1-5c0e-465f-927b-c7e50b69c3e2}/presenceDocs/messagingService", + "publicInfo": { + "capabilities": "Video | Audio", + "typ": "", + "skypeNameVersion": "skype.com", + "nodeInfo": "xx", + "version": "908/1.30.0.128//skype.com" + }, + "privateInfo": { + "epname": "skype" + } + } + }`, + expected: { + id: 1008, + type: EventType.EventMessage, + resourceType: EventResourceType.EndpointPresence, + time: new Date("2018-02-08T14:40:10Z"), + resourceLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/8:bob/endpoints/{22e46ac1-5c0e-465f-927b-c7e50b69c3e2}/presenceDocs/messagingService", + resource: { + id: "messagingService", + type: ResourceType.EndpointPresenceDoc, + selfLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/8:bob/endpoints/{22e46ac1-5c0e-465f-927b-c7e50b69c3e2}/presenceDocs/messagingService", + publicInfo: { + capabilities: "Video | Audio", + typ: "", + skypeNameVersion: "skype.com", + nodeInfo: "xx", + version: "908/1.30.0.128//skype.com", + }, + privateInfo: { + epName: "skype", + }, + }, + }, + }, + { + name: "New Control/Typing message", + input: `{ + "id": 1032, + "type": "EventMessage", + "resourceType": "NewMessage", + "time": "2018-02-08T16:51:46Z", + "resourceLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net/messages/1518108706884", + "resource": { + "type": "Message", + "messagetype": "Control/Typing", + "originalarrivaltime": "2018-02-08T16:51:46.808Z", + "version": "1518108706884", + "contenttype": "Application/Message", + "origincontextid": "0", + "isactive": true, + "from": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:demurgos.net", + "id": "1518108706884", + "conversationLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net", + "counterpartymessageid": "1518108706884", + "imdisplayname": "demurgos.net", + "ackrequired": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1518108706884/ack", + "composetime": "2018-02-08T16:51:46.808Z" + } + }`, + expected: { + id: 1032, + type: EventType.EventMessage, + resourceType: EventResourceType.NewMessage, + time: new Date("2018-02-08T16:51:46Z"), + resourceLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net/messages/1518108706884", + resource: { + id: "1518108706884", + type: ResourceType.Message, + messageType: MessageType.ControlTyping, + originalArrivalTime: new Date("2018-02-08T16:51:46.808Z"), + version: "1518108706884", + contentType: "Application/Message", + originContextId: "0", + isActive: true, + from: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:demurgos.net", + conversationLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net", + counterpartyMessageId: "1518108706884", + imDisplayName: "demurgos.net", + ackRequired: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1518108706884/ack", + composeTime: new Date("2018-02-08T16:51:46.808Z"), + }, + }, + }, + { + name: "New Control/ClearTyping message", + input: `{ + "id": 1033, + "type": "EventMessage", + "resourceType": "NewMessage", + "time": "2018-02-08T16:51:48Z", + "resourceLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net/messages/1518108708775", + "resource": { + "type": "Message", + "messagetype": "Control/ClearTyping", + "originalarrivaltime": "2018-02-08T16:51:48.696Z", + "version": "1518108708775", + "contenttype": "Application/Message", + "origincontextid": "0", + "isactive": true, + "from": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:demurgos.net", + "id": "1518108708775", + "conversationLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net", + "counterpartymessageid": "1518108708775", + "imdisplayname": "demurgos.net", + "ackrequired": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1518108708775/ack", + "composetime": "2018-02-08T16:51:48.696Z" + } + }`, + expected: { + id: 1033, + type: EventType.EventMessage, + resourceType: EventResourceType.NewMessage, + time: new Date("2018-02-08T16:51:48Z"), + resourceLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net/messages/1518108708775", + resource: { + id: "1518108708775", + clientMessageId: "30120195694598267", + type: ResourceType.Message, + messageType: MessageType.ControlClearTyping, + originalArrivalTime: new Date("2018-02-08T16:51:48.696Z"), + version: "1518108708775", + contentType: "Application/Message", + originContextId: "0", + isActive: true, + from: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:demurgos.net", + conversationLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net", + counterpartyMessageId: "1518108708775", + imDisplayName: "demurgos.net", + ackRequired: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1518108708775/ack", + composeTime: new Date("2018-02-08T16:51:48.696Z"), + }, + }, + }, + { + name: "New RichText message", + input: `{ + "id": 1034, + "type": "EventMessage", + "resourceType": "NewMessage", + "time": "2018-02-08T16:51:49Z", + "resourceLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net/messages/1518108709179", + "resource": { + "clientmessageid": "30120195694598267", + "type": "Message", + "messagetype": "RichText", + "originalarrivaltime": "2018-02-08T16:51:48.692Z", + "version": "1518108709179", + "contenttype": "text", + "origincontextid": "0", + "isactive": true, + "from": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:demurgos.net", + "id": "1518108709179", + "conversationLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net", + "counterpartymessageid": "1518108709179", + "imdisplayname": "Demurgos", + "ackrequired": "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1518108709179/ack", + "content": "Hello, World!", + "composetime": "2018-02-08T16:51:48.692Z" + } + }`, + expected: { + id: 1034, + type: EventType.EventMessage, + resourceType: EventResourceType.NewMessage, + time: new Date("2018-02-08T16:51:49Z"), + resourceLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net/messages/1518108709179", + resource: { + id: "1518108709179", + clientMessageId: "30120195694598267", + type: ResourceType.Message, + messageType: MessageType.RichText, + originalArrivalTime: new Date("2018-02-08T16:51:48.692Z"), + version: "1518108709179", + contentType: "text", + originContextId: "0", + isActive: true, + from: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:demurgos.net", + conversationLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:demurgos.net", + counterpartyMessageId: "1518108709179", + imDisplayName: "Demurgos", + ackRequired: "https://db4-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1518108709179/ack", + content: "Hello, World!", + composeTime: new Date("2018-02-08T16:51:48.692Z"), + }, + }, + }, + ]; + + for (const item of testItems) { + it(`should read the item: ${item.name}`, function () { + const actual: SkypeEvent = $SkypeEvent.read(JSON_READER, item.input); + chai.assert.isTrue($SkypeEvent.equals(actual, item.expected)); + }); + } +}); diff --git a/src/test/types/resources/control-clear-typing-message.spec.ts b/src/test/types/resources/control-clear-typing-message.spec.ts new file mode 100644 index 0000000..1172864 --- /dev/null +++ b/src/test/types/resources/control-clear-typing-message.spec.ts @@ -0,0 +1,58 @@ +import chai from "chai"; +import { JSON_READER } from "../../../lib/json-reader"; +import { + $ControlClearTypingMessage, + ControlClearTypingMessage, +} from "../../../lib/types/resources/control-clear-typing-message"; +import { MessageType } from "../../../lib/types/resources/message-type"; +import { ResourceType } from "../../../lib/types/resources/resource-type"; + +describe("Read ControlClearTypingMessage", function () { + interface TestItem { + name: string; + input: string; + expected: ControlClearTypingMessage; + } + + // tslint:disable:max-line-length + const testItems: TestItem[] = [ + { + name: "Simple Control/ClearTyping", + input: `{ + "id": "1483879804631", + "ackrequired": "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483879804631/ack", + "originalarrivaltime": "2017-01-08T12:50:04.626Z", + "imdisplayname": "Bob", + "messagetype": "Control/ClearTyping", + "conversationLink": "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", + "composetime": "2017-01-08T12:50:04.626Z", + "isactive": true, + "from": "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", + "type": "Message", + "counterpartymessageid": "1483879804624", + "version": "1483879804631" + }`, + expected: { + id: "1483879804631", + type: ResourceType.Message, + messageType: MessageType.ControlClearTyping, + ackRequired: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483879804631/ack", + originalArrivalTime: new Date("2017-01-08T12:50:04.626Z"), + imDisplayName: "Bob", + conversationLink: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", + composeTime: new Date("2017-01-08T12:50:04.626Z"), + isActive: true, + from: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", + counterpartyMessageId: "1483879804624", + version: "1483879804631", + }, + }, + ]; + + for (const item of testItems) { + it(`should read the item: ${item.name}`, function () { + const actual: ControlClearTypingMessage = $ControlClearTypingMessage.read(JSON_READER, item.input); + chai.assert.isTrue($ControlClearTypingMessage.equals(actual, item.expected)); + }); + } +}); diff --git a/src/test/types/resources/control-typing-message.spec.ts b/src/test/types/resources/control-typing-message.spec.ts new file mode 100644 index 0000000..bd0cedb --- /dev/null +++ b/src/test/types/resources/control-typing-message.spec.ts @@ -0,0 +1,55 @@ +import chai from "chai"; +import { JSON_READER } from "../../../lib/json-reader"; +import { $ControlTypingMessage, ControlTypingMessage } from "../../../lib/types/resources/control-typing-message"; +import { MessageType } from "../../../lib/types/resources/message-type"; +import { ResourceType } from "../../../lib/types/resources/resource-type"; + +describe("Read ControlTypingMessage", function () { + interface TestItem { + name: string; + input: string; + expected: ControlTypingMessage; + } + + // tslint:disable:max-line-length + const testItems: TestItem[] = [ + { + name: "Simple Control/Typing", + input: `{ + "id": "1483885996187", + "ackrequired": "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483885996187/ack", + "originalarrivaltime": "2017-01-08T14:33:16.196Z", + "imdisplayname": "Bob", + "messagetype": "Control/Typing", + "conversationLink": "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", + "composetime": "2017-01-08T14:33:16.196Z", + "isactive": true, + "from": "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", + "type": "Message", + "counterpartymessageid": "1483885996189", + "version": "1483885996187" + }`, + expected: { + id: "1483885996187", + type: ResourceType.Message, + messageType: MessageType.ControlTyping, + ackRequired: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483885996187/ack", + originalArrivalTime: new Date("2017-01-08T14:33:16.196Z"), + imDisplayName: "Bob", + conversationLink: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", + composeTime: new Date("2017-01-08T14:33:16.196Z"), + isActive: true, + from: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", + counterpartyMessageId: "1483885996189", + version: "1483885996187", + }, + }, + ]; + + for (const item of testItems) { + it(`should read the item: ${item.name}`, function () { + const actual: ControlTypingMessage = $ControlTypingMessage.read(JSON_READER, item.input); + chai.assert.isTrue($ControlTypingMessage.equals(actual, item.expected)); + }); + } +}); diff --git a/src/test/types/resources/event-call-message.spec.ts b/src/test/types/resources/event-call-message.spec.ts new file mode 100644 index 0000000..2358086 --- /dev/null +++ b/src/test/types/resources/event-call-message.spec.ts @@ -0,0 +1,64 @@ +import chai from "chai"; +import { JSON_READER } from "../../../lib/json-reader"; +import { + $ControlClearTypingMessage, + ControlClearTypingMessage, +} from "../../../lib/types/resources/control-clear-typing-message"; +import { $EventCallMessage, EventCallMessage } from "../../../lib/types/resources/event-call-message"; +import { MessageType } from "../../../lib/types/resources/message-type"; +import { ResourceType } from "../../../lib/types/resources/resource-type"; + +describe("Read EventCallMessage", function () { + interface TestItem { + name: string; + input: string; + expected: EventCallMessage; + } + + // tslint:disable:max-line-length + const testItems: TestItem[] = [ + { + name: "Simple Event/Call", + input: `{ + "clientmessageid": "16930058130863214577", + "composetime": "2017-01-08T14:49:20.395Z", + "messagetype": "Event/Call", + "originalarrivaltime": "2017-01-08T14:49:20.395Z", + "type": "Message", + "version": "1483886960408", + "isactive": true, + "from": "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", + "id": "1483886960408", + "conversationLink": "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", + "counterpartymessageid": "1483886960402", + "imdisplayname": "Bob", + "ackrequired": "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483886960408/ack", + "content": "\\n \\n Bob\\n \\n", + "skypeguid": "2ff47f4b-5b79-4076-a1ae-d34d6d89b135" + }`, + expected: { + id: "1483886960408", + type: ResourceType.Message, + messageType: MessageType.EventCall, + composeTime: new Date("2017-01-08T14:49:20.395Z"), + originalArrivalTime: new Date("2017-01-08T14:49:20.395Z"), + version: "1483886960408", + isActive: true, + from: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:bob", + conversationLink: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/8:bob", + counterpartyMessageId: "1483886960402", + imDisplayName: "Bob", + ackRequired: "https://db5-client-s.gateway.messenger.live.com/v1/users/ME/conversations/ALL/messages/1483886960408/ack", + content: "\n \n Bob\n \n", + skypeGuid: "2ff47f4b-5b79-4076-a1ae-d34d6d89b135", + }, + }, + ]; + + for (const item of testItems) { + it(`should read the item: ${item.name}`, function () { + const actual: EventCallMessage = $EventCallMessage.read(JSON_READER, item.input); + chai.assert.isTrue($EventCallMessage.equals(actual, item.expected)); + }); + } +}); diff --git a/src/test/types/resources/user-presence-doc-resource.spec.ts b/src/test/types/resources/user-presence-doc-resource.spec.ts new file mode 100644 index 0000000..84db624 --- /dev/null +++ b/src/test/types/resources/user-presence-doc-resource.spec.ts @@ -0,0 +1,107 @@ +import chai from "chai"; +import { JSON_READER } from "../../../lib/json-reader"; +import { ResourceType } from "../../../lib/types/resources/resource-type"; +import { UserPresenceAvailability } from "../../../lib/types/resources/user-presence-availability"; +import { + $UserPresenceDocResource, + UserPresenceDocResource, +} from "../../../lib/types/resources/user-presence-doc-resource"; +import { UserPresenceStatus } from "../../../lib/types/resources/user-presence-status"; + +describe("Read UserPresenceDocResource", function () { + interface TestItem { + name: string; + input: string; + expected: UserPresenceDocResource; + } + + // tslint:disable:max-line-length + const testItems: TestItem[] = [ + { + name: "Idle Online user", + input: `{ + "id": "messagingService", + "type": "UserPresenceDoc", + "selfLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/presenceDocs/messagingService", + "availability": "Online", + "status": "Idle", + "capabilities": "Video | Audio", + "lastSeenAt": "2018-02-08T14:09:33.000Z", + "endpointPresenceDocLinks": [ + "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{a71de238-e491-4d3f-8610-96736bc17961}/presenceDocs/messagingService" + ] + }`, + expected: { + id: "messagingService", + type: ResourceType.UserPresenceDoc, + selfLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/presenceDocs/messagingService", + availability: UserPresenceAvailability.Online, + status: UserPresenceStatus.Idle, + capabilities: "Video | Audio", + lastSeenAt: new Date("2018-02-08T14:09:33.000Z"), + endpointPresenceDocLinks: [ + "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{a71de238-e491-4d3f-8610-96736bc17961}/presenceDocs/messagingService", + ], + }, + }, + { + name: "Busy Online user", + input: `{ + "id": "messagingService", + "type": "UserPresenceDoc", + "selfLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/presenceDocs/messagingService", + "availability": "Online", + "status": "Busy", + "capabilities": "Seamless", + "lastSeenAt": "2018-01-11T18:08:17.000Z", + "endpointPresenceDocLinks": [ + "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{1b6f5cbb-1790-30de-5818-3f199fc053d0}/presenceDocs/messagingService" + ] + }`, + expected: { + id: "messagingService", + type: ResourceType.UserPresenceDoc, + selfLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/presenceDocs/messagingService", + availability: UserPresenceAvailability.Online, + status: UserPresenceStatus.Busy, + capabilities: "Seamless", + lastSeenAt: new Date("2018-01-11T18:08:17.000Z"), + endpointPresenceDocLinks: [ + "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{1b6f5cbb-1790-30de-5818-3f199fc053d0}/presenceDocs/messagingService", + ], + }, + }, + { + name: "Missing lastSeenAt", + input: `{ + "id": "messagingService", + "type": "UserPresenceDoc", + "selfLink": "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/presenceDocs/messagingService", + "availability": "Online", + "status": "Online", + "capabilities": "Video | Audio", + "endpointPresenceDocLinks": [ + "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{67725fe0-35a7-49ea-87a3-557301b72cfb}/presenceDocs/messagingService" + ] + }`, + expected: { + id: "messagingService", + type: ResourceType.UserPresenceDoc, + selfLink: "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/presenceDocs/messagingService", + availability: UserPresenceAvailability.Online, + status: UserPresenceStatus.Online, + capabilities: "Video | Audio", + endpointPresenceDocLinks: [ + "https://db4-client-s.gateway.messenger.live.com/v1/users/8:demurgos.net/endpoints/{67725fe0-35a7-49ea-87a3-557301b72cfb}/presenceDocs/messagingService", + ], + }, + }, + ]; + + for (const item of testItems) { + it(`should read the item: ${item.name}`, function () { + const actual: UserPresenceDocResource = $UserPresenceDocResource.read(JSON_READER, item.input); + chai.assert.isTrue($UserPresenceDocResource.equals(actual, item.expected)); + }); + } +});