diff --git a/.gitignore b/.gitignore index 71221a8..1e16b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ yarn-error.log tests/integration/config/production.js tests/integration/config/staging.js tests/integration/config/development.js +.vscode/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..aaf3c74 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,2 @@ +singleQuote: true +trailingComma: all \ No newline at end of file diff --git a/package.json b/package.json index 12add7f..e23fb44 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "@pusher/chatkit-client", "description": "Pusher Chatkit client SDK for browsers and react native", "main": "dist/web/chatkit.js", + "types": "dist/web/declarations/index.d.ts", "version": "1.5.0", "author": "Pusher", "license": "MIT", @@ -15,6 +16,7 @@ }, "dependencies": { "@pusher/platform": "^0.16.1", + "@types/ramda": "^0.26.9", "ramda": "^0.25.0" }, "devDependencies": { @@ -26,9 +28,6 @@ "babel-preset-env": "^1.6.1", "babelify": "^8.0.0", "browserify": "^15.2.0", - "eslint": "^5.8.0", - "eslint-config-prettier": "^3.1.0", - "eslint-plugin-prettier": "^3.0.0", "prettier": "1.14.3", "publish-please": "^5.2.0", "rollup": "^0.55.3", @@ -37,15 +36,18 @@ "rollup-plugin-commonjs": "^8.3.0", "rollup-plugin-json": "^2.3.0", "rollup-plugin-node-resolve": "^3.0.2", + "rollup-plugin-typescript": "^1.0.1", "rollup-plugin-uglify": "^3.0.0", "snazzy": "^7.0.0", "tap-colorize": "^1.2.0", "tape": "^4.8.0", - "tape-run": "^4.0.0" + "tape-run": "^4.0.0", + "tslint": "^5.8.0", + "typescript": "^3.4.0" }, "scripts": { - "lint": "eslint src tests rollup", - "format": "prettier --write src/**/*.js tests/**/*.js rollup/**/*.js example/**/*.js", + "lint": "tslint -c tslint.json 'src/**/*.ts'", + "format": "prettier --write src/**/*.ts tests/**/*.js rollup/**/*.js example/**/*.js", "build": "yarn build:web && yarn build:react-native", "build:web": "rollup -c rollup/web.js", "build:react-native": "rollup -c rollup/react-native.js", @@ -61,25 +63,5 @@ "prettier": { "semi": false, "trailingComma": "all" - }, - "eslintConfig": { - "extends": [ - "prettier", - "eslint:recommended" - ], - "plugins": [ - "prettier" - ], - "rules": { - "prettier/prettier": "error" - }, - "parserOptions": { - "sourceType": "module", - "ecmaVersion": 2018 - }, - "env": { - "browser": true, - "es6": true - } } } diff --git a/rollup/shared.js b/rollup/shared.js index 7e1ffd4..cb671d3 100644 --- a/rollup/shared.js +++ b/rollup/shared.js @@ -3,6 +3,7 @@ import commonjs from "rollup-plugin-commonjs" import resolve from "rollup-plugin-node-resolve" import uglify from "rollup-plugin-uglify" import json from "rollup-plugin-json" +import typescript from "rollup-plugin-typescript" const pusherPlatformExports = [ "BaseClient", @@ -12,27 +13,17 @@ const pusherPlatformExports = [ ] export default { - input: "src/main.js", + input: "src/main.ts", plugins: [ json(), - babel({ - presets: [ - [ - "env", - { - modules: false, - }, - ], - ], - plugins: ["external-helpers", "transform-object-rest-spread"], - exclude: ["node_modules/**"], - }), resolve(), + typescript({tsconfig: "tsconfig.json"}), commonjs({ namedExports: { "node_modules/@pusher/platform/dist/web/pusher-platform.js": pusherPlatformExports, "node_modules/@pusher/platform/react-native.js": pusherPlatformExports, }, + extensions: ['.js', '.ts'], }), uglify(), ], diff --git a/src/attachment.js b/src/attachment.ts similarity index 61% rename from src/attachment.js rename to src/attachment.ts index 9a9c49e..6c4c03b 100644 --- a/src/attachment.js +++ b/src/attachment.ts @@ -1,5 +1,28 @@ +import { Instance } from "@pusher/platform"; +import { BasicMessagePartPayload } from "./message"; + +export interface AttachmentMessagePartPayload extends BasicMessagePartPayload { + name: string; + size: number; + _id: string; + _downloadURL: string; + _expiration: Date; +} + export class Attachment { - constructor(basicAttachment, roomId, instance) { + + public type: string; + public name: string; + public size: number; + public customData?: any; + + private _id: string; + private _downloadURL: string; + private _expiration: Date; + private _roomId: string; + private _instance: Instance; + + public constructor(basicAttachment: AttachmentMessagePartPayload, roomId: string, instance: Instance) { this.type = basicAttachment.type this.name = basicAttachment.name this.size = basicAttachment.size @@ -20,17 +43,17 @@ export class Attachment { this._fetchNewDownloadURL = this._fetchNewDownloadURL.bind(this) } - url() { + public url() { return this.urlExpiry().getTime() - Date.now() < 1000 * 60 * 30 ? this._fetchNewDownloadURL() : Promise.resolve(this._downloadURL) } - urlExpiry() { + public urlExpiry() { return this._expiration } - _fetchNewDownloadURL() { + private _fetchNewDownloadURL() { return this._instance .request({ method: "GET", @@ -38,7 +61,7 @@ export class Attachment { this._id }`, }) - .then(res => { + .then((res: any) => { const { download_url, expiration } = JSON.parse(res) this._downloadURL = download_url this._expiration = new Date(expiration) diff --git a/src/chat-manager.js b/src/chat-manager.ts similarity index 67% rename from src/chat-manager.js rename to src/chat-manager.ts index fd98a46..09c9690 100644 --- a/src/chat-manager.js +++ b/src/chat-manager.ts @@ -1,40 +1,54 @@ -import { BaseClient, HOST_BASE, Instance } from "@pusher/platform" +import { BaseClient, HOST_BASE, Instance, Logger } from "@pusher/platform" import { split } from "ramda" import { CurrentUser } from "./current-user" -import { typeCheck, typeCheckObj } from "./utils" +import { TokenProvider } from './token-provider' import { DEFAULT_CONNECTION_TIMEOUT } from "./constants" import { version } from "../package.json" export class ChatManager { - constructor({ instanceLocator, tokenProvider, userId, ...options } = {}) { - typeCheck("instanceLocator", "string", instanceLocator) - typeCheck("tokenProvider", "object", tokenProvider) - typeCheck("tokenProvider.fetchToken", "function", tokenProvider.fetchToken) - typeCheck("userId", "string", userId) + + public userId: string; + public connectionTimeout: number; + public currentUser?: CurrentUser; + + public serverInstanceV2: Instance; + public serverInstanceV4: Instance; + public filesInstance: Instance; + public cursorsInstance: Instance; + public presenceInstance: Instance; + + public constructor({instanceLocator, tokenProvider, userId, baseClient, logger, connectionTimeout}: { + instanceLocator: string, + tokenProvider: TokenProvider, + userId: string, + baseClient?: BaseClient, + logger?: Logger, + connectionTimeout?: number, + }) { const cluster = split(":", instanceLocator)[1] if (cluster === undefined) { throw new TypeError( `expected instanceLocator to be of the format x:y:z, but was ${instanceLocator}`, ) } - const baseClient = - options.baseClient || + baseClient = + baseClient || new BaseClient({ host: `${cluster}.${HOST_BASE}`, - logger: options.logger, + logger: logger, sdkProduct: "chatkit", sdkVersion: version, }) - if (typeof tokenProvider.setUserId === "function") { - tokenProvider.setUserId(userId) + if (tokenProvider.setUserId) { + tokenProvider.setUserId(userId); } const instanceOptions = { client: baseClient, locator: instanceLocator, - logger: options.logger, - tokenProvider, + logger: logger, + tokenProvider: tokenProvider, } this.serverInstanceV2 = new Instance({ serviceName: "chatkit", @@ -63,14 +77,13 @@ export class ChatManager { }) this.userId = userId this.connectionTimeout = - options.connectionTimeout || DEFAULT_CONNECTION_TIMEOUT + connectionTimeout || DEFAULT_CONNECTION_TIMEOUT this.connect = this.connect.bind(this) this.disconnect = this.disconnect.bind(this) } - connect(hooks = {}) { - typeCheckObj("hooks", "function", hooks) + public connect(hooks: CurrentUser['hooks']['global'] = {}) { const currentUser = new CurrentUser({ hooks, id: this.userId, @@ -90,7 +103,7 @@ export class ChatManager { }) } - disconnect() { + public disconnect() { if (this.currentUser) this.currentUser.disconnect() } } diff --git a/src/constants.js b/src/constants.ts similarity index 100% rename from src/constants.js rename to src/constants.ts diff --git a/src/current-user.js b/src/current-user.ts similarity index 63% rename from src/current-user.js rename to src/current-user.ts index 3d0b436..b76e70c 100644 --- a/src/current-user.js +++ b/src/current-user.ts @@ -11,15 +11,9 @@ import { values, } from "ramda" -import { sendRawRequest } from "@pusher/platform" +import { sendRawRequest, Instance, Logger } from "@pusher/platform" -import { - checkOneOf, - typeCheck, - typeCheckArr, - typeCheckObj, - urlEncode, -} from "./utils" +import { urlEncode } from "./utils" import { parseBasicMessage, parseBasicRoom } from "./parsers" import { UserStore } from "./user-store" import { RoomStore } from "./room-store" @@ -29,10 +23,69 @@ import { UserSubscription } from "./user-subscription" import { PresenceSubscription } from "./presence-subscription" import { UserPresenceSubscription } from "./user-presence-subscription" import { RoomSubscription } from "./room-subscription" -import { Message } from "./message" +import { Message, BasicMessage, MessagePart } from "./message" import { SET_CURSOR_WAIT } from "./constants" +import { Room, BasicRoom } from "./room"; +import { User, PresenceStore, BasicUser, Presence } from "./user"; +import { Cursor, BasicCursor } from "./cursor"; + +type Callbacks = { resolve: (data?: void) => void, reject: (error?: any) => void }; +type MessageFetchDirection = 'older' | 'newer' export class CurrentUser { + public id: string; + public encodedId: string; + public connectionTimeout: number; + + public avatarURL?: string; + public createdAt?: string; + public customData?: any; + public name?: string; + public updatedAt?: string; + + public serverInstanceV2: Instance; + public serverInstanceV4: Instance; + public filesInstance: Instance; + public cursorsInstance: Instance; + public presenceInstance: Instance; + public logger: Logger; + public presenceStore: PresenceStore; + public userStore: UserStore; + public roomStore: RoomStore; + public cursorStore: CursorStore; + public typingIndicators: TypingIndicators; + public roomSubscriptions: { [roomId: string]: RoomSubscription }; + public readCursorBuffer: { [roomId: string]: { position: number, callbacks: Callbacks[] } }; + public userPresenceSubscriptions: { [userId: string]: UserPresenceSubscription } + public userSubscription?: UserSubscription; + public presenceSubscription?: PresenceSubscription; + + public hooks: { + global: { + onAddedToRoom?: (room: BasicRoom) => void; + onRemovedFromRoom?: (room: BasicRoom) => void; + onRoomUpdated?: (room: BasicRoom) => void; + onRoomDeleted?: (room: BasicRoom) => void; + onNewReadCursor?: (cursor: BasicCursor) => void; + onUserStartedTyping?: (room: Room, user: User) => void; + onUserStoppedTyping?: (room: Room, user: User) => void; + onUserJoinedRoom?: (room: Room, user: User) => void; + onUserLeftRoom?: (room: Room, user: User) => void; + onPresenceChanged?: (state: { current: Presence, previous: Presence }, user: User) => void; + }, + rooms: { + [roomId: string]: { + onMessage?: (data: Message) => any, + onNewReadCursor?: (cursor: Cursor) => void; + onUserJoined?: (user: User) => void; + onUserLeft?: (user: User) => void; + onPresenceChanged?: (state: { current: Presence, previous: Presence }, user: User) => void; + onUserStartedTyping?: (user: User) => void; + onUserStoppedTyping?: (user: User) => void; + } + } + } + constructor({ serverInstanceV2, serverInstanceV4, @@ -42,6 +95,15 @@ export class CurrentUser { hooks, id, presenceInstance, + }: { + serverInstanceV2: Instance; + serverInstanceV4: Instance; + connectionTimeout: number; + cursorsInstance: Instance; + filesInstance: Instance; + hooks: CurrentUser['hooks']['global']; + id: string; + presenceInstance: Instance; }) { this.hooks = { global: hooks, @@ -65,7 +127,7 @@ export class CurrentUser { this.roomStore = new RoomStore({ instance: this.serverInstanceV4, userStore: this.userStore, - isSubscribedTo: userId => this.isSubscribedTo(userId), + isSubscribedTo: (userId: string) => this.isSubscribedTo(userId), logger: this.logger, }) this.cursorStore = new CursorStore({ @@ -119,19 +181,15 @@ export class CurrentUser { this._uploadAttachment = this._uploadAttachment.bind(this) } - /* public */ - - get rooms() { + public get rooms() { return values(this.roomStore.snapshot()) } - get users() { + public get users() { return values(this.userStore.snapshot()) } - setReadCursor({ roomId, position } = {}) { - typeCheck("roomId", "string", roomId) - typeCheck("position", "number", position) + public setReadCursor({roomId, position}: {roomId: string, position: number}): Promise { return new Promise((resolve, reject) => { if (this.readCursorBuffer[roomId] !== undefined) { this.readCursorBuffer[roomId].position = max( @@ -155,9 +213,7 @@ export class CurrentUser { }) } - readCursor({ roomId, userId = this.id } = {}) { - typeCheck("roomId", "string", roomId) - typeCheck("userId", "string", userId) + public readCursor({roomId, userId = this.id} : {roomId: string, userId?: string }) { if (userId !== this.id && !this.isSubscribedTo(roomId)) { const err = new Error( `Must be subscribed to room ${roomId} to access member's read cursors`, @@ -168,35 +224,31 @@ export class CurrentUser { return this.cursorStore.getSync(userId, roomId) } - isTypingIn({ roomId } = {}) { - typeCheck("roomId", "string", roomId) + public isTypingIn({roomId}: {roomId: string}) { return this.typingIndicators.sendThrottledRequest(roomId) } - createRoom({ name, addUserIds, customData, ...rest } = {}) { - name && typeCheck("name", "string", name) - addUserIds && typeCheckArr("addUserIds", "string", addUserIds) - customData && typeCheck("customData", "object", customData) + public createRoom({name, addUserIds, customData, isPrivate}: { name: string, addUserIds: string[], customData?: any, isPrivate?: boolean }) { return this.serverInstanceV4 .request({ method: "POST", path: "/rooms", json: { created_by_id: this.id, - name, - private: !!rest.private, // private is a reserved word in strict mode! + name: name, + private: !!isPrivate, // private is a reserved word in strict mode! user_ids: addUserIds, custom_data: customData, }, }) - .then(res => this.roomStore.set(parseBasicRoom(JSON.parse(res)))) - .catch(err => { + .then((res: any) => this.roomStore.set(parseBasicRoom(JSON.parse(res)))) + .catch((err: any) => { this.logger.warn("error creating room:", err) throw err }) } - getJoinableRooms() { + public getJoinableRooms() { return this.serverInstanceV4 .request({ method: "GET", @@ -208,14 +260,13 @@ export class CurrentUser { map(parseBasicRoom), ), ) - .catch(err => { + .catch((err: any) => { this.logger.warn("error getting joinable rooms:", err) throw err }) } - joinRoom({ roomId } = {}) { - typeCheck("roomId", "string", roomId) + public joinRoom({roomId}: {roomId: string}): Promise { if (this.isMemberOf(roomId)) { return this.roomStore.get(roomId) } @@ -226,15 +277,14 @@ export class CurrentUser { roomId, )}/join`, }) - .then(res => this.roomStore.set(parseBasicRoom(JSON.parse(res)))) - .catch(err => { + .then((res: any) => this.roomStore.set(parseBasicRoom(JSON.parse(res)))) + .catch((err: any) => { this.logger.warn(`error joining room ${roomId}:`, err) throw err }) } - leaveRoom({ roomId } = {}) { - typeCheck("roomId", "string", roomId) + public leaveRoom({roomId}: {roomId: string}) { return this.roomStore .get(roomId) .then(room => @@ -253,9 +303,7 @@ export class CurrentUser { }) } - addUserToRoom({ userId, roomId } = {}) { - typeCheck("userId", "string", userId) - typeCheck("roomId", "string", roomId) + public addUserToRoom({userId, roomId}: {userId: string, roomId: string}) { return this.serverInstanceV4 .request({ method: "PUT", @@ -265,15 +313,13 @@ export class CurrentUser { }, }) .then(() => this.roomStore.addUserToRoom(roomId, userId)) - .catch(err => { + .catch((err: any) => { this.logger.warn(`error adding user ${userId} to room ${roomId}:`, err) throw err }) } - removeUserFromRoom({ userId, roomId } = {}) { - typeCheck("userId", "string", userId) - typeCheck("roomId", "string", roomId) + public removeUserFromRoom({userId, roomId}: {userId: string, roomId: string}) { return this.serverInstanceV4 .request({ method: "PUT", @@ -283,7 +329,7 @@ export class CurrentUser { }, }) .then(() => this.roomStore.removeUserFromRoom(roomId, userId)) - .catch(err => { + .catch((err: any) => { this.logger.warn( `error removing user ${userId} from room ${roomId}:`, err, @@ -292,15 +338,22 @@ export class CurrentUser { }) } - sendMessage({ text, roomId, attachment } = {}) { - typeCheck("text", "string", text) - typeCheck("roomId", "string", roomId) + public sendMessage({text, roomId, attachment}: { text: string, roomId: string, + attachment?: { + file?: File, + link?: string, + type?: 'image' | 'video' | 'audio' | 'file' , + name?: string + }}): Promise { return new Promise((resolve, reject) => { - if (attachment !== undefined && isDataAttachment(attachment)) { - resolve(this.uploadDataAttachment(roomId, attachment)) - } else if (attachment !== undefined && isLinkAttachment(attachment)) { + if (attachment && isDataAttachment(attachment)) { + resolve(this.uploadDataAttachment(roomId, { + file: attachment.file!, + name: attachment.name! + })) + } else if (attachment && isLinkAttachment(attachment)) { resolve({ resource_link: attachment.link, type: attachment.type }) - } else if (attachment !== undefined) { + } else if (attachment) { reject(new TypeError("attachment was malformed")) } else { resolve() @@ -325,16 +378,21 @@ export class CurrentUser { }) } - sendSimpleMessage({ roomId, text } = {}) { + public sendSimpleMessage({roomId, text}: {roomId: string, text: string}) { return this.sendMultipartMessage({ roomId, parts: [{ type: "text/plain", content: text }], }) } - sendMultipartMessage({ roomId, parts } = {}) { - typeCheck("roomId", "string", roomId) - typeCheckArr("parts", "object", parts) + public sendMultipartMessage({roomId, parts}: {roomId: string, parts: { + type: string; + content?: string; + url?: string; + customData?: any; + file?: File; + name?: string; + }[]}) { if (parts.length === 0) { return Promise.reject( new TypeError("message must contain at least one part"), @@ -342,13 +400,13 @@ export class CurrentUser { } return Promise.all( parts.map(part => { - part.type = part.type || (part.file && part.file.type) - typeCheck("part.type", "string", part.type) - part.content && typeCheck("part.content", "string", part.content) - part.url && typeCheck("part.url", "string", part.url) - part.name && typeCheck("part.name", "string", part.name) - part.file && typeCheck("part.file.size", "number", part.file.size) - return part.file ? this._uploadAttachment({ roomId, part }) : part + part.type = part.type || (part.file && part.file.type) || '' + return part.file ? this._uploadAttachment({roomId, part: { + type: part.type!, + name: part.name, + customData: part.customData, + file: part.file! + }}) : new Promise(resolve => resolve(part)) }), ) .then(parts => @@ -356,7 +414,7 @@ export class CurrentUser { method: "POST", path: `/rooms/${encodeURIComponent(roomId)}/messages`, json: { - parts: parts.map(({ type, content, url, attachment }) => ({ + parts: parts.map(({ type, content, url, attachment }: any) => ({ type, content, url, @@ -372,49 +430,60 @@ export class CurrentUser { }) } - fetchMessages({ roomId, initialId, limit, direction, serverInstance } = {}) { - typeCheck("roomId", "string", roomId) - initialId && typeCheck("initialId", "number", initialId) - limit && typeCheck("limit", "number", limit) - direction && checkOneOf("direction", ["older", "newer"], direction) + public fetchMessages({roomId, initialId, limit, direction, serverInstance}: { + roomId: string, + initialId?: number, + limit?: number, + direction?: MessageFetchDirection, + serverInstance?: Instance + }): Promise { return (serverInstance || this.serverInstanceV2) .request({ method: "GET", path: `/rooms/${encodeURIComponent(roomId)}/messages?${urlEncode({ initial_id: initialId, - limit, - direction, + limit: limit, + direction: direction, })}`, }) - .then(res => { - const messages = JSON.parse(res).map(m => + .then((res: any) => { + const messages = JSON.parse(res).map((m: any) => this.decorateMessage(parseBasicMessage(m)), ) return this.userStore .fetchMissingUsers(uniq(map(prop("senderId"), messages))) - .then(() => sort((x, y) => x.id - y.id, messages)) + .then(() => sort((x: any, y: any) => x.id - y.id, messages)) }) - .catch(err => { + .catch((err: any) => { this.logger.warn(`error fetching messages from room ${roomId}:`, err) throw err }) } - fetchMultipartMessages(options = {}) { - return this.fetchMessages({ - ...options, - serverInstance: this.serverInstanceV4, - }) - } - - subscribeToRoom({ roomId, hooks = {}, messageLimit, serverInstance } = {}) { - typeCheck("roomId", "string", roomId) - typeCheckObj("hooks", "function", hooks) - messageLimit && typeCheck("messageLimit", "number", messageLimit) + public fetchMultipartMessages(options: { + roomId: string, + initialId?: number, + limit?: number, + direction?: MessageFetchDirection + }) { + return this.fetchMessages({...options, serverInstance: this.serverInstanceV4}) + } + + public subscribeToRoom({roomId, hooks = {}, messageLimit, serverInstance}: { + roomId: string, + hooks?: { + onMessage?: (data: Message) => any, + onNewReadCursor?: (cursor: Cursor) => void; + onUserJoined?: (user: User) => void; + onUserLeft?: (user: User) => void; + }, + messageLimit?: number, + serverInstance?: Instance + }) { if (this.roomSubscriptions[roomId]) { this.roomSubscriptions[roomId].cancel() } - this.hooks.rooms[roomId] = hooks + this.hooks.rooms[roomId] = hooks || {} const roomSubscription = new RoomSubscription({ serverInstance: serverInstance || this.serverInstanceV2, connectionTimeout: this.connectionTimeout, @@ -422,41 +491,51 @@ export class CurrentUser { cursorsInstance: this.cursorsInstance, hooks: this.hooks, logger: this.logger, - messageLimit, - roomId, + messageLimit: messageLimit, + roomId: roomId, roomStore: this.roomStore, typingIndicators: this.typingIndicators, userId: this.id, userStore: this.userStore, }) this.roomSubscriptions[roomId] = roomSubscription - return this.joinRoom({ roomId }) + return this.joinRoom({roomId}) .then(room => roomSubscription.connect().then(() => room)) - .catch(err => { + .catch((err: any) => { this.logger.warn(`error subscribing to room ${roomId}:`, err) throw err }) } - subscribeToRoomMultipart(options = {}) { + public subscribeToRoomMultipart(options: { + roomId: string, + hooks?: { + onMessage?: (data: Message) => any, + onNewReadCursor?: (cursor: Cursor) => void; + onUserJoined?: (user: User) => void; + onUserLeft?: (user: User) => void; + }, + messageLimit?: number, + }) { return this.subscribeToRoom({ ...options, serverInstance: this.serverInstanceV4, }) } - updateRoom({ roomId, name, customData, ...rest } = {}) { - typeCheck("roomId", "string", roomId) - name && typeCheck("name", "string", name) - rest.private && typeCheck("private", "boolean", rest.private) - customData && typeCheck("customData", "object", customData) + public updateRoom({roomId, name, customData, isPrivate}: { + roomId: string, + name: string, + customData?: any, + isPrivate: boolean, + }) { return this.serverInstanceV4 .request({ method: "PUT", path: `/rooms/${encodeURIComponent(roomId)}`, json: { name, - private: rest.private, // private is a reserved word in strict mode! + private: isPrivate, custom_data: customData, }, }) @@ -467,8 +546,7 @@ export class CurrentUser { }) } - deleteRoom({ roomId } = {}) { - typeCheck("roomId", "string", roomId) + public deleteRoom({roomId}: {roomId: string}) { return this.serverInstanceV4 .request({ method: "DELETE", @@ -481,16 +559,18 @@ export class CurrentUser { }) } - /* internal */ - - setReadCursorRequest({ roomId, position, callbacks }) { + private setReadCursorRequest({roomId, position, callbacks}: { + roomId: string, + position: number, + callbacks: Callbacks[], + }) { return this.cursorsInstance .request({ method: "PUT", path: `/cursors/0/rooms/${encodeURIComponent(roomId)}/users/${ this.encodedId }`, - json: { position }, + json: { position: position }, }) .then(() => map(x => x.resolve(), callbacks)) .catch(err => { @@ -499,7 +579,7 @@ export class CurrentUser { }) } - uploadDataAttachment(roomId, { file, name }) { + private uploadDataAttachment(roomId: string, {file, name}: { file: File, name: string }) { // TODO polyfill FormData? const body = new FormData() // eslint-disable-line no-undef body.append("file", file, name) @@ -514,17 +594,22 @@ export class CurrentUser { .then(JSON.parse) } - _uploadAttachment({ roomId, part: { type, name, customData, file } }) { + private _uploadAttachment({roomId, part}: {roomId: string; part: { + type: string, + name?: string, + customData?: any, + file: File + }}) { return this.serverInstanceV4 .request({ method: "POST", path: `/rooms/${encodeURIComponent(roomId)}/attachments`, json: { - content_type: type, - content_length: file.size, + content_type: part.type, + content_length: part.file.size, origin: window && window.location && window.location.origin, - name: name || file.name, - custom_data: customData, + name: part.name || part.file.name, + custom_data: part.customData, }, }) .then(res => { @@ -535,23 +620,23 @@ export class CurrentUser { return sendRawRequest({ method: "PUT", url: uploadURL, - body: file, + body: part.file, headers: { - "content-type": type, + "content-type": part.type, }, - }).then(() => ({ type, attachment: { id: attachmentId } })) + }).then(() => ({ type: part.type, attachment: { id: attachmentId } })) }) } - isMemberOf(roomId) { - return contains(roomId, map(prop("id"), this.rooms)) + private isMemberOf(roomId: string) { + return contains(roomId, map(prop("id"), this.rooms)) } - isSubscribedTo(roomId) { + private isSubscribedTo(roomId: string) { return has(roomId, this.roomSubscriptions) } - decorateMessage(basicMessage) { + private decorateMessage(basicMessage: BasicMessage) { return new Message( basicMessage, this.userStore, @@ -560,7 +645,7 @@ export class CurrentUser { ) } - setPropertiesFromBasicUser(basicUser) { + public setPropertiesFromBasicUser(basicUser: BasicUser) { this.avatarURL = basicUser.avatarURL this.createdAt = basicUser.createdAt this.customData = basicUser.customData @@ -568,7 +653,7 @@ export class CurrentUser { this.updatedAt = basicUser.updatedAt } - establishUserSubscription() { + public establishUserSubscription() { this.userSubscription = new UserSubscription({ hooks: this.hooks, userId: this.id, @@ -580,11 +665,11 @@ export class CurrentUser { connectionTimeout: this.connectionTimeout, currentUser: this, }) - return this.userSubscription + return this.userSubscription! .connect() - .then(({ basicUser, basicRooms, basicCursors }) => { + .then(({basicUser, basicRooms, basicCursors}) => { this.setPropertiesFromBasicUser(basicUser) - return Promise.all([ + return Promise.all<(Room | Cursor)>([ ...basicRooms.map(basicRoom => this.roomStore.set(basicRoom)), ...basicCursors.map(basicCursor => this.cursorStore.set(basicCursor)), ]) @@ -595,7 +680,7 @@ export class CurrentUser { }) } - establishPresenceSubscription() { + public establishPresenceSubscription() { this.presenceSubscription = new PresenceSubscription({ userId: this.id, instance: this.presenceInstance, @@ -613,7 +698,7 @@ export class CurrentUser { ]) } - subscribeToUserPresence(userId) { + private subscribeToUserPresence(userId: string) { if (this.userPresenceSubscriptions[userId]) { return Promise.resolve() } @@ -633,28 +718,24 @@ export class CurrentUser { return userPresenceSub.connect() } - disconnect() { - this.userSubscription.cancel() - this.presenceSubscription.cancel() + public disconnect() { + this.userSubscription && this.userSubscription.cancel() + this.presenceSubscription && this.presenceSubscription.cancel() forEachObjIndexed(sub => sub.cancel(), this.roomSubscriptions) forEachObjIndexed(sub => sub.cancel(), this.userPresenceSubscriptions) } } -const isDataAttachment = ({ file, name }) => { +const isDataAttachment = ({file, name}: {file?: File, name?: string}) => { if (file === undefined || name === undefined) { return false } - typeCheck("attachment.file", "object", file) - typeCheck("attachment.name", "string", name) return true } -const isLinkAttachment = ({ link, type }) => { +const isLinkAttachment = ({link, type}: {link?: string, type?: string}) => { if (link === undefined || type === undefined) { return false } - typeCheck("attachment.link", "string", link) - typeCheck("attachment.type", "string", type) return true } diff --git a/src/cursor-store.js b/src/cursor-store.ts similarity index 59% rename from src/cursor-store.js rename to src/cursor-store.ts index 11efae0..37cbeb3 100644 --- a/src/cursor-store.js +++ b/src/cursor-store.ts @@ -1,8 +1,23 @@ -import { Cursor } from "./cursor" +import { Cursor, BasicCursor } from "./cursor" import { parseBasicCursor } from "./parsers" +import { Instance, Logger } from "@pusher/platform"; +import { UserStore } from "./user-store"; +import { RoomStore } from "./room-store"; export class CursorStore { - constructor({ instance, userStore, roomStore, logger }) { + + public instance: Instance; + public userStore: UserStore; + public roomStore: RoomStore; + public logger: Logger; + public cursors: { [key: string]: Cursor }; + + public constructor({instance, userStore, roomStore, logger}: { + instance: Instance, + userStore: UserStore, + roomStore: RoomStore, + logger: Logger, + }) { this.instance = instance this.userStore = userStore this.roomStore = roomStore @@ -16,29 +31,30 @@ export class CursorStore { this.decorate = this.decorate.bind(this) } - set(basicCursor) { + public set(basicCursor: BasicCursor) { const k = key(basicCursor.userId, basicCursor.roomId) - this.cursors[k] = this.decorate(basicCursor) - return this.userStore + const cursor = this.decorate(basicCursor); + cursor && (this.cursors[k] = cursor) + return this.userStore! .fetchMissingUsers([basicCursor.userId]) .then(() => this.cursors[k]) } - get(userId, roomId) { + public get(userId: string, roomId: string) { const k = key(userId, roomId) if (this.cursors[k]) { return Promise.resolve(this.cursors[k]) } - return this.fetchBasicCursor(userId, roomId).then(basicCursor => - this.set(basicCursor), - ) + return this.fetchBasicCursor(userId, roomId).then(basicCursor => { + basicCursor && this.set(basicCursor) + }) } - getSync(userId, roomId) { + public getSync(userId: string, roomId: string) { return this.cursors[key(userId, roomId)] } - fetchBasicCursor(userId, roomId) { + public fetchBasicCursor(userId: string, roomId: string) { return this.instance .request({ method: "GET", @@ -59,12 +75,12 @@ export class CursorStore { }) } - decorate(basicCursor) { + public decorate(basicCursor: BasicCursor) { return basicCursor ? new Cursor(basicCursor, this.userStore, this.roomStore) : undefined } } -const key = (userId, roomId) => +const key = (userId: string, roomId: string) => `${encodeURIComponent(userId)}/${encodeURIComponent(roomId)}` diff --git a/src/cursor-subscription.js b/src/cursor-subscription.ts similarity index 63% rename from src/cursor-subscription.js rename to src/cursor-subscription.ts index fa57013..bb861bc 100644 --- a/src/cursor-subscription.js +++ b/src/cursor-subscription.ts @@ -1,8 +1,29 @@ import { parseBasicCursor } from "./parsers" import { handleCursorSubReconnection } from "./reconnection-handlers" +import { CursorStore } from "./cursor-store"; +import { Instance, Logger, Subscription } from "@pusher/platform"; +import { Cursor, BasicCursor } from "./cursor"; export class CursorSubscription { - constructor(options) { + private roomId: string; + private cursorStore: CursorStore; + private instance: Instance; + private logger: Logger; + private connectionTimeout: number; + private timeout?: NodeJS.Timeout; + public established: boolean = false; + private onNewCursorHook: (cursor: BasicCursor) => void; + private sub?: Subscription; + private onSubscriptionEstablished?: (cursor: Cursor[]) => void; + + public constructor(options: { + onNewCursorHook: (cursor: BasicCursor) => void, + roomId: string, + cursorStore: CursorStore, + instance: Instance, + logger: Logger, + connectionTimeout: number, + }) { this.onNewCursorHook = options.onNewCursorHook this.roomId = options.roomId this.cursorStore = options.cursorStore @@ -17,20 +38,20 @@ export class CursorSubscription { this.onNewCursor = this.onNewCursor.bind(this) } - connect() { + public connect(): Promise { return new Promise((resolve, reject) => { this.timeout = setTimeout(() => { reject(new Error("cursor subscription timed out")) }, this.connectionTimeout) this.onSubscriptionEstablished = initialState => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) resolve(initialState) } this.sub = this.instance.subscribeNonResuming({ path: `/cursors/0/rooms/${encodeURIComponent(this.roomId)}`, listeners: { onError: err => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) reject(err) }, onEvent: this.onEvent, @@ -39,8 +60,8 @@ export class CursorSubscription { }) } - cancel() { - clearTimeout(this.timeout) + public cancel() { + this.timeout && clearTimeout(this.timeout) try { this.sub && this.sub.unsubscribe() } catch (err) { @@ -48,7 +69,7 @@ export class CursorSubscription { } } - onEvent({ body }) { + private onEvent({body}: {body: any}) { switch (body.event_name) { case "initial_state": this.onInitialState(body.data) @@ -59,7 +80,7 @@ export class CursorSubscription { } } - onInitialState({ cursors }) { + private onInitialState({cursors}: {cursors: BasicCursor[]}) { const basicCursors = cursors.map(c => parseBasicCursor(c)) if (!this.established) { @@ -76,9 +97,9 @@ export class CursorSubscription { } } - onNewCursor(data) { + private onNewCursor(data: any) { return this.cursorStore .set(parseBasicCursor(data)) - .then(cursor => this.onNewCursorHook(cursor)) + .then((cursor: BasicCursor) => this.onNewCursorHook(cursor)) } } diff --git a/src/cursor.js b/src/cursor.js deleted file mode 100644 index 08b699f..0000000 --- a/src/cursor.js +++ /dev/null @@ -1,19 +0,0 @@ -export class Cursor { - constructor(basicCursor, userStore, roomStore) { - this.position = basicCursor.position - this.updatedAt = basicCursor.updatedAt - this.userId = basicCursor.userId - this.roomId = basicCursor.roomId - this.type = basicCursor.type - this.userStore = userStore - this.roomStore = roomStore - } - - get user() { - return this.userStore.getSync(this.userId) - } - - get room() { - return this.roomStore.getSync(this.roomId) - } -} diff --git a/src/cursor.ts b/src/cursor.ts new file mode 100644 index 0000000..28740f7 --- /dev/null +++ b/src/cursor.ts @@ -0,0 +1,39 @@ +import { UserStore } from "./user-store"; +import { RoomStore } from "./room-store"; + +export interface BasicCursor { + position: number; + updatedAt: string; + userId: string; + roomId: string; + type: 0; +} + +export class Cursor implements BasicCursor { + public position: number; + public updatedAt: string; + public userId: string; + public roomId: string; + public type: 0; + + public userStore: UserStore; + public roomStore: RoomStore; + + constructor(basicCursor: BasicCursor, userStore: UserStore, roomStore: RoomStore) { + this.position = basicCursor.position + this.updatedAt = basicCursor.updatedAt + this.userId = basicCursor.userId + this.roomId = basicCursor.roomId + this.type = basicCursor.type + this.userStore = userStore + this.roomStore = roomStore + } + + get user() { + return this.userStore.getSync(this.userId) + } + + get room() { + return this.roomStore.getSync(this.roomId) + } +} diff --git a/src/main.js b/src/main.ts similarity index 100% rename from src/main.js rename to src/main.ts diff --git a/src/membership-subscription.js b/src/membership-subscription.ts similarity index 61% rename from src/membership-subscription.js rename to src/membership-subscription.ts index 39b01ce..0659ebb 100644 --- a/src/membership-subscription.js +++ b/src/membership-subscription.ts @@ -1,7 +1,34 @@ import { handleMembershipSubReconnection } from "./reconnection-handlers" +import { UserStore } from "./user-store"; +import { RoomStore } from "./room-store"; +import { Instance, Logger, Subscription } from "@pusher/platform"; +import { Room } from "./room"; +import { User } from "./user"; export class MembershipSubscription { - constructor(options) { + private roomId: string; + private userStore: UserStore; + private roomStore: RoomStore; + private instance: Instance; + private logger: Logger; + private connectionTimeout: number; + private onUserJoinedRoomHook: (room: Room, user: User) => void; + private onUserLeftRoomHook: (room: Room, user: User) => void; + private timeout?: NodeJS.Timeout; + public established?: boolean; + private sub?: Subscription; + private onSubscriptionEstablished?: () => void; + + public constructor(options: { + roomId: string; + instance: Instance; + userStore: UserStore; + roomStore: RoomStore; + logger: Logger; + connectionTimeout: number; + onUserJoinedRoomHook: (room: Room, user: User) => void; + onUserLeftRoomHook: (room: Room, user: User) => void; + }) { this.roomId = options.roomId this.instance = options.instance this.userStore = options.userStore @@ -19,20 +46,20 @@ export class MembershipSubscription { this.onUserLeft = this.onUserLeft.bind(this) } - connect() { + public connect(): Promise { return new Promise((resolve, reject) => { this.timeout = setTimeout(() => { reject(new Error("membership subscription timed out")) }, this.connectionTimeout) - this.onSubscriptionEstablished = initialState => { - clearTimeout(this.timeout) - resolve(initialState) + this.onSubscriptionEstablished = () => { + this.timeout && clearTimeout(this.timeout) + resolve() } this.sub = this.instance.subscribeNonResuming({ path: `/rooms/${encodeURIComponent(this.roomId)}/memberships`, listeners: { onError: err => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) reject(err) }, onEvent: this.onEvent, @@ -41,8 +68,8 @@ export class MembershipSubscription { }) } - cancel() { - clearTimeout(this.timeout) + public cancel() { + this.timeout && clearTimeout(this.timeout) try { this.sub && this.sub.unsubscribe() } catch (err) { @@ -50,7 +77,7 @@ export class MembershipSubscription { } } - onEvent({ body }) { + private onEvent({body}: {body: any}) { switch (body.event_name) { case "initial_state": this.onInitialState(body.data) @@ -64,11 +91,11 @@ export class MembershipSubscription { } } - onInitialState({ user_ids: userIds }) { + private onInitialState({userIds}: {userIds: string[]}) { if (!this.established) { this.established = true this.roomStore.update(this.roomId, { userIds }).then(() => { - this.onSubscriptionEstablished() + this.onSubscriptionEstablished && this.onSubscriptionEstablished() }) } else { handleMembershipSubReconnection({ @@ -82,7 +109,7 @@ export class MembershipSubscription { } } - onUserJoined({ user_id: userId }) { + private onUserJoined({user_id: userId}: {user_id: string}) { this.roomStore .addUserToRoom(this.roomId, userId) .then(room => @@ -92,7 +119,7 @@ export class MembershipSubscription { ) } - onUserLeft({ user_id: userId }) { + private onUserLeft({user_id: userId}: {user_id: string}) { this.roomStore .removeUserFromRoom(this.roomId, userId) .then(room => diff --git a/src/message-subscription.js b/src/message-subscription.ts similarity index 62% rename from src/message-subscription.js rename to src/message-subscription.ts index e4ba190..b67c02e 100644 --- a/src/message-subscription.js +++ b/src/message-subscription.ts @@ -1,9 +1,40 @@ import { parseBasicMessage } from "./parsers" import { urlEncode } from "./utils" -import { Message } from "./message" +import { Message, BasicMessage } from "./message" +import { UserStore } from "./user-store"; +import { RoomStore } from "./room-store"; +import { Instance, Logger, Subscription } from "@pusher/platform"; +import { TypingIndicators } from "./typing-indicators"; export class MessageSubscription { - constructor(options) { + private roomId: string; + private messageLimit?: number; + private userId: string; + private userStore: UserStore; + private roomStore: RoomStore; + private typingIndicators: TypingIndicators; + private instance: Instance; + private logger: Logger; + private connectionTimeout: number; + private messageBuffer: { message: Message, ready: boolean }[]; + private onMessageHook: (message: Message) => void; + + private timeout?: NodeJS.Timeout; + public established?: boolean; + private sub?: Subscription; + + public constructor(options: { + roomId: string; + messageLimit?: number; + userId: string; + instance: Instance; + userStore: UserStore; + roomStore: RoomStore; + typingIndicators: TypingIndicators; + logger: Logger; + connectionTimeout: number; + onMessageHook: (message: Message) => void; + }) { this.roomId = options.roomId this.messageLimit = options.messageLimit this.userId = options.userId @@ -24,7 +55,7 @@ export class MessageSubscription { this.onIsTyping = this.onIsTyping.bind(this) } - connect() { + public connect(): Promise { return new Promise((resolve, reject) => { this.timeout = setTimeout(() => { reject(new Error("message subscription timed out")) @@ -35,11 +66,11 @@ export class MessageSubscription { })}`, listeners: { onOpen: () => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) resolve() }, onError: err => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) reject(err) }, onEvent: this.onEvent, @@ -48,8 +79,8 @@ export class MessageSubscription { }) } - cancel() { - clearTimeout(this.timeout) + public cancel() { + this.timeout && clearTimeout(this.timeout) try { this.sub && this.sub.unsubscribe() } catch (err) { @@ -57,7 +88,7 @@ export class MessageSubscription { } } - onEvent({ body }) { + private onEvent({body}: {body: any}) { switch (body.event_name) { case "new_message": this.onMessage(body.data) @@ -68,7 +99,7 @@ export class MessageSubscription { } } - onMessage(data) { + private onMessage(data: any) { const pending = { message: new Message( parseBasicMessage(data), @@ -90,13 +121,14 @@ export class MessageSubscription { }) } - flushBuffer() { + private flushBuffer() { while (this.messageBuffer.length > 0 && this.messageBuffer[0].ready) { - this.onMessageHook(this.messageBuffer.shift().message) + const first = this.messageBuffer.shift(); + first && this.onMessageHook(first.message) } } - onIsTyping({ user_id: userId }) { + private onIsTyping({user_id: userId}: {user_id: string}) { if (userId !== this.userId) { Promise.all([ this.roomStore.get(this.roomId), diff --git a/src/message.js b/src/message.js deleted file mode 100644 index 2cc9708..0000000 --- a/src/message.js +++ /dev/null @@ -1,41 +0,0 @@ -import { Attachment } from "./attachment" - -export class Message { - constructor(basicMessage, userStore, roomStore, instance) { - this.id = basicMessage.id - this.senderId = basicMessage.senderId - this.roomId = basicMessage.roomId - this.createdAt = basicMessage.createdAt - this.updatedAt = basicMessage.updatedAt - - if (basicMessage.parts) { - // v3 message - this.parts = basicMessage.parts.map( - ({ partType, payload }) => - partType === "attachment" - ? { - partType, - payload: new Attachment(payload, this.roomId, instance), - } - : { partType, payload }, - ) - } else { - // v2 message - this.text = basicMessage.text - if (basicMessage.attachment) { - this.attachment = basicMessage.attachment - } - } - - this.userStore = userStore - this.roomStore = roomStore - } - - get sender() { - return this.userStore.getSync(this.senderId) - } - - get room() { - return this.roomStore.getSync(this.roomId) - } -} diff --git a/src/message.ts b/src/message.ts new file mode 100644 index 0000000..3f48e21 --- /dev/null +++ b/src/message.ts @@ -0,0 +1,104 @@ +import { Attachment, AttachmentMessagePartPayload } from "./attachment" +import { UserStore } from "./user-store"; +import { RoomStore } from "./room-store"; +import { Instance } from "@pusher/platform"; + +export interface BasicMessagePartPayload { + type: string, + content?: string, + url?: string, + customData?: any, + file?: File, + name?: string, + size?: number, + _id?: string, + _downloadURL?: string, + _expiration?: Date +} + +export interface BasicMessagePart { + partType: 'inline' | 'url' | 'attachment', + payload: AttachmentMessagePartPayload | BasicMessagePartPayload +} + +export interface MessagePart { + partType: 'inline' | 'url' | 'attachment', + payload: Attachment | BasicMessagePartPayload +} + +export interface BasicMessage { + id: number; + senderId: string; + roomId: string; + createdAt: string; + updatedAt: string; + parts?: BasicMessagePart[]; + + /** + * @deprecated Old (v2) field. Use message.parts instead. + */ + text?: string; + /** + * @deprecated Old (v2) field. Use message.parts instead. + */ + attachment?: { link: string, type: string, name: string }; +} + +export class Message { + public id: number; + public senderId: string; + public roomId: string; + public createdAt: string; + public updatedAt: string; + public parts: MessagePart[] = []; + private userStore: UserStore; + private roomStore: RoomStore; + + /** + * @deprecated Old (v2) field. Use message.parts instead. + */ + public text?: string; + /** + * @deprecated Old (v2) field. Use message.parts instead. + */ + public attachment?: { link: string, type: string, name: string }; + + + public constructor(basicMessage: BasicMessage, userStore: UserStore, roomStore: RoomStore, instance: Instance) { + this.id = basicMessage.id + this.senderId = basicMessage.senderId + this.roomId = basicMessage.roomId + this.createdAt = basicMessage.createdAt + this.updatedAt = basicMessage.updatedAt + + if (basicMessage.parts) { + // v3 message + this.parts = basicMessage.parts.map( + ({ partType, payload }) => + partType === "attachment" + ? { + partType, + payload: new Attachment(payload as AttachmentMessagePartPayload, this.roomId, instance), + } + : { partType, payload }, + ) + } else { + // v2 message + this.text = basicMessage.text + if (basicMessage.attachment) { + this.attachment = basicMessage.attachment + } + } + + this.userStore = userStore + this.roomStore = roomStore + } + + public get sender() { + return this.userStore.getSync(this.senderId) + } + + public get room() { + return this.roomStore.getSync(this.roomId) + } +} diff --git a/src/parsers.js b/src/parsers.ts similarity index 68% rename from src/parsers.js rename to src/parsers.ts index 4c83671..b609af9 100644 --- a/src/parsers.js +++ b/src/parsers.ts @@ -1,4 +1,9 @@ -export const parseBasicRoom = data => ({ +import { BasicCursor } from "./cursor"; +import { BasicMessage, MessagePart, BasicMessagePart } from "./message"; +import { BasicRoom } from "./room"; +import { BasicUser, Presence } from "./user"; + +export const parseBasicRoom = (data: any): BasicRoom => ({ createdAt: data.created_at, createdByUserId: data.created_by_id, id: data.id, @@ -11,7 +16,7 @@ export const parseBasicRoom = data => ({ lastMessageAt: data.last_message_at, }) -export const parseBasicUser = data => ({ +export const parseBasicUser = (data: any): BasicUser => ({ avatarURL: data.avatar_url, createdAt: data.created_at, customData: data.custom_data, @@ -20,14 +25,14 @@ export const parseBasicUser = data => ({ updatedAt: data.updated_at, }) -export const parsePresence = data => ({ - state: ["online", "offline"].includes(data.state) ? data.state : "unknown", +export const parsePresence = (data: any): { state: Presence } => ({ + state: ["online", "offline"].indexOf(data.state) ? data.state : "unknown", }) -export const parseBasicMessage = data => { +export const parseBasicMessage = (data: any): BasicMessage => { const roomId = data.room_id - const basicMessage = { + const basicMessage: BasicMessage = { roomId, id: data.id, senderId: data.user_id, @@ -37,7 +42,7 @@ export const parseBasicMessage = data => { if (data.parts) { // v3 message - basicMessage.parts = data.parts.map(p => parseMessagePart(p)) + basicMessage.parts = data.parts.map((p: any) => parseMessagePart(p)) } else { // v2 message basicMessage.text = data.text @@ -49,7 +54,7 @@ export const parseBasicMessage = data => { return basicMessage } -export const parseBasicCursor = data => ({ +export const parseBasicCursor = (data: any): BasicCursor => ({ position: data.position, updatedAt: data.updated_at, userId: data.user_id, @@ -57,13 +62,13 @@ export const parseBasicCursor = data => ({ type: data.cursor_type, }) -const parseMessageAttachment = data => ({ +const parseMessageAttachment = (data: any): {link: string, type: string, name: string} => ({ link: data.resource_link, type: data.type, name: data.name, }) -const parseMessagePart = data => { +const parseMessagePart = (data: any): BasicMessagePart => { if (data.content) { return { partType: "inline", diff --git a/src/presence-subscription.js b/src/presence-subscription.ts similarity index 58% rename from src/presence-subscription.js rename to src/presence-subscription.ts index 89875a3..d645fd0 100644 --- a/src/presence-subscription.js +++ b/src/presence-subscription.ts @@ -1,12 +1,27 @@ +import { Logger, Instance, Subscription } from "@pusher/platform"; + export class PresenceSubscription { - constructor(options) { + private userId: string; + private instance: Instance; + private logger: Logger; + private connectionTimeout: number; + + private timeout?: NodeJS.Timeout; + private sub?: Subscription; + + public constructor(options: { + userId: string; + instance: Instance; + logger: Logger; + connectionTimeout: number; + }) { this.userId = options.userId this.instance = options.instance this.logger = options.logger this.connectionTimeout = options.connectionTimeout } - connect() { + public connect(): Promise { return new Promise((resolve, reject) => { this.timeout = setTimeout(() => { reject(new Error("presence subscription timed out")) @@ -15,11 +30,11 @@ export class PresenceSubscription { path: `/users/${encodeURIComponent(this.userId)}/register`, listeners: { onOpen: () => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) resolve() }, onError: err => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) reject(err) }, }, @@ -27,8 +42,8 @@ export class PresenceSubscription { }) } - cancel() { - clearTimeout(this.timeout) + public cancel() { + this.timeout && clearTimeout(this.timeout) try { this.sub && this.sub.unsubscribe() } catch (err) { diff --git a/src/reconnection-handlers.js b/src/reconnection-handlers.ts similarity index 52% rename from src/reconnection-handlers.js rename to src/reconnection-handlers.ts index 77efae7..e294bd1 100644 --- a/src/reconnection-handlers.js +++ b/src/reconnection-handlers.ts @@ -1,11 +1,27 @@ -export function handleUserSubReconnection({ - basicUser, - basicRooms, - basicCursors, - currentUser, - roomStore, - cursorStore, - hooks, +import { BasicUser, User } from "./user"; +import { BasicRoom, Room } from "./room"; +import { BasicCursor, Cursor } from "./cursor"; +import { CurrentUser } from "./current-user"; +import { RoomStore } from "./room-store"; +import { CursorStore } from "./cursor-store"; +import { UserStore } from "./user-store"; + +export function handleUserSubReconnection({basicUser, basicRooms, basicCursors, currentUser, roomStore, cursorStore, hooks}: { + basicUser: BasicUser, + basicRooms: BasicRoom[], + basicCursors: BasicCursor[], + currentUser: CurrentUser, + roomStore: RoomStore, + cursorStore: CursorStore, + hooks: { + global: { + onAddedToRoom?: (room: BasicRoom) => void; + onRemovedFromRoom?: (room: BasicRoom) => void; + onRoomUpdated?: (room: BasicRoom) => void; + onRoomDeleted?: (room: BasicRoom) => void; + onNewReadCursor?: (cursor: BasicCursor) => void; + } + }, }) { currentUser.setPropertiesFromBasicUser(basicUser) @@ -37,31 +53,31 @@ export function handleUserSubReconnection({ } return handleCursorSubReconnection({ - basicCursors, - cursorStore, + basicCursors: basicCursors, + cursorStore: cursorStore, onNewCursorHook: hooks.global.onNewReadCursor, }) } -export function handleMembershipSubReconnection({ - userIds, - roomId, - roomStore, - userStore, - onUserJoinedRoomHook, - onUserLeftRoomHook, +export function handleMembershipSubReconnection({userIds, roomId, roomStore, userStore, onUserJoinedRoomHook, onUserLeftRoomHook}: { + userIds: string[], + roomId: string, + roomStore: RoomStore, + userStore: UserStore, + onUserJoinedRoomHook: (room: Room, user: User) => void, + onUserLeftRoomHook: (room: Room, user: User) => void, }) { return userStore.fetchMissingUsers(userIds).then(() => { const room = roomStore.getSync(roomId) userIds - .filter(userId => !room.userIds.includes(userId)) + .filter(userId => room.userIds.indexOf(userId) >= 0) .forEach(userId => userStore.get(userId).then(user => onUserJoinedRoomHook(room, user)), ) room.userIds - .filter(userId => !userIds.includes(userId)) + .filter(userId => userIds.indexOf(userId) >= 0) .forEach(userId => userStore.get(userId).then(user => onUserLeftRoomHook(room, user)), ) @@ -70,10 +86,10 @@ export function handleMembershipSubReconnection({ }) } -export function handleCursorSubReconnection({ - basicCursors, - cursorStore, - onNewCursorHook, +export function handleCursorSubReconnection({basicCursors, cursorStore, onNewCursorHook}: { + basicCursors: BasicCursor[], + cursorStore: CursorStore, + onNewCursorHook?: (cursor: Cursor) => void, }) { return Promise.all( basicCursors.map(basicCursor => { diff --git a/src/room-store.js b/src/room-store.ts similarity index 62% rename from src/room-store.js rename to src/room-store.ts index e269866..8894f1b 100644 --- a/src/room-store.js +++ b/src/room-store.ts @@ -1,10 +1,25 @@ import { append, uniq, pipe } from "ramda" import { parseBasicRoom } from "./parsers" -import { Room } from "./room" +import { Room, BasicRoom } from "./room" +import { Instance, Logger } from "@pusher/platform"; +import { UserStore } from "./user-store"; + +type RoomUpdateData = Partial; export class RoomStore { - constructor(options) { + private instance: Instance; + private userStore: UserStore; + private isSubscribedTo: (userId: string) => boolean + private logger: Logger; + private rooms: { [roomId: string]: Room} + + public constructor(options: { + instance: Instance; + userStore: UserStore; + isSubscribedTo: (userId: string) => boolean; + logger: Logger; + }) { this.instance = options.instance this.userStore = options.userStore this.isSubscribedTo = options.isSubscribedTo @@ -26,38 +41,39 @@ export class RoomStore { this.decorate = this.decorate.bind(this) } - setSync(basicRoom) { + public setSync(basicRoom: BasicRoom) { if (!this.rooms[basicRoom.id]) { - this.rooms[basicRoom.id] = this.decorate(basicRoom) + const room = this.decorate(basicRoom); + room && (this.rooms[basicRoom.id] = room) } return this.rooms[basicRoom.id] } - set(basicRoom) { + public set(basicRoom: BasicRoom) { return Promise.resolve(this.setSync(basicRoom)) } - get(roomId) { + public get(roomId: string) { return Promise.resolve(this.rooms[roomId]).then( room => room || - this.fetchBasicRoom(roomId).then(basicRoom => - this.set(roomId, basicRoom), + this.fetchBasicRoom(roomId).then((basicRoom: BasicRoom) => + this.set(basicRoom), ), ) } - popSync(roomId) { + public popSync(roomId: string) { const room = this.rooms[roomId] delete this.rooms[roomId] return room } - pop(roomId) { + public pop(roomId: string) { return Promise.resolve(this.popSync(roomId)) } - addUserToRoom(roomId, userId) { + public addUserToRoom(roomId: string, userId: string) { return Promise.all([ this.get(roomId).then(room => { room.userIds = uniq(append(userId, room.userIds)) @@ -67,54 +83,54 @@ export class RoomStore { ]).then(([room]) => room) } - removeUserFromRoom(roomId, userId) { + public removeUserFromRoom(roomId: string, userId: string) { return this.get(roomId).then(room => { room.userIds = room.userIds.filter(id => id !== userId) return room }) } - updateSync(roomId, updates) { + public updateSync(roomId: string, updates: RoomUpdateData) { const room = this.getSync(roomId) - for (const k in updates) { - room[k] = updates[k] + for (const k of Object.keys(updates)) { + room[k as keyof RoomUpdateData] = updates[k as keyof RoomUpdateData] } return room } - update(roomId, updates) { + public update(roomId: string, updates: RoomUpdateData) { return Promise.all([ this.get(roomId).then(() => this.updateSync(roomId, updates)), this.userStore.fetchMissingUsers(updates.userIds || []), ]).then(([room]) => room) } - fetchBasicRoom(roomId) { + private fetchBasicRoom(roomId: string) { return this.instance .request({ method: "GET", path: `/rooms/${encodeURIComponent(roomId)}`, }) .then( - pipe( + pipe( JSON.parse, parseBasicRoom, ), ) - .catch(err => { + .catch((err: any) => { this.logger.warn(`error fetching details for room ${roomId}:`, err) }) } - snapshot() { + public snapshot() { return this.rooms } - getSync(roomId) { + public getSync(roomId: string) { return this.rooms[roomId] } - decorate(basicRoom) { + private decorate(basicRoom?: BasicRoom): Room | undefined { return basicRoom ? new Room({ basicRoom, diff --git a/src/room-subscription.js b/src/room-subscription.ts similarity index 60% rename from src/room-subscription.js rename to src/room-subscription.ts index 3738657..ccf6f05 100644 --- a/src/room-subscription.js +++ b/src/room-subscription.ts @@ -1,9 +1,51 @@ import { CursorSubscription } from "./cursor-subscription" import { MessageSubscription } from "./message-subscription" import { MembershipSubscription } from "./membership-subscription" +import { Subscription, Instance, Logger } from "@pusher/platform"; +import { Room } from "./room"; +import { User } from "./user"; +import { UserStore } from "./user-store"; +import { RoomStore } from "./room-store"; +import { TypingIndicators } from "./typing-indicators"; +import { Message } from "./message"; +import { CursorStore } from "./cursor-store"; +import { Cursor } from "./cursor"; export class RoomSubscription { - constructor(options) { + private cancelled: boolean = false; + private connected: boolean = false; + private messageSub: MessageSubscription; + private cursorSub: CursorSubscription; + private membershipSub: MembershipSubscription; + private buffer: (() => void)[]; + + public constructor(options: { + roomId: string; + messageLimit?: number; + userId: string; + serverInstance: Instance; + userStore: UserStore; + roomStore: RoomStore; + typingIndicators: TypingIndicators; + logger: Logger; + connectionTimeout: number; + cursorStore: CursorStore; + cursorsInstance: Instance; + hooks: { + rooms: { + [roomId: string]: { + onMessage?: (message: Message) => void; + onNewReadCursor?: (cursor: Cursor) => void; + onUserJoined?: (user: User) => void; + onUserLeft?: (user: User) => void; + } + } + global: { + onUserJoinedRoom?: (room: Room, user: User) => void; + onUserLeftRoom?: (room: Room, user: User) => void; + } + } + }) { this.buffer = [] this.messageSub = new MessageSubscription({ @@ -16,12 +58,12 @@ export class RoomSubscription { typingIndicators: options.typingIndicators, logger: options.logger, connectionTimeout: options.connectionTimeout, - onMessageHook: this.bufferWhileConnecting(message => { + onMessageHook: this.bufferWhileConnecting((message: Message) => { if ( options.hooks.rooms[options.roomId] && options.hooks.rooms[options.roomId].onMessage ) { - options.hooks.rooms[options.roomId].onMessage(message) + options.hooks.rooms[options.roomId].onMessage!(message) } }), }) @@ -39,7 +81,7 @@ export class RoomSubscription { cursor.type === 0 && cursor.userId !== options.userId ) { - options.hooks.rooms[options.roomId].onNewReadCursor(cursor) + options.hooks.rooms[options.roomId].onNewReadCursor!(cursor) } }), }) @@ -59,7 +101,7 @@ export class RoomSubscription { options.hooks.rooms[room.id] && options.hooks.rooms[room.id].onUserJoined ) { - options.hooks.rooms[room.id].onUserJoined(user) + options.hooks.rooms[room.id].onUserJoined!(user) } }), onUserLeftRoomHook: this.bufferWhileConnecting((room, user) => { @@ -70,13 +112,13 @@ export class RoomSubscription { options.hooks.rooms[room.id] && options.hooks.rooms[room.id].onUserLeft ) { - options.hooks.rooms[room.id].onUserLeft(user) + options.hooks.rooms[room.id].onUserLeft!(user) } }), }) } - connect() { + public connect() { if (this.cancelled) { return Promise.reject( new Error("attempt to connect a cancelled room subscription"), @@ -89,15 +131,15 @@ export class RoomSubscription { ]).then(() => this.flushBuffer()) } - cancel() { + public cancel() { this.cancelled = true this.messageSub.cancel() this.cursorSub.cancel() this.membershipSub.cancel() } - bufferWhileConnecting(f) { - return (...args) => { + private bufferWhileConnecting(f: (...args: any[]) => void) { + return (...args: any[]) => { if (this.connected) { f(...args) } else { @@ -106,7 +148,7 @@ export class RoomSubscription { } } - flushBuffer() { + private flushBuffer() { this.connected = true this.buffer.forEach(f => f()) delete this.buffer diff --git a/src/room.js b/src/room.ts similarity index 57% rename from src/room.js rename to src/room.ts index 1aed8e5..ba10515 100644 --- a/src/room.js +++ b/src/room.ts @@ -1,7 +1,43 @@ import { contains, filter, values } from "ramda" +import { UserStore } from "./user-store"; +import { Logger } from "@pusher/platform"; + +export interface BasicRoom { + createdAt: string; + createdByUserId: string; + id: string; + isPrivate: boolean; + name: string; + updatedAt: string; + customData?: any; + deletedAt: string; + unreadCount: number; + lastMessageAt: string; +} export class Room { - constructor({ basicRoom, userStore, isSubscribedTo, logger }) { + public createdAt: string; + public createdByUserId: string; + public id: string; + public isPrivate: boolean; + public name: string; + public updatedAt: string; + public customData?: any; + public deletedAt: string; + public unreadCount: number; + public lastMessageAt: string; + public userIds: string[]; + + private userStore: UserStore; + private isSubscribedTo: (userId: string) => boolean; + private logger: Logger; + + public constructor({ basicRoom, userStore, isSubscribedTo, logger }: { + basicRoom: BasicRoom; + userStore: UserStore; + isSubscribedTo: (userId: string) => boolean; + logger: Logger; + }) { this.createdAt = basicRoom.createdAt this.createdByUserId = basicRoom.createdByUserId this.deletedAt = basicRoom.deletedAt @@ -20,7 +56,7 @@ export class Room { this.eq = this.eq.bind(this) } - get users() { + public get users() { if (!this.isSubscribedTo(this.id)) { const err = new Error( `Must be subscribed to room ${this.id} to access users property`, @@ -34,7 +70,7 @@ export class Room { ) } - eq(other) { + public eq(other: BasicRoom) { return ( this.createdAt === other.createdAt && this.createdByUserId === other.createdByUserId && diff --git a/src/token-provider.js b/src/token-provider.ts similarity index 56% rename from src/token-provider.js rename to src/token-provider.ts index a213132..86e11e6 100644 --- a/src/token-provider.js +++ b/src/token-provider.ts @@ -1,16 +1,28 @@ -import { sendRawRequest } from "@pusher/platform" +import { sendRawRequest, TokenProvider as PlatformTokenProvider } from "@pusher/platform" -import { appendQueryParams, typeCheck, unixSeconds, urlEncode } from "./utils" +import { appendQueryParams, unixSeconds, urlEncode } from "./utils" -export class TokenProvider { - constructor({ url, queryParams, headers, withCredentials } = {}) { - typeCheck("url", "string", url) - queryParams && typeCheck("queryParams", "object", queryParams) - headers && typeCheck("headers", "object", headers) +export class TokenProvider implements PlatformTokenProvider { + public userId?: string; + private url: string; + private queryParams: any; + private headers?: { [header: string]: any }; + private withCredentials: boolean; + + private cachedToken?: string; + private cacheExpiresAt: number = 0; + private req?: Promise<{ token: any; expiresIn: any; }> + + public constructor({url, queryParams, headers, withCredentials}: { + url: string, + queryParams?: any, + headers?: { [header: string]: string }, + withCredentials?: boolean }) + { this.url = url this.queryParams = queryParams this.headers = headers - this.withCredentials = withCredentials + this.withCredentials = withCredentials || false this.fetchToken = this.fetchToken.bind(this) this.fetchFreshToken = this.fetchFreshToken.bind(this) @@ -20,16 +32,21 @@ export class TokenProvider { this.setUserId = this.setUserId.bind(this) } - fetchToken() { + // Interface implementation, currently unused. + public clearToken() { + + } + + public fetchToken() { return !this.cacheIsStale() ? Promise.resolve(this.cachedToken) - : (this.req || this.fetchFreshToken()).then(({ token, expiresIn }) => { + : (this.req || this.fetchFreshToken())!.then(({ token, expiresIn }) => { this.cache(token, expiresIn) return token }) } - fetchFreshToken() { + public fetchFreshToken() { this.req = sendRawRequest({ method: "POST", url: appendQueryParams( @@ -43,35 +60,35 @@ export class TokenProvider { }, withCredentials: this.withCredentials, }) - .then(res => { + .then((res: any) => { const { access_token: token, expires_in: expiresIn } = JSON.parse(res) delete this.req return { token, expiresIn } }) - .catch(err => { + .catch((err: any) => { delete this.req throw err }) return this.req } - cacheIsStale() { + private cacheIsStale() { return !this.cachedToken || unixSeconds() > this.cacheExpiresAt } - cache(token, expiresIn) { + private cache(token: string, expiresIn: number) { this.cachedToken = token this.cacheExpiresAt = unixSeconds() + expiresIn } - clearCache() { + private clearCache() { this.cachedToken = undefined - this.cacheExpiresAt = undefined + this.cacheExpiresAt = 0 } // To allow ChatManager to feed the userId to the TokenProvider. Not set // directly so as not to mess with a custom TokenProvider implementation. - setUserId(userId) { + public setUserId(userId: string) { this.clearCache() this.userId = userId } diff --git a/src/typing-indicators.js b/src/typing-indicators.ts similarity index 61% rename from src/typing-indicators.js rename to src/typing-indicators.ts index 59a18e4..3dac75f 100644 --- a/src/typing-indicators.js +++ b/src/typing-indicators.ts @@ -1,7 +1,31 @@ import { TYPING_INDICATOR_TTL, TYPING_INDICATOR_LEEWAY } from "./constants" +import { Instance, Logger } from "@pusher/platform"; +import { Room } from "./room"; +import { User } from "./user"; export class TypingIndicators { - constructor({ hooks, instance, logger }) { + private logger: Logger; + private instance: Instance; + private hooks: { + rooms: { + [roomId: string]: { + onUserStartedTyping?: (user: User) => void; + onUserStoppedTyping?: (user: User) => void; + } + }, + global: { + onUserStartedTyping?: (room: Room, user: User) => void; + onUserStoppedTyping?: (room: Room, user: User) => void; + } + }; + private lastSentRequests: { [roomId: string]: number }; + private timers: { [roomId: string]: { [userId: string]: NodeJS.Timeout } }; + + public constructor({hooks, instance, logger}: { + hooks: TypingIndicators['hooks']; + instance: Instance; + logger: Logger; + }) { this.hooks = hooks this.instance = instance this.logger = logger @@ -14,7 +38,7 @@ export class TypingIndicators { this.onStopped = this.onStopped.bind(this) } - sendThrottledRequest(roomId) { + public sendThrottledRequest(roomId: string): Promise { const now = Date.now() const sent = this.lastSentRequests[roomId] if (sent && now - sent < TYPING_INDICATOR_TTL - TYPING_INDICATOR_LEEWAY) { @@ -27,7 +51,7 @@ export class TypingIndicators { path: `/rooms/${encodeURIComponent(roomId)}/typing_indicators`, }) .catch(err => { - delete this.typingRequestSent[roomId] + delete this.lastSentRequests[roomId] this.logger.warn( `Error sending typing indicator in room ${roomId}`, err, @@ -36,7 +60,7 @@ export class TypingIndicators { }) } - onIsTyping(room, user) { + public onIsTyping(room: Room, user: User) { if (!this.timers[room.id]) { this.timers[room.id] = {} } @@ -51,7 +75,7 @@ export class TypingIndicators { }, TYPING_INDICATOR_TTL) } - onStarted(room, user) { + private onStarted(room: Room, user: User) { if (this.hooks.global.onUserStartedTyping) { this.hooks.global.onUserStartedTyping(room, user) } @@ -59,11 +83,11 @@ export class TypingIndicators { this.hooks.rooms[room.id] && this.hooks.rooms[room.id].onUserStartedTyping ) { - this.hooks.rooms[room.id].onUserStartedTyping(user) + this.hooks.rooms[room.id].onUserStartedTyping!(user) } } - onStopped(room, user) { + private onStopped(room: Room, user: User) { if (this.hooks.global.onUserStoppedTyping) { this.hooks.global.onUserStoppedTyping(room, user) } @@ -71,7 +95,7 @@ export class TypingIndicators { this.hooks.rooms[room.id] && this.hooks.rooms[room.id].onUserStoppedTyping ) { - this.hooks.rooms[room.id].onUserStoppedTyping(user) + this.hooks.rooms[room.id].onUserStoppedTyping!(user) } } } diff --git a/src/user-presence-subscription.js b/src/user-presence-subscription.ts similarity index 50% rename from src/user-presence-subscription.js rename to src/user-presence-subscription.ts index 2248594..4a87fce 100644 --- a/src/user-presence-subscription.js +++ b/src/user-presence-subscription.ts @@ -1,9 +1,44 @@ import { contains, compose, forEach, filter, toPairs } from "ramda" import { parsePresence } from "./parsers" +import { UserStore } from "./user-store"; +import { RoomStore } from "./room-store"; +import { Presence, User, PresenceStore } from "./user"; +import { Logger, Subscription, Instance } from "@pusher/platform"; +import { Room } from "./room"; export class UserPresenceSubscription { - constructor(options) { + private userId: string; + private hooks: { + global: { + onPresenceChanged?: (state: { current: Presence, previous: Presence }, user: User) => void; + }; + rooms: { + [roomId: string]: { + onPresenceChanged?: (state: { current: Presence, previous: Presence }, user: User) => void; + } + } + } + private instance: Instance; + private userStore: UserStore; + private roomStore: RoomStore; + private presenceStore: PresenceStore; + private logger: Logger; + private connectionTimeout: number; + private timeout?: NodeJS.Timeout; + private onSubscriptionEstablished?: () => void; + private sub?: Subscription; + + public constructor(options: { + userId: string; + hooks: UserPresenceSubscription["hooks"]; + instance: Instance; + userStore: UserStore; + roomStore: RoomStore; + presenceStore: PresenceStore; + logger: Logger; + connectionTimeout: number; + }) { this.userId = options.userId this.hooks = options.hooks this.instance = options.instance @@ -19,20 +54,20 @@ export class UserPresenceSubscription { this.onPresenceState = this.onPresenceState.bind(this) } - connect() { + public connect(): Promise { return new Promise((resolve, reject) => { this.timeout = setTimeout(() => { reject(new Error("user presence subscription timed out")) }, this.connectionTimeout) this.onSubscriptionEstablished = () => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) resolve() } this.sub = this.instance.subscribeNonResuming({ path: `/users/${encodeURIComponent(this.userId)}`, listeners: { onError: err => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) reject(err) }, onEvent: this.onEvent, @@ -41,8 +76,8 @@ export class UserPresenceSubscription { }) } - cancel() { - clearTimeout(this.timeout) + public cancel() { + this.timeout && clearTimeout(this.timeout) try { this.sub && this.sub.unsubscribe() } catch (err) { @@ -50,7 +85,7 @@ export class UserPresenceSubscription { } } - onEvent({ body }) { + private onEvent({body}: {body: any}) { switch (body.event_name) { case "presence_state": this.onPresenceState(body.data) @@ -58,8 +93,10 @@ export class UserPresenceSubscription { } } - onPresenceState(data) { - this.onSubscriptionEstablished() + + + private onPresenceState({data}: {data: any}) { + this.onSubscriptionEstablished && this.onSubscriptionEstablished() const previous = this.presenceStore[this.userId] || "unknown" const current = parsePresence(data).state if (current === previous) { @@ -70,17 +107,19 @@ export class UserPresenceSubscription { if (this.hooks.global.onPresenceChanged) { this.hooks.global.onPresenceChanged({ current, previous }, user) } - compose( - forEach(([roomId, hooks]) => - this.roomStore.get(roomId).then(room => { + compose( + forEach(([roomId, hooks]: PresencePair) => + this.roomStore.get(roomId).then((room: Room) => { if (contains(user.id, room.userIds)) { - hooks.onPresenceChanged({ current, previous }, user) + hooks.onPresenceChanged && hooks.onPresenceChanged({ current, previous }, user) } }), ), - filter(pair => pair[1].onPresenceChanged !== undefined), + filter((pair: PresencePair) => pair[1].onPresenceChanged !== undefined), toPairs, )(this.hooks.rooms) }) } } + +type PresencePair = [string, UserPresenceSubscription['hooks']['rooms']['']]; \ No newline at end of file diff --git a/src/user-store.js b/src/user-store.ts similarity index 62% rename from src/user-store.js rename to src/user-store.ts index d69df30..6cbf9a8 100644 --- a/src/user-store.js +++ b/src/user-store.ts @@ -2,10 +2,22 @@ import { difference } from "ramda" import { appendQueryParamsAsArray } from "./utils" import { parseBasicUser } from "./parsers" -import { User } from "./user" +import { User, PresenceStore, BasicUser } from "./user" +import { Instance, Logger } from "@pusher/platform"; export class UserStore { - constructor({ instance, presenceStore, logger }) { + private instance: Instance; + private presenceStore: PresenceStore; + private logger: Logger; + private reqs: { [userId: string]: Promise } + public onSetHooks: ((userId: string) => void)[]; + private users: { [userId: string]: User }; + + public constructor({instance, presenceStore, logger}: { + instance: Instance; + presenceStore: PresenceStore; + logger: Logger; + }) { this.instance = instance this.presenceStore = presenceStore this.logger = logger @@ -22,20 +34,21 @@ export class UserStore { this.decorate = this.decorate.bind(this) } - set(basicUser) { - this.users[basicUser.id] = this.decorate(basicUser) + public set(basicUser: BasicUser) { + const user = this.decorate(basicUser) + user && (this.users[basicUser.id] = user) this.onSetHooks.forEach(hook => hook(basicUser.id)) return Promise.resolve(this.users[basicUser.id]) } - get(userId) { + public get(userId: string) { return this.fetchMissingUsers([userId]).then(() => this.users[userId]) } - fetchMissingUsers(userIds) { + public fetchMissingUsers(userIds: string[]) { const missing = difference( userIds, - Object.values(this.users).map(u => u.id), + Object.keys(this.users).map(k => this.users[k].id), ) const missingNotInProgress = difference(missing, Object.keys(this.reqs)) if (missingNotInProgress.length > 0) { @@ -44,15 +57,15 @@ export class UserStore { return Promise.all(userIds.map(userId => this.reqs[userId])) } - fetchBasicUsers(userIds) { + public fetchBasicUsers(userIds: string[]) { const req = this.instance .request({ method: "GET", path: appendQueryParamsAsArray("id", userIds, "/users_by_ids"), }) .then(res => { - const basicUsers = JSON.parse(res).map(u => parseBasicUser(u)) - basicUsers.forEach(user => this.set(user)) + const basicUsers = JSON.parse(res).map((u: any) => parseBasicUser(u)) + basicUsers.forEach((user: BasicUser | undefined) => user && this.set(user)) userIds.forEach(userId => { delete this.reqs[userId] }) @@ -66,15 +79,15 @@ export class UserStore { }) } - snapshot() { + public snapshot() { return this.users } - getSync(userId) { + public getSync(userId: string) { return this.users[userId] } - decorate(basicUser) { + private decorate(basicUser: BasicUser): User | undefined { return basicUser ? new User(basicUser, this.presenceStore) : undefined } } diff --git a/src/user-subscription.js b/src/user-subscription.ts similarity index 54% rename from src/user-subscription.js rename to src/user-subscription.ts index c7ed430..df464e2 100644 --- a/src/user-subscription.js +++ b/src/user-subscription.ts @@ -1,14 +1,59 @@ import { parseBasicRoom, parseBasicUser, parseBasicCursor } from "./parsers" import { handleUserSubReconnection } from "./reconnection-handlers" +import { Instance, Logger, Subscription } from "@pusher/platform"; +import { RoomStore } from "./room-store"; +import { CursorStore } from "./cursor-store"; +import { CurrentUser } from "./current-user"; +import { Room, BasicRoom } from "./room"; +import { Cursor, BasicCursor } from "./cursor"; +import { BasicUser } from "./user"; +import { TypingIndicators } from "./typing-indicators"; export class UserSubscription { - constructor(options) { + private userId: string; + private hooks: { + global: { + onAddedToRoom?: (room: BasicRoom) => void; + onRemovedFromRoom?: (room: BasicRoom) => void; + onRoomUpdated?: (room: BasicRoom) => void; + onRoomDeleted?: (room: BasicRoom) => void; + onNewReadCursor?: (cursor: BasicCursor) => void; + } + }; + private readonly instance: Instance; + private readonly roomStore: RoomStore; + private readonly cursorStore: CursorStore; + private readonly typingIndicators: TypingIndicators; + private readonly logger: Logger; + private readonly connectionTimeout: number; + private readonly currentUser: CurrentUser; + + private timeout?: NodeJS.Timeout; + private onSubscriptionEstablished?: ({basicUser, basicRooms, basicCursors}: { + basicUser: BasicUser, + basicRooms: BasicRoom[], + basicCursors: BasicCursor[] + }) => void; + private established: boolean = false; + private sub?: Subscription; + + constructor(options: { + userId: string; + hooks: UserSubscription["hooks"]; + instance: Instance; + roomStore: RoomStore; + cursorStore: CursorStore; + typingIndicators: TypingIndicators, + logger: Logger; + connectionTimeout: number; + currentUser: CurrentUser; + }) { this.userId = options.userId this.hooks = options.hooks this.instance = options.instance this.roomStore = options.roomStore this.cursorStore = options.cursorStore - this.roomSubscriptions = options.roomSubscriptions + this.typingIndicators = options.typingIndicators this.logger = options.logger this.connectionTimeout = options.connectionTimeout this.currentUser = options.currentUser @@ -23,20 +68,24 @@ export class UserSubscription { this.onRoomDeleted = this.onRoomDeleted.bind(this) } - connect() { + public connect(): Promise<{ + basicUser: BasicUser, + basicRooms: BasicRoom[], + basicCursors: BasicCursor[] + }> { return new Promise((resolve, reject) => { this.timeout = setTimeout(() => { reject(new Error("user subscription timed out")) }, this.connectionTimeout) - this.onSubscriptionEstablished = initialState => { - clearTimeout(this.timeout) + this.onSubscriptionEstablished = (initialState: {basicUser: BasicUser, basicRooms : BasicRoom[], basicCursors: BasicCursor[]}) => { + this.timeout && clearTimeout(this.timeout) resolve(initialState) } this.sub = this.instance.subscribeNonResuming({ path: "/users", listeners: { onError: err => { - clearTimeout(this.timeout) + this.timeout && clearTimeout(this.timeout) reject(err) }, onEvent: this.onEvent, @@ -45,8 +94,8 @@ export class UserSubscription { }) } - cancel() { - clearTimeout(this.timeout) + public cancel() { + this.timeout && clearTimeout(this.timeout) try { this.sub && this.sub.unsubscribe() } catch (err) { @@ -54,7 +103,7 @@ export class UserSubscription { } } - onEvent({ body }) { + private onEvent(body: any) { switch (body.event_name) { case "initial_state": this.onInitialState(body.data) @@ -77,17 +126,17 @@ export class UserSubscription { } } - onInitialState({ - current_user: userData, - rooms: roomsData, - cursors: cursorsData, + private onInitialState(data: { + current_user: any, + rooms: any, + cursors: any, }) { - const basicUser = parseBasicUser(userData) - const basicRooms = roomsData.map(d => parseBasicRoom(d)) - const basicCursors = cursorsData.map(d => parseBasicCursor(d)) + const basicUser = parseBasicUser(data.current_user) + const basicRooms = data.rooms.map((d: any) => parseBasicRoom(d)) + const basicCursors = data.cursors.map((d: any) => parseBasicCursor(d)) if (!this.established) { this.established = true - this.onSubscriptionEstablished({ basicUser, basicRooms, basicCursors }) + this.onSubscriptionEstablished && this.onSubscriptionEstablished({basicUser, basicRooms, basicCursors}) } else { handleUserSubReconnection({ basicUser, @@ -101,7 +150,7 @@ export class UserSubscription { } } - onAddedToRoom({ room: roomData }) { + private onAddedToRoom({ room: roomData }: {room: any}) { this.roomStore.set(parseBasicRoom(roomData)).then(room => { if (this.hooks.global.onAddedToRoom) { this.hooks.global.onAddedToRoom(room) @@ -109,7 +158,7 @@ export class UserSubscription { }) } - onRemovedFromRoom({ room_id: roomId }) { + private onRemovedFromRoom({room_id: roomId}: {room_id: string}) { this.roomStore.pop(roomId).then(room => { if (room && this.hooks.global.onRemovedFromRoom) { this.hooks.global.onRemovedFromRoom(room) @@ -117,7 +166,7 @@ export class UserSubscription { }) } - onRoomUpdated({ room: roomData }) { + private onRoomUpdated({ room: roomData }: {room: any}) { const updates = parseBasicRoom(roomData) this.roomStore.update(updates.id, updates).then(room => { if (this.hooks.global.onRoomUpdated) { @@ -126,7 +175,7 @@ export class UserSubscription { }) } - onRoomDeleted({ room_id: roomId }) { + private onRoomDeleted({room_id: roomId}: {room_id: string}) { this.roomStore.pop(roomId).then(room => { if (room && this.hooks.global.onRoomDeleted) { this.hooks.global.onRoomDeleted(room) @@ -134,7 +183,7 @@ export class UserSubscription { }) } - onNewCursor(data) { + private onNewCursor(data: any) { return this.cursorStore.set(parseBasicCursor(data)).then(cursor => { if (this.hooks.global.onNewReadCursor && cursor.type === 0) { this.hooks.global.onNewReadCursor(cursor) diff --git a/src/user.js b/src/user.js deleted file mode 100644 index 6ab64a8..0000000 --- a/src/user.js +++ /dev/null @@ -1,17 +0,0 @@ -export class User { - constructor(basicUser, presenceStore) { - this.avatarURL = basicUser.avatarURL - this.createdAt = basicUser.createdAt - this.customData = basicUser.customData - this.id = basicUser.id - this.name = basicUser.name - this.updatedAt = basicUser.updatedAt - this.presenceStore = presenceStore - } - - get presence() { - return { - state: this.presenceStore[this.id] || "unknown", - } - } -} diff --git a/src/user.ts b/src/user.ts new file mode 100644 index 0000000..022d62c --- /dev/null +++ b/src/user.ts @@ -0,0 +1,40 @@ +export interface BasicUser { + avatarURL: string; + createdAt: string; + customData?: any; + id: string; + name: string; + updatedAt: string; +} + +export interface PresenceStore { + [userId: string]: Presence; +} + +export type Presence = "unknown" | "online" | "offline"; + +export class User implements BasicUser { + public avatarURL: string; + public createdAt: string; + public customData?: any; + public id: string; + public name: string; + public updatedAt: string; + private presenceStore: PresenceStore; + + public constructor(basicUser: BasicUser, presenceStore: PresenceStore) { + this.avatarURL = basicUser.avatarURL + this.createdAt = basicUser.createdAt + this.customData = basicUser.customData + this.id = basicUser.id + this.name = basicUser.name + this.updatedAt = basicUser.updatedAt + this.presenceStore = presenceStore + } + + public get presence() { + return { + state: this.presenceStore[this.id] || "unknown", + } + } +} diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index 5a11cfa..0000000 --- a/src/utils.js +++ /dev/null @@ -1,66 +0,0 @@ -import { - contains, - filter, - forEachObjIndexed, - join, - map, - pipe, - toPairs, -} from "ramda" - -export const urlEncode = pipe( - filter(x => x !== undefined), - toPairs, - map(([k, v]) => `${k}=${encodeURIComponent(v)}`), - join("&"), -) - -export const appendQueryParams = (queryParams, url) => { - const separator = contains("?", url) ? "&" : "?" - return url + separator + urlEncode(queryParams) -} - -export const appendQueryParamsAsArray = (key, values, url) => { - const separator = contains("?", url) ? "" : "?" - const encodedPairs = map( - v => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`, - values, - ) - return url + separator + join("&", encodedPairs) -} - -export const typeCheck = (name, expectedType, value) => { - const type = typeof value - if (type !== expectedType) { - throw new TypeError( - `expected ${name} to be of type ${expectedType} but was of type ${type}`, - ) - } -} - -// checks that all of an arrays elements are of the given type -export const typeCheckArr = (name, expectedType, arr) => { - if (!Array.isArray(arr)) { - throw new TypeError(`expected ${name} to be an array`) - } - arr.forEach((value, i) => typeCheck(`${name}[${i}]`, expectedType, value)) -} - -// checks that all of an objects values are of the given type -export const typeCheckObj = (name, expectedType, obj) => { - typeCheck(name, "object", obj) - forEachObjIndexed( - (value, key) => typeCheck(`${name}.${key}`, expectedType, value), - obj, - ) -} - -export const checkOneOf = (name, values, value) => { - if (!contains(value, values)) { - throw new TypeError( - `expected ${name} to be one of ${values} but was ${value}`, - ) - } -} - -export const unixSeconds = () => Math.floor(Date.now() / 1000) diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..2550258 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,31 @@ +import { + contains, + filter, + join, + map, + pipe, + toPairs, +} from "ramda" + +export const urlEncode = pipe( + filter(x => x !== undefined), + toPairs, + map(([k, v]) => `${k}=${encodeURIComponent(v)}`), + join("&"), +) + +export const appendQueryParams = (queryParams: any, url: string) => { + const separator = contains("?", url) ? "&" : "?" + return url + separator + urlEncode(queryParams) +} + +export const appendQueryParamsAsArray = (key: string, values: any[], url: string) => { + const separator = contains("?", url) ? "" : "?" + const encodedPairs = map( + v => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`, + values, + ) + return url + separator + join("&", encodedPairs) +} + +export const unixSeconds = () => Math.floor(Date.now() / 1000) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fc16757 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationDir": "declarations", + "lib": ["es6", "webworker", "dom"], + "module": "CommonJS", + "removeComments": true, + "sourceMap": true, + "strict": true, + "target": "es5", + "resolveJsonModule": true, + "moduleResolution": "node" + }, + "exclude": [ + "node_modules", + "target", + "test" + ] +} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..bfe53f5 --- /dev/null +++ b/tslint.json @@ -0,0 +1,27 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "linterOptions": { + "exclude": [ + "src/declarations/**/*.d.ts" + ] + }, + "rules": { + "arrow-parens": false, + "interface-name": [true, "never-prefix"], + "interface-over-type-literal": false, + "max-classes-per-file": [false], + "member-access": [true, "no-public"], + "member-ordering": [true, {"order": "instance-sandwich"}], + "quotemark": [ + true, + "single", + "avoid-escape" + ], + "semicolon": [true, "always", "ignore-bound-class-methods"] + }, + "rulesDirectory": [] + } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 07a5609..49ddc48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -80,6 +80,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.36.tgz#eac05d576fbcd0b4ea3c912dc58c20475c08d9e4" integrity sha512-SL6KhfM7PTqiFmbCW3eVNwVBZ+88Mrzbuvn9olPsfv43mbiWaFY+nRcz/TGGku0/lc2FepdMbImdMY1JrQ+zbw== +"@types/ramda@^0.26.9": + version "0.26.9" + resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.26.9.tgz#f0bbba59c04de9d29aaa1d1c0daedefc7244bb68" + integrity sha512-yozsk6sXE29kP/mzLCgZ6RFSwx2pD41ZgW1m4rk3meKyj4mw77ISxnu5Knc1tLaKQd2ocdd7Y0daCG678olPfw== + "@types/tough-cookie@*": version "2.3.5" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d" @@ -103,11 +108,6 @@ acorn-dynamic-import@^4.0.0: resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== -acorn-jsx@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.0.tgz#958584ddb60990c02c97c1bd9d521fce433bb101" - integrity sha512-XkB50fn0MURDyww9+UYL3c1yLbOBz0ZFvrdYlGB8l+Ije1oSC75qAqrzSPjYQbdnQUzhlUGNKuesryAv0gxZOg== - acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.6.2.tgz#b7d7ceca6f22e6417af933a62cad4de01048d5d2" @@ -143,16 +143,6 @@ ajv@^5.3.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.5.3: - version "6.5.4" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.4.tgz#247d5274110db653706b550fcc2b797ca28cfc59" - integrity sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - ansi-escapes@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" @@ -255,18 +245,6 @@ array-reduce@~0.0.0: resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys= -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" @@ -277,11 +255,6 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -arrify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - asap@^2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -1184,7 +1157,7 @@ buffer@^5.0.2: base64-js "^1.0.2" ieee754 "^1.1.4" -builtin-modules@^1.0.0: +builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= @@ -1232,18 +1205,6 @@ cached-path-relative@^1.0.0: resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7" integrity sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc= -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" - integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= - dependencies: - callsites "^0.2.0" - -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" - integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= - camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -1267,7 +1228,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@2.4.1, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0: +chalk@2.4.1, chalk@^2.0.0, chalk@^2.3.0: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== @@ -1321,11 +1282,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -1419,6 +1375,11 @@ commander@^2.11.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== +commander@^2.12.1: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== + commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -1551,17 +1512,6 @@ cross-spawn-async@^2.1.6: lru-cache "^4.0.0" which "^1.2.8" -cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - crypto-browserify@^3.0.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -1617,13 +1567,6 @@ debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6. dependencies: ms "2.0.0" -debug@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" - integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== - dependencies: - ms "^2.1.1" - decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1651,11 +1594,6 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - defer-to-connect@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.0.2.tgz#4bae758a314b034ae33902b5aac25a8dd6a8633e" @@ -1695,19 +1633,6 @@ defined@^1.0.0, defined@~1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= -del@^2.0.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" - integrity sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag= - dependencies: - globby "^5.0.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - rimraf "^2.2.8" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1765,6 +1690,11 @@ dezalgo@^1.0.1: asap "^2.0.0" wrappy "1" +diff@^3.2.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -1774,13 +1704,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - domain-browser@~1.1.0: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" @@ -1970,115 +1893,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -eslint-config-prettier@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.1.0.tgz#2c26d2cdcfa3a05f0642cd7e6e4ef3316cdabfa2" - integrity sha512-QYGfmzuc4q4J6XIhlp8vRKdI/fI0tQfQPy1dME3UOLprE+v4ssH/3W9LM2Q7h5qBcy5m0ehCrBDU2YF8q6OY8w== - dependencies: - get-stdin "^6.0.0" - -eslint-plugin-prettier@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.0.tgz#f6b823e065f8c36529918cdb766d7a0e975ec30c" - integrity sha512-4g11opzhqq/8+AMmo5Vc2Gn7z9alZ4JqrbZ+D4i8KlSyxeQhZHlmIrY8U9Akf514MoEhogPa87Jgkq87aZ2Ohw== - dependencies: - prettier-linter-helpers "^1.0.0" - -eslint-scope@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" - integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-utils@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" - integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q== - -eslint-visitor-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" - integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== - -eslint@^5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.8.0.tgz#91fbf24f6e0471e8fdf681a4d9dd1b2c9f28309b" - integrity sha512-Zok6Bru3y2JprqTNm14mgQ15YQu/SMDkWdnmHfFg770DIUlmMFd/gqqzCHekxzjHZJxXv3tmTpH0C1icaYJsRQ== - dependencies: - "@babel/code-frame" "^7.0.0" - ajv "^6.5.3" - chalk "^2.1.0" - cross-spawn "^6.0.5" - debug "^4.0.1" - doctrine "^2.1.0" - eslint-scope "^4.0.0" - eslint-utils "^1.3.1" - eslint-visitor-keys "^1.0.0" - espree "^4.0.0" - esquery "^1.0.1" - esutils "^2.0.2" - file-entry-cache "^2.0.0" - functional-red-black-tree "^1.0.1" - glob "^7.1.2" - globals "^11.7.0" - ignore "^4.0.6" - imurmurhash "^0.1.4" - inquirer "^6.1.0" - is-resolvable "^1.1.0" - js-yaml "^3.12.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.5" - minimatch "^3.0.4" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.2" - pluralize "^7.0.0" - progress "^2.0.0" - regexpp "^2.0.1" - require-uncached "^1.0.3" - semver "^5.5.1" - strip-ansi "^4.0.0" - strip-json-comments "^2.0.1" - table "^5.0.2" - text-table "^0.2.0" - -espree@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-4.1.0.tgz#728d5451e0fd156c04384a7ad89ed51ff54eb25f" - integrity sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w== - dependencies: - acorn "^6.0.2" - acorn-jsx "^5.0.0" - eslint-visitor-keys "^1.0.0" - esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" - integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== - dependencies: - estraverse "^4.0.0" - -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== - dependencies: - estraverse "^4.1.0" - -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" - integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= - estree-walker@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.2.1.tgz#bdafe8095383d8414d5dc2ecf4c9173b6db9412e" @@ -2089,6 +1908,11 @@ estree-walker@^0.5.0, estree-walker@^0.5.2: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39" integrity sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig== +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -2214,26 +2038,11 @@ fast-deep-equal@^1.0.0: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - -fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== - fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= -fast-levenshtein@~2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - fd-slicer@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" @@ -2248,14 +2057,6 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" - filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -2290,16 +2091,6 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -flat-cache@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" - integrity sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE= - dependencies: - circular-json "^0.3.1" - del "^2.0.2" - graceful-fs "^4.1.2" - write "^0.2.1" - for-each@~0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -2381,11 +2172,6 @@ function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -2410,11 +2196,6 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= -get-stdin@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" - integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== - get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -2449,7 +2230,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@^7.0.3, glob@^7.0.5, glob@^7.1.0, glob@^7.1.2, glob@~7.1.2: +glob@^7.0.5, glob@^7.1.0, glob@^7.1.2, glob@~7.1.2: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -2461,28 +2242,23 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.0, glob@^7.1.2, glob@~7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.7.0: - version "11.8.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.8.0.tgz#c1ef45ee9bed6badf0663c5cb90e8d1adec1321d" - integrity sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA== +glob@^7.1.1: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== -globby@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" - integrity sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0= - dependencies: - array-union "^1.0.1" - arrify "^1.0.0" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -2705,16 +2481,6 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -2757,7 +2523,7 @@ inline-source-map@~0.6.0: dependencies: source-map "~0.5.3" -inquirer@6.2.0, inquirer@^6.1.0: +inquirer@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.0.tgz#51adcd776f661369dc1e894859c2560a224abdd8" integrity sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg== @@ -2958,25 +2724,6 @@ is-number@^4.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== -is-path-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= - -is-path-in-cwd@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" - integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ== - dependencies: - is-path-inside "^1.0.0" - -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= - dependencies: - path-is-inside "^1.0.1" - is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -3006,11 +2753,6 @@ is-regex@^1.0.4: dependencies: has "^1.0.1" -is-resolvable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - is-symbol@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" @@ -3080,10 +2822,10 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" - integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== +js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -3113,21 +2855,11 @@ json-schema-traverse@^0.3.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - json-stable-stringify@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" @@ -3251,14 +2983,6 @@ labeled-stream-splicer@^2.0.0: isarray "^2.0.4" stream-splicer "^2.0.0" -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -3315,7 +3039,7 @@ lodash.toarray@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= -lodash@4.17.11, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5: +lodash@4.17.11, lodash@^4.17.10, lodash@^4.17.4: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -3628,11 +3352,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - needle@^2.2.1: version "2.2.4" resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" @@ -3642,11 +3361,6 @@ needle@^2.2.1: iconv-lite "^0.4.4" sax "^1.2.4" -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - node-emoji@1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826" @@ -3824,18 +3538,6 @@ optimist@^0.6.1, optimist@~0.6.1: minimist "~0.0.1" wordwrap "~0.0.2" -optionator@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" - integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.4" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - wordwrap "~1.0.0" - ordered-emitter@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/ordered-emitter/-/ordered-emitter-0.1.1.tgz#aa20bdafbdcc1631834a350f68b4ef8eb34eed7b" @@ -3945,17 +3647,7 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1, path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - -path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-parse@^1.0.5: +path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== @@ -4026,21 +3718,11 @@ plist@0.2.1: dependencies: sax "0.1.x" -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== - posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" @@ -4051,13 +3733,6 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - prettier@1.14.3: version "1.14.3" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895" @@ -4099,11 +3774,6 @@ progress-stream@^1.1.0: speedometer "~0.1.2" through2 "~0.2.3" -progress@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.1.tgz#c9242169342b1c29d275889c95734621b1952e31" - integrity sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg== - promisify-event@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/promisify-event/-/promisify-event-1.0.0.tgz#bd7523ea06b70162f370979016b53a686c60e90f" @@ -4166,11 +3836,6 @@ punycode@^1.3.2, punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -4362,11 +4027,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - regexpu-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" @@ -4436,19 +4096,6 @@ request@^2.45.0, request@^2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" -require-uncached@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" - -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" - integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= - resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -4466,6 +4113,13 @@ resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.4.0: dependencies: path-parse "^1.0.5" +resolve@^1.10.0, resolve@^1.3.2: + version "1.11.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232" + integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw== + dependencies: + path-parse "^1.0.6" + resolve@~1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" @@ -4564,6 +4218,14 @@ rollup-plugin-node-resolve@^3.0.2: is-module "^1.0.0" resolve "^1.1.6" +rollup-plugin-typescript@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-typescript/-/rollup-plugin-typescript-1.0.1.tgz#86565033b714c3d1f3aba510aad3dc519f7091e9" + integrity sha512-rwJDNn9jv/NsKZuyBb/h0jsclP4CJ58qbvZt2Q9zDIGILF2LtdtvCqMOL+Gq9IVq5MTrTlHZNrn8h7VjQgd8tw== + dependencies: + resolve "^1.10.0" + rollup-pluginutils "^2.5.0" + rollup-plugin-uglify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/rollup-plugin-uglify/-/rollup-plugin-uglify-3.0.0.tgz#a34eca24617709c6bf1778e9653baafa06099b86" @@ -4587,6 +4249,13 @@ rollup-pluginutils@^2.0.1: estree-walker "^0.5.2" micromatch "^2.3.11" +rollup-pluginutils@^2.5.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97" + integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg== + dependencies: + estree-walker "^0.6.1" + rollup@^0.55.3: version "0.55.5" resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.55.5.tgz#2f88c300f7cf24b5ec2dca8a6aba73b04e087e93" @@ -4633,7 +4302,7 @@ sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -"semver@2 || 3 || 4 || 5", semver@5.6.0, semver@^5.3.0, semver@^5.5.0, semver@^5.5.1: +"semver@2 || 3 || 4 || 5", semver@5.6.0, semver@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== @@ -4684,18 +4353,6 @@ shasum@^1.0.0: json-stable-stringify "~0.0.0" sha.js "~2.4.4" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - shell-quote@^1.4.3, shell-quote@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" @@ -4728,13 +4385,6 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= -slice-ansi@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" - integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== - dependencies: - is-fullwidth-code-point "^2.0.0" - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -4969,7 +4619,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.1.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -5033,7 +4683,7 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: +strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= @@ -5072,16 +4722,6 @@ syntax-error@^1.1.1: dependencies: acorn-node "^1.2.0" -table@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/table/-/table-5.1.0.tgz#69a54644f6f01ad1628f8178715b408dc6bf11f7" - integrity sha512-e542in22ZLhD/fOIuXs/8yDZ9W61ltF8daM88rkRNtgTIct+vI2fTnAyu/Db2TCfEcI8i7mjZz6meLq0nW7TYg== - dependencies: - ajv "^6.5.3" - lodash "^4.17.10" - slice-ansi "1.0.0" - string-width "^2.1.1" - tap-colorize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tap-colorize/-/tap-colorize-1.2.0.tgz#cdffa443c73d88d5a4b394c129f8734e2c32baab" @@ -5311,11 +4951,42 @@ trumpet@^1.7.1: readable-stream "^1.0.27-1" through2 "^1.0.0" +tslib@^1.8.0, tslib@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslint@^5.8.0: + version "5.17.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.17.0.tgz#f9f0ce2011d8e90debaa6e9b4975f24cd16852b8" + integrity sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^3.2.0" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.1" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + tty-browserify@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" @@ -5333,18 +5004,16 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^3.4.0: + version "3.5.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.2.tgz#a09e1dc69bc9551cadf17dba10ee42cf55e5d56c" + integrity sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA== + uglify-es@^3.3.7: version "3.3.9" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" @@ -5393,13 +5062,6 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -5495,7 +5157,7 @@ vm-browserify@~0.0.1: dependencies: indexof "0.0.1" -which@^1.2.8, which@^1.2.9: +which@^1.2.8: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -5514,23 +5176,11 @@ wordwrap@~0.0.2: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= -wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= - dependencies: - mkdirp "^0.5.1" - x256@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/x256/-/x256-0.0.2.tgz#c9af18876f7a175801d564fe70ad9e8317784934"