From 9b6d88c636c7837b6fb20c5275ef553e5cbc6c1e Mon Sep 17 00:00:00 2001 From: Dominik Piatek Date: Thu, 3 Aug 2023 14:24:41 +0100 Subject: [PATCH 1/8] Sync ably-js version for demo Update as newer types were incompatible. --- src/Cursors.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Cursors.ts b/src/Cursors.ts index f95ad8d1..f3cf9d43 100644 --- a/src/Cursors.ts +++ b/src/Cursors.ts @@ -153,7 +153,11 @@ export default class Cursors extends EventEmitter { } async getOthers(): Promise> { +<<<<<<< HEAD const self = this.space.members.getSelf(); +======= + const self = this.space.getSelf(); +>>>>>>> a7025d1 (Sync ably-js version for demo) if (!self) return {}; const allCursors = await this.getAll(); @@ -161,9 +165,12 @@ export default class Cursors extends EventEmitter { delete allCursorsFiltered[self.connectionId]; return allCursorsFiltered; } +<<<<<<< HEAD async getAll() { const channel = this.getChannel(); return await this.cursorHistory.getLastCursorUpdate(channel, this.options.paginationLimit); } +======= +>>>>>>> a7025d1 (Sync ably-js version for demo) } From 8ef61ae5d54b4d4dd8ceb0db35d7f242ebd44d51 Mon Sep 17 00:00:00 2001 From: Dominik Piatek Date: Thu, 3 Aug 2023 15:02:05 +0100 Subject: [PATCH 2/8] Refactor structure of presence updates This commit changes how we handle updates for presence spaces. An presence update becomes a PresenceMember: export type PresenceMember = { data: { profileUpdate: { id: string | null; current: ProfileData; }; locationUpdate: { id: string | null; previous: unknown; current: unknown; }; }; } & Omit; Which then gets translated for the developer to a SpaceMember: export type SpaceMember = { clientId: string; connectionId: string; isConnected: boolean; profileData: ProfileData; location: unknown; lastEvent: { name: Types.PresenceAction; timestamp: number; }; }; data on PresenceMember contains the last update for profileData an location. The current key is the value of these properties on SpaceMember. profileUpdate and locationUpdate contain an id. This id is set on publish, but only when we are providing new data, not copying already set data. The handlers check the id to decide if an update should be emitted (it will still be applied, and it should be the same). --- src/Cursors.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Cursors.ts b/src/Cursors.ts index f3cf9d43..f95ad8d1 100644 --- a/src/Cursors.ts +++ b/src/Cursors.ts @@ -153,11 +153,7 @@ export default class Cursors extends EventEmitter { } async getOthers(): Promise> { -<<<<<<< HEAD const self = this.space.members.getSelf(); -======= - const self = this.space.getSelf(); ->>>>>>> a7025d1 (Sync ably-js version for demo) if (!self) return {}; const allCursors = await this.getAll(); @@ -165,12 +161,9 @@ export default class Cursors extends EventEmitter { delete allCursorsFiltered[self.connectionId]; return allCursorsFiltered; } -<<<<<<< HEAD async getAll() { const channel = this.getChannel(); return await this.cursorHistory.getLastCursorUpdate(channel, this.options.paginationLimit); } -======= ->>>>>>> a7025d1 (Sync ably-js version for demo) } From b5b2a643f87a6c4d88e98b2080edd1a9c1ca064d Mon Sep 17 00:00:00 2001 From: Dominik Piatek Date: Mon, 24 Jul 2023 09:42:52 +0100 Subject: [PATCH 3/8] Add strict options to tsconfig --- tsconfig.base.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsconfig.base.json b/tsconfig.base.json index b2d4f476..ca0aa81c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -5,7 +5,9 @@ "target": "es6", "rootDir": "./src", "sourceMap": true, - "strict": false, + "strict": true, + "alwaysStrict": true, + "noImplicitThis": true, "esModuleInterop": true, "declaration": true, "moduleResolution": "node", From a33c0eaac8f6603f2aa84f9aa209dd533d41dafe Mon Sep 17 00:00:00 2001 From: Dominik Piatek Date: Mon, 7 Aug 2023 17:57:04 +0200 Subject: [PATCH 4/8] Update ts config to be strict --- src/CursorBatching.ts | 4 +-- src/CursorDispensing.ts | 9 ++--- src/Cursors.ts | 38 +++++++++++--------- src/Locations.ts | 4 +-- src/Space.ts | 6 +++- src/Spaces.test.ts | 16 +++++---- src/Spaces.ts | 8 ++++- src/utilities/EventEmitter.test.ts | 40 ++++++++++----------- src/utilities/EventEmitter.ts | 56 +++++++++++++----------------- src/utilities/math.ts | 2 +- src/utilities/test/fakes.ts | 20 ++++++++--- 11 files changed, 110 insertions(+), 93 deletions(-) diff --git a/src/CursorBatching.ts b/src/CursorBatching.ts index 5d0ecc79..b7e47830 100644 --- a/src/CursorBatching.ts +++ b/src/CursorBatching.ts @@ -44,14 +44,14 @@ export default class CursorBatching { this.outgoingBuffers.push(value); } - private async publishFromBuffer(channel, eventName: string) { + private async publishFromBuffer(channel: Types.RealtimeChannelPromise, eventName: string) { if (!this.isRunning) { this.isRunning = true; await this.batchToChannel(channel, eventName); } } - private async batchToChannel(channel, eventName: string) { + private async batchToChannel(channel: Types.RealtimeChannelPromise, eventName: string) { if (!this.hasMovement) { this.isRunning = false; return; diff --git a/src/CursorDispensing.ts b/src/CursorDispensing.ts index d73905d2..ae8df5c9 100644 --- a/src/CursorDispensing.ts +++ b/src/CursorDispensing.ts @@ -7,13 +7,8 @@ export default class CursorDispensing { private buffer: Record = {}; private handlerRunning: boolean = false; private timerIds: ReturnType[] = []; - private emitCursorUpdate: (update: CursorUpdate) => void; - private getCurrentBatchTime: () => number; - constructor(emitCursorUpdate, getCurrentBatchTime) { - this.emitCursorUpdate = emitCursorUpdate; - this.getCurrentBatchTime = getCurrentBatchTime; - } + constructor(private emitCursorUpdate: (update: CursorUpdate) => void, private getCurrentBatchTime: () => number) {} emitFromBatch(batchDispenseInterval: number) { if (!this.bufferHaveData()) { @@ -66,7 +61,7 @@ export default class CursorDispensing { } processBatch(message: RealtimeMessage) { - const updates = message.data || []; + const updates: CursorUpdate[] = message.data || []; updates.forEach((update) => { const enhancedMsg = { diff --git a/src/Cursors.ts b/src/Cursors.ts index f95ad8d1..563fbfd0 100644 --- a/src/Cursors.ts +++ b/src/Cursors.ts @@ -20,20 +20,6 @@ type CursorsEventMap = { export const CURSOR_UPDATE = 'cursorUpdate'; -const emitterHasListeners = (emitter) => { - const flattenEvents = (obj) => - Object.entries(obj) - .map((_, v) => v) - .flat(); - - return ( - emitter.any.length > 0 || - emitter.anyOnce.length > 0 || - flattenEvents(emitter.events).length > 0 || - flattenEvents(emitter.eventsOnce).length > 0 - ); -}; - export default class Cursors extends EventEmitter { private readonly cursorBatching: CursorBatching; private readonly cursorDispensing: CursorDispensing; @@ -92,9 +78,29 @@ export default class Cursors extends EventEmitter { private isUnsubscribed() { const channel = this.getChannel(); - return !emitterHasListeners(channel['subscriptions']); + + interface ChannelWithSubscriptions extends Types.RealtimeChannelPromise { + subscriptions: EventEmitter<{}>; + } + + const subscriptions = (channel as ChannelWithSubscriptions).subscriptions; + return !this.emitterHasListeners(subscriptions); } + private emitterHasListeners = (emitter: EventEmitter<{}>) => { + const flattenEvents = (obj: Record) => + Object.entries(obj) + .map((_, v) => v) + .flat(); + + return ( + emitter.any.length > 0 || + emitter.anyOnce.length > 0 || + flattenEvents(emitter.events).length > 0 || + flattenEvents(emitter.eventsOnce).length > 0 + ); + }; + subscribe>( listenerOrEvents?: K | K[] | EventListener, listener?: EventListener, @@ -136,7 +142,7 @@ export default class Cursors extends EventEmitter { } } - const hasListeners = emitterHasListeners(this); + const hasListeners = this.emitterHasListeners(this); if (!hasListeners) { const channel = this.getChannel(); diff --git a/src/Locations.ts b/src/Locations.ts index ea4d55a7..41eec662 100644 --- a/src/Locations.ts +++ b/src/Locations.ts @@ -117,14 +117,14 @@ export default class Locations extends EventEmitter { return this.space.members .getAll() .filter((member) => member.connectionId !== self?.connectionId) - .reduce((acc, member) => { + .reduce((acc: Record, member: SpaceMember) => { acc[member.connectionId] = member.location; return acc; }, {}); } getAll(): Record { - return this.space.members.getAll().reduce((acc, member) => { + return this.space.members.getAll().reduce((acc: Record, member: SpaceMember) => { acc[member.connectionId] = member.location; return acc; }, {}); diff --git a/src/Space.ts b/src/Space.ts index 1233f12a..c1deaeda 100644 --- a/src/Space.ts +++ b/src/Space.ts @@ -92,7 +92,11 @@ class Space extends EventEmitter { return new Promise((resolve) => { const presence = this.channel.presence; - presence['subscriptions'].once('enter', async () => { + interface PresenceWithSubscriptions extends Types.RealtimePresencePromise { + subscriptions: EventEmitter<{ enter: [unknown] }>; + } + + (presence as PresenceWithSubscriptions).subscriptions.once('enter', async () => { const presenceMessages = await presence.get(); const members = this.members.mapPresenceMembersToSpaceMembers(presenceMessages); diff --git a/src/Spaces.test.ts b/src/Spaces.test.ts index 3e859ca2..f204abab 100644 --- a/src/Spaces.test.ts +++ b/src/Spaces.test.ts @@ -1,17 +1,17 @@ import { it, describe, expect, expectTypeOf, vi, beforeEach } from 'vitest'; import { Realtime, Types } from 'ably/promises'; -import Spaces from './Spaces.js'; +import Spaces, { type ClientWithOptions } from './Spaces.js'; interface SpacesTestContext { - client: Types.RealtimePromise; + client: ClientWithOptions; } vi.mock('ably/promises'); describe('Spaces', () => { beforeEach((context) => { - context.client = new Realtime({ key: 'asd' }); + context.client = new Realtime({ key: 'asd' }) as ClientWithOptions; }); it('expects the injected client to be of the type RealtimePromise', ({ client }) => { @@ -32,14 +32,18 @@ describe('Spaces', () => { it('applies the agent header to an existing SDK instance', ({ client }) => { const spaces = new Spaces(client); - expect(client['options'].agents).toEqual({ 'ably-spaces': spaces.version, 'space-custom-client': true }); + expect(client.options.agents).toEqual({ + 'ably-spaces': spaces.version, + 'space-custom-client': true, + }); }); it('extend the agents array when it already exists', ({ client }) => { - client['options']['agents'] = { 'some-client': '1.2.3' }; + (client as ClientWithOptions).options.agents = { 'some-client': '1.2.3' }; const spaces = new Spaces(client); + const ablyClient = spaces.ably as ClientWithOptions; - expect(spaces.ably['options'].agents).toEqual({ + expect(ablyClient.options.agents).toEqual({ 'some-client': '1.2.3', 'ably-spaces': spaces.version, 'space-custom-client': true, diff --git a/src/Spaces.ts b/src/Spaces.ts index 1a8ef033..56acfea5 100644 --- a/src/Spaces.ts +++ b/src/Spaces.ts @@ -5,6 +5,12 @@ import Space from './Space.js'; import type { SpaceOptions } from './types.js'; import type { Subset } from './utilities/types.js'; +export interface ClientWithOptions extends Types.RealtimePromise { + options: { + agents?: Record; + }; +} + class Spaces { private spaces: Record = {}; ably: Types.RealtimePromise; @@ -13,7 +19,7 @@ class Spaces { constructor(client: Types.RealtimePromise) { this.ably = client; - this.addAgent(this.ably['options']); + this.addAgent((this.ably as ClientWithOptions)['options']); this.ably.time(); } diff --git a/src/utilities/EventEmitter.test.ts b/src/utilities/EventEmitter.test.ts index e96650ac..aa4c87f6 100644 --- a/src/utilities/EventEmitter.test.ts +++ b/src/utilities/EventEmitter.test.ts @@ -98,14 +98,14 @@ describe('EventEmitter', () => { it('adds a listener to the "any" set of event listeners', (context) => { context.eventEmitter['on'](context.spy); expect(context.eventEmitter['any']).toStrictEqual([context.spy]); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).toHaveBeenCalledOnce(); }); it('adds a listener to a provided field of an event listener', (context) => { context.eventEmitter['on']('myEvent', context.spy); expect(context.eventEmitter['events']['myEvent']).toStrictEqual([context.spy]); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).toHaveBeenCalledOnce(); }); @@ -114,11 +114,11 @@ describe('EventEmitter', () => { expect(context.eventEmitter['events']['myEvent']).toStrictEqual([context.spy]); expect(context.eventEmitter['events']['myOtherEvent']).toStrictEqual([context.spy]); expect(context.eventEmitter['events']['myThirdEvent']).toStrictEqual([context.spy]); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).toHaveBeenCalledTimes(1); - context.eventEmitter['emit']('myOtherEvent'); + context.eventEmitter['emit']('myOtherEvent', ''); expect(context.spy).toHaveBeenCalledTimes(2); - context.eventEmitter['emit']('myThirdEvent'); + context.eventEmitter['emit']('myThirdEvent', ''); expect(context.spy).toHaveBeenCalledTimes(3); }); }); @@ -148,7 +148,7 @@ describe('EventEmitter', () => { expect(context.eventEmitter['anyOnce']).toStrictEqual([]); expect(context.eventEmitter['events']).toStrictEqual(Object.create(null)); expect(context.eventEmitter['eventsOnce']).toStrictEqual(Object.create(null)); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).not.toHaveBeenCalled(); }); @@ -158,7 +158,7 @@ describe('EventEmitter', () => { expect(context.eventEmitter['anyOnce']).toStrictEqual([altListener]); expect(context.eventEmitter['events']['myEvent']).not.toContain(context.spy); expect(context.eventEmitter['eventsOnce']['myEvent']).not.toContain(context.spy); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).not.toHaveBeenCalled(); }); @@ -168,9 +168,9 @@ describe('EventEmitter', () => { expect(context.eventEmitter['anyOnce']).toStrictEqual([context.spy, altListener]); expect(context.eventEmitter['events']['myEvent']).not.toContain(context.spy); expect(context.eventEmitter['events']['myOtherEvent']).toContain(context.spy); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).toHaveBeenCalledTimes(2); - context.eventEmitter['emit']('myOtherEvent'); + context.eventEmitter['emit']('myOtherEvent', ''); expect(context.spy).toHaveBeenCalledTimes(3); }); @@ -182,11 +182,11 @@ describe('EventEmitter', () => { expect(eventEmitter['events']['myEvent']).toBe(undefined); expect(eventEmitter['events']['myOtherEvent']).toBe(undefined); expect(eventEmitter['events']['myThirdEvent']).toContain(specificListener); - eventEmitter['emit']('myEvent'); - eventEmitter['emit']('myOtherEvent'); + eventEmitter['emit']('myEvent', ''); + eventEmitter['emit']('myOtherEvent', ''); expect(specificListener).not.toHaveBeenCalled(); expect(eventEmitter['events']['myThirdEvent']).toContain(specificListener); - eventEmitter['emit']('myThirdEvent'); + eventEmitter['emit']('myThirdEvent', ''); expect(specificListener).toHaveBeenCalledOnce(); }); @@ -241,17 +241,17 @@ describe('EventEmitter', () => { it('adds a listener to anyOnce on calling `once` with a listener', (context) => { context.eventEmitter['once'](context.spy); expect(context.eventEmitter['anyOnce']).toHaveLength(1); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).toHaveBeenCalledOnce(); - context.eventEmitter['emit']('myOtherEvent'); + context.eventEmitter['emit']('myOtherEvent', ''); }); it('adds a listener to an eventOnce on calling `once` with a listener and event name', (context) => { context.eventEmitter['once']('myEvent', context.spy); expect(context.eventEmitter['eventsOnce']['myEvent']).toHaveLength(1); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).toHaveBeenCalledOnce(); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).toHaveBeenCalledOnce(); }); @@ -260,7 +260,7 @@ describe('EventEmitter', () => { expect(context.eventEmitter['eventsOnce']['myEvent']).toHaveLength(1); expect(context.eventEmitter['eventsOnce']['myOtherEvent']).toHaveLength(1); expect(context.eventEmitter['eventsOnce']['myThirdEvent']).toHaveLength(1); - expect(context.eventEmitter['emit']('myEvent')); + expect(context.eventEmitter['emit']('myEvent', '')); expect(context.eventEmitter['eventsOnce']['myEvent']).toBe(undefined); expect(context.eventEmitter['eventsOnce']['myOtherEvent']).toBe(undefined); expect(context.eventEmitter['eventsOnce']['myThirdEvent']).toBe(undefined); @@ -279,7 +279,7 @@ describe('EventEmitter', () => { context.eventEmitter['on']('myEvent', context.spy); expect(context.eventEmitter['listeners']('myEvent')).toContain(context.spy); expect(context.spy).not.toHaveBeenCalled(); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.spy).toHaveBeenCalledOnce(); // anyOnce must also be emptied expect(context.eventEmitter['anyOnce']).toStrictEqual([]); @@ -287,7 +287,7 @@ describe('EventEmitter', () => { it('emits any events in anyOnce on emitting specific events', (context) => { context.eventEmitter['on']('myEvent', context.spy); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.eventEmitter['anyOnce']).toStrictEqual([]); expect(context.spy).toHaveBeenCalledOnce(); }); @@ -295,7 +295,7 @@ describe('EventEmitter', () => { it('emits an event and removes it on being called for a once operation', (context) => { context.eventEmitter['once']('myEvent', context.spy); expect(context.eventEmitter['listeners']('myEvent')).toContain(context.spy); - context.eventEmitter['emit']('myEvent'); + context.eventEmitter['emit']('myEvent', ''); expect(context.eventEmitter['listeners']('myEvent')).toBe(null); expect(context.eventEmitter['anyOnce']).toStrictEqual([]); expect(context.spy).toHaveBeenCalledOnce(); diff --git a/src/utilities/EventEmitter.ts b/src/utilities/EventEmitter.ts index 0359f224..358b74c6 100644 --- a/src/utilities/EventEmitter.ts +++ b/src/utilities/EventEmitter.ts @@ -23,13 +23,13 @@ export function removeListener( eventFilter?: string, ) { let listeners: Function[] | Record; - let index; - let eventName; + let index: number; + let eventName: string; for (let targetListenersIndex = 0; targetListenersIndex < targetListeners.length; targetListenersIndex++) { listeners = targetListeners[targetListenersIndex]; - if (eventFilter) { + if (isString(eventFilter) && isObject(listeners)) { listeners = listeners[eventFilter]; } @@ -39,8 +39,9 @@ export function removeListener( } /* If events object has an event name key with no listeners then remove the key to stop the list growing indefinitely */ - if (eventFilter && listeners.length === 0) { - delete targetListeners[targetListenersIndex][eventFilter]; + const parentCollection = targetListeners[targetListenersIndex]; + if (eventFilter && listeners.length === 0 && isObject(parentCollection)) { + delete parentCollection[eventFilter]; } } else if (isObject(listeners)) { for (eventName in listeners) { @@ -58,21 +59,21 @@ export function inspect(args: unknown): string { } export class InvalidArgumentError extends Error { - constructor(...args) { + constructor(...args: [string | undefined]) { super(...args); } } -export type EventMap = Record; +export type EventMap = Record; // extract all the keys of an event map and use them as a type export type EventKey = string & keyof T; export type EventListener = (params: T) => void; export default class EventEmitter { - protected any: Array; - protected events: Record; - protected anyOnce: Array; - protected eventsOnce: Record; + any: Array; + events: Record; + anyOnce: Array; + eventsOnce: Record; constructor() { this.any = []; @@ -86,10 +87,7 @@ export default class EventEmitter { * @param listenerOrEvents (optional) the name of the event to listen to or the listener to be called. * @param listener (optional) the listener to be called. */ - protected on>( - listenerOrEvents?: K | K[] | EventListener, - listener?: EventListener, - ): void { + on>(listenerOrEvents?: K | K[] | EventListener, listener?: EventListener): void { // .on(() => {}) if (isFunction(listenerOrEvents)) { this.any.push(listenerOrEvents); @@ -120,10 +118,7 @@ export default class EventEmitter { * the listener is treated as an 'any' listener. * @param listener (optional) the listener to remove. If not supplied, all listeners are removed. */ - protected off>( - listenerOrEvents?: K | K[] | EventListener, - listener?: EventListener, - ): void { + off>(listenerOrEvents?: K | K[] | EventListener, listener?: EventListener): void { // .off() // don't use arguments.length === 0 here as don't won't handle // cases like .off(undefined) which is a valid call @@ -178,7 +173,7 @@ export default class EventEmitter { * @param event (optional) the name of the event, or none for 'any' * @return array of events, or null if none */ - protected listeners>(event: K): Function[] | null { + listeners>(event: K): Function[] | null { if (event) { const listeners = [...(this.events[event] ?? [])]; @@ -195,9 +190,9 @@ export default class EventEmitter { /** * Emit an event * @param event the event name - * @param args the arguments to pass to the listener + * @param arg the arguments to pass to the listener */ - emit>(event: K, ...args: unknown[] /* , args... */) { + emit>(event: K, arg: T[K]) { const eventThis = { event }; const listeners: Function[] = []; @@ -222,7 +217,7 @@ export default class EventEmitter { } listeners.forEach(function (listener) { - callListener(eventThis, listener, args); + callListener(eventThis, listener, [arg]); }); } @@ -231,7 +226,7 @@ export default class EventEmitter { * @param listenerOrEvents (optional) the name of the event to listen to * @param listener (optional) the listener to be called */ - protected once>( + once>( listenerOrEvents: K | K[] | EventListener, listener?: EventListener, ): void | Promise { @@ -245,12 +240,14 @@ export default class EventEmitter { // .once(["eventName"], () => {}) if (isArray(listenerOrEvents) && isFunction(listener)) { const self = this; + listenerOrEvents.forEach(function (eventName) { - const listenerWrapper = function (listenerThis: any) { - const innerArgs = Array.prototype.slice.call(arguments); + const listenerWrapper: EventListener = function (this: EventListener, listenerThis) { + const innerArgs = Array.prototype.slice.call(arguments) as [params: T[K]]; listenerOrEvents.forEach((eventName) => { self.off(eventName, this); }); + listener.apply(listenerThis, innerArgs); }; self.once(eventName, listenerWrapper); @@ -277,12 +274,7 @@ export default class EventEmitter { * @param listener the listener to be called * @param listenerArgs */ - protected whenState( - targetState: string, - currentState: string, - listener: EventListener, - ...listenerArgs: unknown[] - ) { + whenState(targetState: string, currentState: string, listener: EventListener, ...listenerArgs: unknown[]) { const eventThis = { event: targetState }; if (typeof targetState !== 'string' || typeof currentState !== 'string') { diff --git a/src/utilities/math.ts b/src/utilities/math.ts index 33bc7d7d..0e6c2a69 100644 --- a/src/utilities/math.ts +++ b/src/utilities/math.ts @@ -1,4 +1,4 @@ -function clamp(num, min, max) { +function clamp(num: number, min: number, max: number) { return Math.min(Math.max(num, min), max); } diff --git a/src/utilities/test/fakes.ts b/src/utilities/test/fakes.ts index 49e5c2a1..696b99d3 100644 --- a/src/utilities/test/fakes.ts +++ b/src/utilities/test/fakes.ts @@ -1,10 +1,14 @@ +import { Types } from 'ably'; + +import Space from '../../Space.js'; + import type { SpaceMember } from '../../types.js'; import type { PresenceMember } from '../../utilities/types.js'; // import { nanoidId } from '../../../__mocks__/nanoid/index.js'; const nanoidId = 'NanoidID'; -const enterPresenceMessage = { +const enterPresenceMessage: Types.PresenceMessage = { clientId: '1', data: { profileUpdate: { @@ -24,17 +28,23 @@ const enterPresenceMessage = { timestamp: 1, }; -const updatePresenceMessage = { +const updatePresenceMessage: Types.PresenceMessage = { ...enterPresenceMessage, action: 'update', }; -const leavePresenceMessage = { +const leavePresenceMessage: Types.PresenceMessage = { ...enterPresenceMessage, action: 'leave', }; -const createPresenceMessage = (type, override?) => { +type MessageMap = { + enter: typeof enterPresenceMessage; + update: typeof updatePresenceMessage; + leave: typeof leavePresenceMessage; +}; + +const createPresenceMessage = (type: T, override?: Partial) => { switch (type) { case 'enter': return { ...enterPresenceMessage, ...override }; @@ -47,7 +57,7 @@ const createPresenceMessage = (type, override?) => { } }; -const createPresenceEvent = (space, type, override?) => { +const createPresenceEvent = (space: Space, type: T, override?: Partial) => { space['onPresenceUpdate'](createPresenceMessage(type, override)); }; From d2d25484b44d477ae2fec17b1291675bfa84acb0 Mon Sep 17 00:00:00 2001 From: Dominik Piatek Date: Wed, 9 Aug 2023 17:11:27 +0100 Subject: [PATCH 5/8] Create rollup config for IIFE bundle --- package-lock.json | 13 +++++++------ package.json | 3 ++- rollup.config.mjs | 13 +++++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 rollup.config.mjs diff --git a/package-lock.json b/package-lock.json index e043b8ba..96fde898 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "husky": "^8.0.0", "mock-socket": "^9.1.5", "prettier": "^2.8.3", + "rollup": "^3.28.0", "typescript": "^4.9.5", "vitest": "^0.29.8" } @@ -3832,9 +3833,9 @@ } }, "node_modules/rollup": { - "version": "3.23.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.23.1.tgz", - "integrity": "sha512-ybRdFVHOoljGEFILHLd2g/qateqUdjE6YS41WXq4p3C/WwD3xtWxV4FYWETA1u9TeXQc5K8L8zHE5d/scOvrOQ==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -7384,9 +7385,9 @@ } }, "rollup": { - "version": "3.23.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.23.1.tgz", - "integrity": "sha512-ybRdFVHOoljGEFILHLd2g/qateqUdjE6YS41WXq4p3C/WwD3xtWxV4FYWETA1u9TeXQc5K8L8zHE5d/scOvrOQ==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", "dev": true, "requires": { "fsevents": "~2.3.2" diff --git a/package.json b/package.json index 011cf65c..3337b423 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "build": "npm run build:mjs && npm run build:cjs && npm run build:iife", "build:mjs": "npx tsc --project tsconfig.mjs.json && cp res/package.mjs.json dist/mjs/package.json", "build:cjs": "npx tsc --project tsconfig.cjs.json && cp res/package.cjs.json dist/cjs/package.json", - "build:iife": "rm -rf dist/iife && npx tsc --project tsconfig.iife.json && rollup dist/iife/index.js --file dist/iife/index.bundle.js --format iife --name Spaces -e ably -g ably:Ably", + "build:iife": "rm -rf dist/iife && npx tsc --project tsconfig.iife.json && rollup -c", "prepare": "husky install" }, "exports": { @@ -49,6 +49,7 @@ "husky": "^8.0.0", "mock-socket": "^9.1.5", "prettier": "^2.8.3", + "rollup": "^3.28.0", "typescript": "^4.9.5", "vitest": "^0.29.8" }, diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 00000000..a0381498 --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,13 @@ +export default { + input: 'dist/iife/index.js', + output: { + format: 'iife', + name: 'Spaces', + file: 'dist/iife/index.bundle.js', + globals: { + ably: 'Ably', + nanoid: 'nanoid', + }, + }, + external: ['ably', 'nanoid'], +}; From 578d13c5e9e6c5104be748edcf4557b132bd606d Mon Sep 17 00:00:00 2001 From: Dominik Piatek Date: Wed, 9 Aug 2023 17:11:59 +0100 Subject: [PATCH 6/8] Revert strict flag on demo --- demo/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/tsconfig.json b/demo/tsconfig.json index 6cb7ec2d..a7fc6fbf 100644 --- a/demo/tsconfig.json +++ b/demo/tsconfig.json @@ -15,7 +15,7 @@ "jsx": "react-jsx", /* Linting */ - "strict": false, + "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true From e872b842b8fe5553d9f758259b175c41298da433 Mon Sep 17 00:00:00 2001 From: Dominik Piatek Date: Wed, 9 Aug 2023 17:12:26 +0100 Subject: [PATCH 7/8] Fix comparsion on slide --- demo/src/components/CurrentSlide.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/src/components/CurrentSlide.tsx b/demo/src/components/CurrentSlide.tsx index bb9d68ad..97486756 100644 --- a/demo/src/components/CurrentSlide.tsx +++ b/demo/src/components/CurrentSlide.tsx @@ -10,7 +10,7 @@ interface Props { export const CurrentSlide = ({ slides }: Props) => { const containerRef = useRef(null); const { self } = useMembers(); - const slide = self?.location?.slide || 0; + const slide = parseInt(self?.location?.slide || '', 10) || 0; useTrackCursor(containerRef); From 3afcb3c7b0b45d324de9a7a73c290b2a23a916ac Mon Sep 17 00:00:00 2001 From: Dominik Piatek Date: Wed, 9 Aug 2023 17:14:50 +0100 Subject: [PATCH 8/8] Remove circular dependency --- src/CursorBatching.ts | 2 +- src/CursorConstants.ts | 1 + src/Cursors.test.ts | 3 ++- src/Cursors.ts | 3 +-- 4 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 src/CursorConstants.ts diff --git a/src/CursorBatching.ts b/src/CursorBatching.ts index b7e47830..08851e06 100644 --- a/src/CursorBatching.ts +++ b/src/CursorBatching.ts @@ -1,6 +1,6 @@ import { Types } from 'ably'; -import { CURSOR_UPDATE } from './Cursors.js'; +import { CURSOR_UPDATE } from './CursorConstants.js'; import type { CursorUpdate } from './types.js'; import type { CursorsOptions } from './types.js'; diff --git a/src/CursorConstants.ts b/src/CursorConstants.ts new file mode 100644 index 00000000..c08e3333 --- /dev/null +++ b/src/CursorConstants.ts @@ -0,0 +1 @@ +export const CURSOR_UPDATE = 'cursorUpdate'; diff --git a/src/Cursors.test.ts b/src/Cursors.test.ts index c837e070..d04f7626 100644 --- a/src/Cursors.test.ts +++ b/src/Cursors.test.ts @@ -2,7 +2,8 @@ import { it, describe, expect, vi, beforeEach, vitest, afterEach } from 'vitest' import { Realtime, Types } from 'ably/promises'; import Space from './Space.js'; -import Cursors, { CURSOR_UPDATE } from './Cursors.js'; +import Cursors from './Cursors.js'; +import { CURSOR_UPDATE } from './CursorConstants.js'; import { createPresenceMessage } from './utilities/test/fakes.js'; import CursorBatching from './CursorBatching.js'; import CursorDispensing from './CursorDispensing.js'; diff --git a/src/Cursors.ts b/src/Cursors.ts index 563fbfd0..98b7aeba 100644 --- a/src/Cursors.ts +++ b/src/Cursors.ts @@ -10,6 +10,7 @@ import EventEmitter, { type EventListener, } from './utilities/EventEmitter.js'; import CursorHistory from './CursorHistory.js'; +import { CURSOR_UPDATE } from './CursorConstants.js'; import type { CursorsOptions, CursorUpdate } from './types.js'; import type { RealtimeMessage } from './utilities/types.js'; @@ -18,8 +19,6 @@ type CursorsEventMap = { cursorsUpdate: CursorUpdate; }; -export const CURSOR_UPDATE = 'cursorUpdate'; - export default class Cursors extends EventEmitter { private readonly cursorBatching: CursorBatching; private readonly cursorDispensing: CursorDispensing;