From 052c0ccad6ad882cfb532cb9de367157bc18a0e3 Mon Sep 17 00:00:00 2001 From: Pavel Mikhalkevich Date: Sun, 12 Nov 2023 19:21:21 +0500 Subject: [PATCH 01/16] feat: add 'deriveOptions' to useChannel args --- .../react-hooks/src/AblyReactHooks.ts | 1 + src/platform/react-hooks/src/fakes/ably.ts | 5 ++ .../react-hooks/src/hooks/useChannel.test.tsx | 65 +++++++++++++++++++ .../react-hooks/src/hooks/useChannel.ts | 19 ++++-- 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 2742f29a7e..0b31381c87 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -3,6 +3,7 @@ import { Types } from '../../../../ably.js'; export type ChannelNameAndOptions = { channelName: string; options?: Types.ChannelOptions; + deriveOptions?: Types.DeriveOptions; id?: string; subscribeOnly?: boolean; skip?: boolean; diff --git a/src/platform/react-hooks/src/fakes/ably.ts b/src/platform/react-hooks/src/fakes/ably.ts index a94cf735f1..0823fe23b8 100644 --- a/src/platform/react-hooks/src/fakes/ably.ts +++ b/src/platform/react-hooks/src/fakes/ably.ts @@ -107,6 +107,11 @@ export class ClientChannelsCollection { return channelConnection; } } + + public getDerived(name: string, options: Types.DeriveOptions): ClientSingleChannelConnection { + options; + return this.get(name); + } } export class ClientSingleChannelConnection extends EventEmitter { diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index b499c05158..63652d06b7 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -176,6 +176,59 @@ describe('useChannel', () => { }); }); +describe('useChannel with deriveOptions', () => { + let channels: FakeAblyChannels; + let ablyClient: FakeAblySdk; + let anotherClient: FakeAblySdk; + + const Channels = { + tasks: 'tasks', + alerts: 'alerts', + }; + beforeEach(() => { + channels = new FakeAblyChannels([Channels.tasks, Channels.alerts]); + ablyClient = new FakeAblySdk().connectTo(channels); + anotherClient = new FakeAblySdk().connectTo(channels); + }); + + it('component updates when new message arrives', async () => { + renderInCtxProvider( + ablyClient, + + ); + await act(async () => { + await anotherClient.channels + .get('tasks') + .publish({ text: 'A new task for you', extras: { headers: { user: 'robert.pike@domain.io' } } }); + }); + + const messageUl = screen.getAllByRole('derived-channel-messages')[0]; + expect(messageUl.childElementCount).toBe(1); + expect(messageUl.children[0].innerHTML).toBe('A new task for you'); + }); + + it('component will not update if message filtered out', async () => { + renderInCtxProvider( + ablyClient, + + ); + await act(async () => { + await anotherClient.channels + .get('tasks') + .publish({ text: 'This one is for another Rob', extras: { headers: { user: 'robert.griesemer@domain.io' } } }); + }); + + const messageUl = screen.getAllByRole('derived-channel-messages')[0]; + expect(messageUl.childElementCount).toBe(0); + }); +}); + const UseChannelComponentMultipleClients = () => { const [messages, updateMessages] = useState([]); useChannel({ channelName: 'blah' }, (message) => { @@ -201,6 +254,18 @@ const UseChannelComponent = ({ skip }: { skip?: boolean }) => { return ; }; +const UseDerivedChannelComponent = ({ channelName, deriveOptions }) => { + const [messages, setMessages] = useState([]); + + useChannel({ channelName, deriveOptions }, (message) => { + setMessages((prev) => [...prev, message]); + }); + + const messagePreviews = messages.map((msg, index) =>
  • {msg.data.text}
  • ); + + return
      {messagePreviews}
    ; +}; + interface UseChannelStateErrorsComponentProps { onConnectionError?: (err: Types.ErrorInfo) => unknown; onChannelError?: (err: Types.ErrorInfo) => unknown; diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index d747281a7d..2321f5e3f7 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -37,18 +37,23 @@ export function useChannel( const ably = useAbly(channelHookOptions.id); - const { channelName, options: channelOptions, skip } = channelHookOptions; + const { channelName, options: channelOptions, deriveOptions, skip } = channelHookOptions; const channelEvent = typeof eventOrCallback === 'string' ? eventOrCallback : null; const ablyMessageCallback = typeof eventOrCallback === 'string' ? callback : eventOrCallback; + const deriveOptionsRef = useRef(deriveOptions); const channelOptionsRef = useRef(channelOptions); const ablyMessageCallbackRef = useRef(ablyMessageCallback); - const channel = useMemo( - () => ably.channels.get(channelName, channelOptionsWithAgent(channelOptionsRef.current)), - [ably, channelName] - ); + const channel = useMemo(() => { + const derived = deriveOptionsRef.current; + const withAgent = channelOptionsWithAgent(channelOptionsRef.current); + const channel = derived + ? ably.channels.getDerived(channelName, derived, withAgent) + : ably.channels.get(channelName, withAgent); + return channel; + }, [ably, channelName]); const { connectionError, channelError } = useStateErrors(channelHookOptions); @@ -59,6 +64,10 @@ export function useChannel( channelOptionsRef.current = channelOptions; }, [channel, channelOptions]); + useEffect(() => { + deriveOptionsRef.current = deriveOptions; + }, [deriveOptions]); + useEffect(() => { ablyMessageCallbackRef.current = ablyMessageCallback; }, [ablyMessageCallback]); From c910d7cc8357d92ea1f242cb21703efa26c1be4a Mon Sep 17 00:00:00 2001 From: Pavel Mikhalkevich Date: Sun, 12 Nov 2023 20:36:38 +0500 Subject: [PATCH 02/16] feat: adjust 'Channel' class in 'fake' ably --- src/platform/react-hooks/src/fakes/ably.ts | 46 +++++++++++++++-- .../react-hooks/src/hooks/useChannel.test.tsx | 51 ++++++++++++++++++- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/platform/react-hooks/src/fakes/ably.ts b/src/platform/react-hooks/src/fakes/ably.ts index 0823fe23b8..6c298bfe9e 100644 --- a/src/platform/react-hooks/src/fakes/ably.ts +++ b/src/platform/react-hooks/src/fakes/ably.ts @@ -1,4 +1,5 @@ import { Types } from 'ably'; +import { search } from 'jmespath'; export class FakeAblySdk { public clientId: string; @@ -89,7 +90,7 @@ class Connection extends EventEmitter { export class ClientChannelsCollection { private client: FakeAblySdk; private channels: FakeAblyChannels; - private _channelConnections: Map; + private _channelConnections: Map; constructor(client: FakeAblySdk, channels: FakeAblyChannels) { this.client = client; @@ -108,9 +109,14 @@ export class ClientChannelsCollection { } } - public getDerived(name: string, options: Types.DeriveOptions): ClientSingleChannelConnection { - options; - return this.get(name); + public getDerived(name: string, options: Types.DeriveOptions): ClientSingleDerivedChannelConnection { + let channelConnection = this._channelConnections.get(name); + if (channelConnection) return channelConnection as ClientSingleDerivedChannelConnection; + + const channel = this.channels.get(name); + channelConnection = new ClientSingleDerivedChannelConnection(this.client, channel, options); + this._channelConnections.set(name, channelConnection); + return channelConnection; } } @@ -152,6 +158,32 @@ export class ClientSingleChannelConnection extends EventEmitter { } } +export class ClientSingleDerivedChannelConnection extends EventEmitter { + private client: FakeAblySdk; + private channel: Channel; + private deriveOpts: Types.DeriveOptions; + + constructor(client: FakeAblySdk, channel: Channel, deriveOptions?: Types.DeriveOptions) { + super(); + this.client = client; + this.channel = channel; + this.deriveOpts = deriveOptions; + } + + public async subscribe( + eventOrCallback: Types.messageCallback | string | Array, + listener?: Types.messageCallback + ) { + if (typeof eventOrCallback === 'function') eventOrCallback.deriveOptions = this.deriveOpts; + if (typeof listener === 'function') listener.deriveOpts = this.deriveOpts; + this.channel.subscribe(this.client.clientId, eventOrCallback, listener); + } + + public unsubscribe() { + this.channel.subscriptionsPerClient.delete(this.client.clientId); + } +} + export class ClientPresenceConnection { private client: FakeAblySdk; private presence: ChannelPresence; @@ -251,7 +283,11 @@ export class Channel { } for (const subscription of subs) { - subscription(messageEnvelope); + const filter = subscription.deriveOptions?.filter; + if (!filter) return subscription(messageEnvelope); + const headers = messageEnvelope.data?.extras?.headers; + const found = search({ headers }, filter); + if (found) subscription(messageEnvelope); } } diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index 63652d06b7..813a4f06fc 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -191,12 +191,25 @@ describe('useChannel with deriveOptions', () => { anotherClient = new FakeAblySdk().connectTo(channels); }); + it('component can use "useChannel" with "deriveOptions" and renders nothing by default', async () => { + renderInCtxProvider( + ablyClient, + + ); + const messageUl = screen.getAllByRole('derived-channel-messages')[0]; + + expect(messageUl.childElementCount).toBe(0); + }); + it('component updates when new message arrives', async () => { renderInCtxProvider( ablyClient, ); await act(async () => { @@ -215,7 +228,7 @@ describe('useChannel with deriveOptions', () => { ablyClient, ); await act(async () => { @@ -227,6 +240,40 @@ describe('useChannel with deriveOptions', () => { const messageUl = screen.getAllByRole('derived-channel-messages')[0]; expect(messageUl.childElementCount).toBe(0); }); + + it('component will update if some messages qualify', async () => { + renderInCtxProvider( + ablyClient, + + ); + await act(async () => { + const channel = anotherClient.channels.get('tasks'); + await channel.publish({ + text: 'This one is for another Rob', + extras: { headers: { user: 'robert.griesemer@domain.io' } }, + }); + await channel.publish({ + text: 'This one is for the whole domain', + extras: { headers: { company: 'domain' } }, + }); + await channel.publish({ + text: 'This one is for Ken', + extras: { headers: { user: 'ken.thompson@domain.io' } }, + }); + await channel.publish({ + text: 'This one is also a domain-wide fan-out', + extras: { headers: { company: 'domain' } }, + }); + }); + + const messageUl = screen.getAllByRole('derived-channel-messages')[0]; + expect(messageUl.childElementCount).toBe(2); + expect(messageUl.children[0].innerHTML).toBe('This one is for the whole domain'); + expect(messageUl.children[1].innerHTML).toBe('This one is also a domain-wide fan-out'); + }); }); const UseChannelComponentMultipleClients = () => { From cf939ad22a4f67d00d36186feb9cf222424679b4 Mon Sep 17 00:00:00 2001 From: Pavel Mikhalkevich Date: Sun, 12 Nov 2023 21:30:47 +0500 Subject: [PATCH 03/16] feat: add test from multiple clients --- .../react-hooks/src/hooks/useChannel.test.tsx | 84 +++++++++++++++---- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index 813a4f06fc..090a7f96ce 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -177,27 +177,27 @@ describe('useChannel', () => { }); describe('useChannel with deriveOptions', () => { - let channels: FakeAblyChannels; - let ablyClient: FakeAblySdk; - let anotherClient: FakeAblySdk; - const Channels = { tasks: 'tasks', alerts: 'alerts', }; + + let channels: FakeAblyChannels; + let ablyClient: FakeAblySdk; + let anotherClient: FakeAblySdk; + let yetAnotherClient: FakeAblySdk; + beforeEach(() => { channels = new FakeAblyChannels([Channels.tasks, Channels.alerts]); ablyClient = new FakeAblySdk().connectTo(channels); anotherClient = new FakeAblySdk().connectTo(channels); + yetAnotherClient = new FakeAblySdk().connectTo(channels); }); it('component can use "useChannel" with "deriveOptions" and renders nothing by default', async () => { renderInCtxProvider( ablyClient, - + ); const messageUl = screen.getAllByRole('derived-channel-messages')[0]; @@ -210,11 +210,11 @@ describe('useChannel with deriveOptions', () => { + /> ); await act(async () => { await anotherClient.channels - .get('tasks') + .get(Channels.tasks) .publish({ text: 'A new task for you', extras: { headers: { user: 'robert.pike@domain.io' } } }); }); @@ -229,11 +229,11 @@ describe('useChannel with deriveOptions', () => { + /> ); await act(async () => { await anotherClient.channels - .get('tasks') + .get(Channels.tasks) .publish({ text: 'This one is for another Rob', extras: { headers: { user: 'robert.griesemer@domain.io' } } }); }); @@ -247,10 +247,10 @@ describe('useChannel with deriveOptions', () => { + /> ); await act(async () => { - const channel = anotherClient.channels.get('tasks'); + const channel = anotherClient.channels.get(Channels.tasks); await channel.publish({ text: 'This one is for another Rob', extras: { headers: { user: 'robert.griesemer@domain.io' } }, @@ -274,6 +274,42 @@ describe('useChannel with deriveOptions', () => { expect(messageUl.children[0].innerHTML).toBe('This one is for the whole domain'); expect(messageUl.children[1].innerHTML).toBe('This one is also a domain-wide fan-out'); }); + + it('component can use "useChannel" with multiple clients', async () => { + const cliendId = 'client'; + const anotherClientId = 'anotherClient'; + + render( + + + + + + ); + + await act(async () => { + await yetAnotherClient.channels.get(Channels.tasks).publish({ + text: 'A task for Griesemer', + extras: { headers: { user: 'robert.griesemer@domain.io' } }, + }); + await yetAnotherClient.channels.get(Channels.alerts).publish({ + text: 'A company-wide alert', + extras: { headers: { company: 'domain' } }, + }); + }); + + const messageUl = screen.getAllByRole('derived-channel-messages')[0]; + + expect(messageUl.childElementCount).toBe(2); + expect(messageUl.children[0].innerHTML).toBe('A task for Griesemer'); + expect(messageUl.children[1].innerHTML).toBe('A company-wide alert'); + }); }); const UseChannelComponentMultipleClients = () => { @@ -301,6 +337,26 @@ const UseChannelComponent = ({ skip }: { skip?: boolean }) => { return
      {messagePreviews}
    ; }; +const UseDerivedChannelComponentMultipleClients = ({ + channelName, + clientId, + anotherClientId, + anotherChannelName, + deriveOptions, +}) => { + const [messages, setMessages] = useState([]); + useChannel({ id: clientId, channelName, deriveOptions }, (message) => { + setMessages((prev) => [...prev, message]); + }); + useChannel({ id: anotherClientId, channelName: anotherChannelName, deriveOptions }, (message) => { + setMessages((prev) => [...prev, message]); + }); + + const messagePreviews = messages.map((msg, index) =>
  • {msg.data.text}
  • ); + + return
      {messagePreviews}
    ; +}; + const UseDerivedChannelComponent = ({ channelName, deriveOptions }) => { const [messages, setMessages] = useState([]); From 51574e0bfe082e313585c29439fae3000f2b7fc9 Mon Sep 17 00:00:00 2001 From: Pavel Mikhalkevich Date: Sun, 12 Nov 2023 21:31:50 +0500 Subject: [PATCH 04/16] feat: add 'jmespath' lib typedefs --- package-lock.json | 13 +++++++++++++ package.json | 1 + 2 files changed, 14 insertions(+) diff --git a/package-lock.json b/package-lock.json index d88155956b..c58c1e93fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@ably/vcdiff-decoder": "1.0.4", "@testing-library/react": "^13.3.0", "@types/crypto-js": "^4.0.1", + "@types/jmespath": "^0.15.2", "@types/node": "^15.0.0", "@types/request": "^2.48.7", "@types/ws": "^8.2.0", @@ -1250,6 +1251,12 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "node_modules/@types/jmespath": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz", + "integrity": "sha512-pegh49FtNsC389Flyo9y8AfkVIZn9MMPE9yJrO9svhq6Fks2MwymULWjZqySuxmctd3ZH4/n7Mr98D+1Qo5vGA==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -15146,6 +15153,12 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "@types/jmespath": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz", + "integrity": "sha512-pegh49FtNsC389Flyo9y8AfkVIZn9MMPE9yJrO9svhq6Fks2MwymULWjZqySuxmctd3ZH4/n7Mr98D+1Qo5vGA==", + "dev": true + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", diff --git a/package.json b/package.json index accfca59d6..4978380866 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@ably/vcdiff-decoder": "1.0.4", "@testing-library/react": "^13.3.0", "@types/crypto-js": "^4.0.1", + "@types/jmespath": "^0.15.2", "@types/node": "^15.0.0", "@types/request": "^2.48.7", "@types/ws": "^8.2.0", From 25b1c9731c361260abea79085ece3a98e9892291 Mon Sep 17 00:00:00 2001 From: Pavel Mikhalkevich Date: Mon, 13 Nov 2023 00:25:35 +0500 Subject: [PATCH 05/16] feat: add complete test suite for use derived channel --- .../react-hooks/src/hooks/useChannel.test.tsx | 248 ++++++++++++++++-- 1 file changed, 225 insertions(+), 23 deletions(-) diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index 090a7f96ce..7bba8f65ff 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -134,18 +134,10 @@ describe('useChannel', () => { it('should use the latest version of the message callback', async () => { let callbackCount = 0; - const TestComponent = () => { - const [count, setCount] = React.useState(0); - - useChannel('blah', () => { - callbackCount++; - setCount(count + 1); - }); - - return
    {count}
    ; - }; - - renderInCtxProvider(ablyClient, ); + renderInCtxProvider( + ablyClient, + callbackCount++} /> + ); await act(async () => { ablyClient.channels.get('blah').publish({ text: 'test message 1' }); @@ -241,7 +233,7 @@ describe('useChannel with deriveOptions', () => { expect(messageUl.childElementCount).toBe(0); }); - it('component will update if some messages qualify', async () => { + it('component will update with only those messages that qualify', async () => { renderInCtxProvider( ablyClient, { deriveOptions={{ filter: 'headers.user == `"robert.pike@domain.io"` || headers.company == `"domain"`' }} /> ); + await act(async () => { const channel = anotherClient.channels.get(Channels.tasks); await channel.publish({ @@ -287,7 +280,9 @@ describe('useChannel with deriveOptions', () => { channelName={Channels.tasks} anotherClientId={anotherClientId} anotherChannelName={Channels.alerts} - deriveOptions={{ filter: 'headers.user == `"robert.griesemer@domain.io"` || headers.company == `"domain"`' }} + deriveOptions={{ + filter: 'headers.user == `"robert.griesemer@domain.io"` || headers.company == `"domain"`', + }} /> @@ -310,6 +305,158 @@ describe('useChannel with deriveOptions', () => { expect(messageUl.children[0].innerHTML).toBe('A task for Griesemer'); expect(messageUl.children[1].innerHTML).toBe('A company-wide alert'); }); + + it('handles channel errors', async () => { + const onChannelError = vi.fn(); + const reason = { message: 'channel error occurred' }; + + renderInCtxProvider( + ablyClient, + + ); + + const channelErrorElem = screen.getByRole('channelError'); + expect(onChannelError).toHaveBeenCalledTimes(0); + expect(channelErrorElem.innerHTML).toEqual(''); + + act(() => ablyClient.channels.get(Channels.alerts).emit('failed', { reason })); + + expect(channelErrorElem.innerHTML).toEqual(reason.message); + expect(onChannelError).toHaveBeenCalledTimes(1); + expect(onChannelError).toHaveBeenCalledWith(reason); + }); + + it('handles connection errors', async () => { + const onConnectionError = vi.fn(); + const reason = { message: 'failed to establish connection' }; + + renderInCtxProvider( + ablyClient, + + ); + + const channelErrorElem = screen.getByRole('connectionError'); + expect(onConnectionError).toHaveBeenCalledTimes(0); + expect(channelErrorElem.innerHTML).toEqual(''); + + act(() => ablyClient.connection.emit('failed', { reason })); + + expect(channelErrorElem.innerHTML).toEqual(reason.message); + expect(onConnectionError).toHaveBeenCalledTimes(1); + expect(onConnectionError).toHaveBeenCalledWith(reason); + }); + + it('wildcard filter', async () => { + renderInCtxProvider( + ablyClient, + + ); + + await act(async () => { + const text = 'Will receive this text due to wildcard filter'; + await anotherClient.channels.get(Channels.alerts).publish({ text }); + }); + + const messageUl = screen.getAllByRole('derived-channel-messages')[0]; + expect(messageUl.childElementCount).toBe(1); + }); + + it('skip param', async () => { + renderInCtxProvider( + ablyClient, + + ); + + await act(async () => { + const text = 'Will skip due to "skip=true"'; + await anotherClient.channels.get(Channels.alerts).publish({ text }); + }); + + const messageUl = screen.getAllByRole('derived-channel-messages')[0]; + expect(messageUl.childElementCount).toBe(0); + }); + + it('should use the latest version of the message callback', async () => { + let callbackCount = 0; + + renderInCtxProvider( + ablyClient, + callbackCount++} + /> + ); + + await act(async () => { + const channel = anotherClient.channels.get(Channels.tasks); + await channel.publish({ + text: 'This one is for another Rob', + extras: { headers: { user: 'robert.griesemer@domain.io' } }, + }); + await channel.publish({ + text: 'This one is for the whole domain', + extras: { headers: { company: 'domain' } }, + }); + await channel.publish({ + text: 'This one is for Ken', + extras: { headers: { user: 'ken.thompson@domain.io' } }, + }); + await channel.publish({ + text: 'This one is also a domain-wide fan-out', + extras: { headers: { company: 'domain' } }, + }); + await channel.publish({ + text: 'This one for Mr.Pike will also get through...', + extras: { headers: { user: 'robert.pike@domain.io' } }, + }); + await channel.publish({ + text: '.... as well as this message', + extras: { headers: { user: 'robert.pike@domain.io' } }, + }); + }); + + expect(callbackCount).toBe(4); + expect(screen.getByRole('counter').innerHTML).toEqual(`${callbackCount}`); + }); + + it('should re-subscribe if event name has changed', async () => { + const channel = ablyClient.channels.get(Channels.alerts); + channel.subscribe = vi.fn(); + channel.unsubscribe = vi.fn(); + + const eventName = 'event1'; + const newEventName = 'event2'; + + renderInCtxProvider( + ablyClient, + + ); + + await waitFor(() => expect(channel.subscribe).toHaveBeenCalledWith(eventName, expect.any(Function))); + + await waitFor(() => expect(channel.unsubscribe).toHaveBeenCalledWith(eventName, expect.any(Function))); + + expect(channel.subscribe).toHaveBeenCalledWith(newEventName, expect.any(Function)); + }); }); const UseChannelComponentMultipleClients = () => { @@ -337,13 +484,21 @@ const UseChannelComponent = ({ skip }: { skip?: boolean }) => { return
      {messagePreviews}
    ; }; +interface UseDerivedChannelComponentMultipleClientsProps { + clientId: string; + channelName: string; + anotherClientId: string; + anotherChannelName: string; + deriveOptions: Types.DeriveOptions; +} + const UseDerivedChannelComponentMultipleClients = ({ channelName, clientId, anotherClientId, anotherChannelName, deriveOptions, -}) => { +}: UseDerivedChannelComponentMultipleClientsProps) => { const [messages, setMessages] = useState([]); useChannel({ id: clientId, channelName, deriveOptions }, (message) => { setMessages((prev) => [...prev, message]); @@ -357,10 +512,16 @@ const UseDerivedChannelComponentMultipleClients = ({ return
      {messagePreviews}
    ; }; -const UseDerivedChannelComponent = ({ channelName, deriveOptions }) => { +interface UseDerivedChannelComponentProps { + channelName: string; + deriveOptions: Types.DeriveOptions; + skip?: boolean; +} + +const UseDerivedChannelComponent = ({ channelName, deriveOptions, skip = false }: UseDerivedChannelComponentProps) => { const [messages, setMessages] = useState([]); - useChannel({ channelName, deriveOptions }, (message) => { + useChannel({ channelName, deriveOptions, skip }, (message) => { setMessages((prev) => [...prev, message]); }); @@ -372,10 +533,18 @@ const UseDerivedChannelComponent = ({ channelName, deriveOptions }) => { interface UseChannelStateErrorsComponentProps { onConnectionError?: (err: Types.ErrorInfo) => unknown; onChannelError?: (err: Types.ErrorInfo) => unknown; + channelName?: string; + deriveOptions?: Types.DeriveOptions; } -const UseChannelStateErrorsComponent = ({ onConnectionError, onChannelError }: UseChannelStateErrorsComponentProps) => { - const { connectionError, channelError } = useChannel({ channelName: 'blah', onConnectionError, onChannelError }); +const UseChannelStateErrorsComponent = ({ + onConnectionError, + onChannelError, + channelName = 'blah', + deriveOptions, +}: UseChannelStateErrorsComponentProps) => { + const opts = { channelName, deriveOptions, onConnectionError, onChannelError }; + const { connectionError, channelError } = useChannel(opts); return ( <> @@ -385,14 +554,47 @@ const UseChannelStateErrorsComponent = ({ onConnectionError, onChannelError }: U ); }; -const ChangingEventComponent = ({ newEventName }: { newEventName: string }) => { - const [eventName, setEventName] = useState('event1'); +interface LatestMessageCallbackComponentProps { + channelName: string; + deriveOptions?: Types.DeriveOptions; + callback: () => any; +} + +const LatestMessageCallbackComponent = ({ + channelName, + deriveOptions, + callback, +}: LatestMessageCallbackComponentProps) => { + const [count, setCount] = React.useState(0); + + useChannel({ channelName, deriveOptions }, () => { + callback(); + setCount((count) => count + 1); + }); + + return
    {count}
    ; +}; + +interface ChangingEventComponentProps { + newEventName: string; + channelName?: string; + deriveOptions?: Types.DeriveOptions; + eventName?: string; +} + +const ChangingEventComponent = ({ + channelName = 'blah', + eventName = 'event1', + newEventName, + deriveOptions, +}: ChangingEventComponentProps) => { + const [currentEventName, setCurrentEventName] = useState(eventName); - useChannel('blah', eventName, vi.fn()); + useChannel({ channelName, deriveOptions }, currentEventName, vi.fn()); useEffect(() => { const timeoutId = setTimeout(() => { - setEventName(newEventName); + setCurrentEventName(newEventName); }, 50); return () => clearTimeout(timeoutId); From 9bd2e77d0262ade81907d471d1c352120735b066 Mon Sep 17 00:00:00 2001 From: Pavel Mikhalkevich Date: Mon, 13 Nov 2023 23:48:41 +0500 Subject: [PATCH 06/16] feat: do not use async syntax for sync fake-ably ops --- .../react-hooks/src/hooks/useChannel.test.tsx | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index 7bba8f65ff..4cd2e7e3a1 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -204,8 +204,9 @@ describe('useChannel with deriveOptions', () => { deriveOptions={{ filter: 'headers.user == `"robert.pike@domain.io"`' }} /> ); - await act(async () => { - await anotherClient.channels + + act(() => { + anotherClient.channels .get(Channels.tasks) .publish({ text: 'A new task for you', extras: { headers: { user: 'robert.pike@domain.io' } } }); }); @@ -223,8 +224,9 @@ describe('useChannel with deriveOptions', () => { deriveOptions={{ filter: 'headers.user == `"robert.pike@domain.io"`' }} /> ); - await act(async () => { - await anotherClient.channels + + act(() => { + anotherClient.channels .get(Channels.tasks) .publish({ text: 'This one is for another Rob', extras: { headers: { user: 'robert.griesemer@domain.io' } } }); }); @@ -242,21 +244,21 @@ describe('useChannel with deriveOptions', () => { /> ); - await act(async () => { + act(() => { const channel = anotherClient.channels.get(Channels.tasks); - await channel.publish({ + channel.publish({ text: 'This one is for another Rob', extras: { headers: { user: 'robert.griesemer@domain.io' } }, }); - await channel.publish({ + channel.publish({ text: 'This one is for the whole domain', extras: { headers: { company: 'domain' } }, }); - await channel.publish({ + channel.publish({ text: 'This one is for Ken', extras: { headers: { user: 'ken.thompson@domain.io' } }, }); - await channel.publish({ + channel.publish({ text: 'This one is also a domain-wide fan-out', extras: { headers: { company: 'domain' } }, }); @@ -288,12 +290,12 @@ describe('useChannel with deriveOptions', () => { ); - await act(async () => { - await yetAnotherClient.channels.get(Channels.tasks).publish({ + act(() => { + yetAnotherClient.channels.get(Channels.tasks).publish({ text: 'A task for Griesemer', extras: { headers: { user: 'robert.griesemer@domain.io' } }, }); - await yetAnotherClient.channels.get(Channels.alerts).publish({ + yetAnotherClient.channels.get(Channels.alerts).publish({ text: 'A company-wide alert', extras: { headers: { company: 'domain' } }, }); @@ -361,9 +363,9 @@ describe('useChannel with deriveOptions', () => { >
    ); - await act(async () => { + act(() => { const text = 'Will receive this text due to wildcard filter'; - await anotherClient.channels.get(Channels.alerts).publish({ text }); + anotherClient.channels.get(Channels.alerts).publish({ text }); }); const messageUl = screen.getAllByRole('derived-channel-messages')[0]; @@ -380,9 +382,9 @@ describe('useChannel with deriveOptions', () => { >
    ); - await act(async () => { + act(() => { const text = 'Will skip due to "skip=true"'; - await anotherClient.channels.get(Channels.alerts).publish({ text }); + anotherClient.channels.get(Channels.alerts).publish({ text }); }); const messageUl = screen.getAllByRole('derived-channel-messages')[0]; @@ -401,29 +403,29 @@ describe('useChannel with deriveOptions', () => { /> ); - await act(async () => { + act(() => { const channel = anotherClient.channels.get(Channels.tasks); - await channel.publish({ + channel.publish({ text: 'This one is for another Rob', extras: { headers: { user: 'robert.griesemer@domain.io' } }, }); - await channel.publish({ + channel.publish({ text: 'This one is for the whole domain', extras: { headers: { company: 'domain' } }, }); - await channel.publish({ + channel.publish({ text: 'This one is for Ken', extras: { headers: { user: 'ken.thompson@domain.io' } }, }); - await channel.publish({ + channel.publish({ text: 'This one is also a domain-wide fan-out', extras: { headers: { company: 'domain' } }, }); - await channel.publish({ + channel.publish({ text: 'This one for Mr.Pike will also get through...', extras: { headers: { user: 'robert.pike@domain.io' } }, }); - await channel.publish({ + channel.publish({ text: '.... as well as this message', extras: { headers: { user: 'robert.pike@domain.io' } }, }); From 8f998d0fb22137d346237e4e35164c9342aabc75 Mon Sep 17 00:00:00 2001 From: Pavel Mikhalkevich Date: Tue, 14 Nov 2023 01:39:37 +0500 Subject: [PATCH 07/16] feat: add derived channel usage to 'sample-app' --- .../react-hooks/sample-app/src/App.tsx | 97 ++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/src/platform/react-hooks/sample-app/src/App.tsx b/src/platform/react-hooks/sample-app/src/App.tsx index fafebf8d3a..b60121fe5f 100644 --- a/src/platform/react-hooks/sample-app/src/App.tsx +++ b/src/platform/react-hooks/sample-app/src/App.tsx @@ -11,11 +11,36 @@ import './App.css'; function App() { const [messages, updateMessages] = useState([]); + const [derivedChannelMessages, updateDerivedChannelMessages] = useState([]); + const [frontOficeOnlyMessages, updateFrontOfficeOnlyMessages] = useState([]); + const [skip, setSkip] = useState(false); const { channel, ably } = useChannel({ channelName: 'your-channel-name', skip }, (message) => { updateMessages((prev) => [...prev, message]); }); + useChannel( + { + channelName: 'your-derived-channel-name', + deriveOptions: { filter: 'headers.email == `"rob.pike@domain.com"` || headers.company == `"domain"`' }, + }, + (message) => { + updateDerivedChannelMessages((prev) => [...prev, message]); + } + ); + + useChannel( + { + channelName: 'your-derived-channel-name', + deriveOptions: { filter: 'headers.role == `"front-office"` || headers.company == `"domain"`' }, + }, + (message) => { + updateFrontOfficeOnlyMessages((prev) => [...prev, message]); + } + ); + + const { channel: anotherChannelPublisher } = useChannel({ channelName: 'your-derived-channel-name' }); + const { presenceData, updateStatus } = usePresence( { channelName: 'your-channel-name', skip }, { foo: 'bar' }, @@ -42,7 +67,14 @@ function App() { setChannelStateReason(stateChange.reason ?? undefined); }); - const messagePreviews = messages.map((msg, index) =>
  • {msg.data.text}
  • ); + const messagePreviews = messages.map((message, idx) => ); + const derivedChannelMessagePreviews = derivedChannelMessages.map((message, idx) => ( + + )); + const frontOfficeMessagePreviews = frontOficeOnlyMessages.map((message, idx) => ( + + )); + const presentClients = presenceData.map((msg, index) => (
  • {msg.clientId}: {JSON.stringify(msg.data)} @@ -69,6 +101,57 @@ function App() { > Update status to hello + + +
    @@ -94,7 +177,13 @@ function App() {
    {ablyErr}

    Messages

    -
      {messagePreviews}
    + {
      {messagePreviews}
    } + +

    Derived Channel Messages

    +
      {derivedChannelMessagePreviews}
    + +

    Front Office Messages

    +
      {frontOfficeMessagePreviews}

    Present Clients

      {presentClients}
    @@ -103,6 +192,10 @@ function App() { ); } +function MessagePreview({ message }: { message: Types.Message }) { + return
  • {message.data.text}
  • ; +} + function ConnectionState() { const ably = useAbly(); const [connectionState, setConnectionState] = useState(ably.connection.state); From d8f3cd6d5738f08a95c2273c5414e78226af5fc0 Mon Sep 17 00:00:00 2001 From: Pavel Mikhalkevich Date: Thu, 16 Nov 2023 01:03:36 +0500 Subject: [PATCH 08/16] feat: add 'jmespath' as explicit dev-dep --- package-lock.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index c58c1e93fe..4183a572d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "grunt-shell": "~1.1", "grunt-webpack": "^4.0.2", "hexy": "~0.2", + "jmespath": "^0.16.0", "jsdom": "^20.0.0", "kexec": "ably-forks/node-kexec#update-for-node-12", "minimist": "^1.2.5", diff --git a/package.json b/package.json index 4978380866..1381f78e41 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "grunt-shell": "~1.1", "grunt-webpack": "^4.0.2", "hexy": "~0.2", + "jmespath": "^0.16.0", "jsdom": "^20.0.0", "kexec": "ably-forks/node-kexec#update-for-node-12", "minimist": "^1.2.5", From 63adfc4000379f15a2f82c6bc00f2056ded48aaa Mon Sep 17 00:00:00 2001 From: Pavel Mikhalkevich Date: Thu, 16 Nov 2023 01:36:16 +0500 Subject: [PATCH 09/16] feat: add derived channel usage example to 'docs/react.md' --- docs/react.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/react.md b/docs/react.md index 2a10480638..0eb0223b32 100644 --- a/docs/react.md +++ b/docs/react.md @@ -139,6 +139,29 @@ const { channel } = useChannel({ channelName: "your-channel-name", options: { .. }); ``` +[Subscription filters](https://ably.com/docs/channels#filter-subscribe) are also supported: + +```javascript +const deriveOptions = { filter: 'headers.email == `"rob.pike@domain.com"` || headers.company == `"domain"`' } +const { channel } = useChannel({ channelName: "your-derived-channel-name", options: { ... }, deriveOptions }, (message) => { + ... +}); +``` + +Please note that attempts to publish to a derived channel (the one created or retrieved with a filter expression) will fail. In order to send messages to the channel called _"your-derived-channel-name"_ from the example above, you will need to create another channel instance without a filter expression. + +```javascript +const channelName = "your-derived-channel-name"; +const options = { ... }; +const deriveOptions = { filter: 'headers.email == `"rob.pike@domain.com"` || headers.company == `"domain"`' } +const callback = (message) => { ... }; + +const { channel: readOnlyChannelInstance } = useChannel({ channelName, options, deriveOptions }, callback); +const { channel: readWriteChannelInstance } = useChannel({ channelName, options }, callback); // NB! No 'deriveOptions' passed here + +readWriteChannelInstance.publish("test-message", { text: "message text" }); +``` + --- ### usePresence From 7af5a4b07721da42c42b46983fd89d2aa5ed3a03 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Mon, 20 Nov 2023 12:18:26 +0000 Subject: [PATCH 10/16] fix: use 'ably' as import path from react-hooks The import paths here only worked before by accident - tsc would compile then to `from '../../../ably'` which in most cases would resolve to `node_modules/ably` however in certain environments (pnpm) this would not be the case and therefore the types would not be imported correctly. I've updated the tsconfig compilerOptions.paths field so that these are resolved correctly for development, and when consumed as a library these will just import directly from the 'ably' module. --- src/platform/react-hooks/src/AblyProvider.tsx | 8 +++----- src/platform/react-hooks/src/AblyReactHooks.ts | 2 +- src/platform/react-hooks/src/hooks/useAbly.ts | 2 +- src/platform/react-hooks/src/hooks/useChannel.ts | 2 +- .../react-hooks/src/hooks/useChannelStateListener.ts | 2 +- .../react-hooks/src/hooks/useConnectionStateListener.ts | 2 +- src/platform/react-hooks/src/hooks/useEventListener.ts | 2 +- src/platform/react-hooks/src/hooks/usePresence.ts | 2 +- src/platform/react-hooks/src/hooks/useStateErrors.ts | 2 +- src/platform/react-hooks/tsconfig.json | 5 ++++- 10 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/platform/react-hooks/src/AblyProvider.tsx b/src/platform/react-hooks/src/AblyProvider.tsx index c26f8e12a4..56eca00604 100644 --- a/src/platform/react-hooks/src/AblyProvider.tsx +++ b/src/platform/react-hooks/src/AblyProvider.tsx @@ -1,7 +1,5 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore import * as Ably from 'ably'; -import { Types } from '../../../../ably.js'; +import { Types } from 'ably'; import React, { useMemo } from 'react'; const canUseSymbol = typeof Symbol === 'function' && typeof Symbol.for === 'function'; @@ -31,7 +29,7 @@ export const AblyProvider = ({ client, children, id = 'default' }: AblyProviderP throw new Error('AblyProvider: the `client` prop is required'); } - if (!(client instanceof Ably.Realtime) && !client?.options?.promises) { + if (!(client instanceof Ably.Realtime) && !(client as any)?.options?.promises) { throw new Error('AblyProvider: the `client` prop must take an instance of Ably.Realtime.Promise'); } @@ -39,7 +37,7 @@ export const AblyProvider = ({ client, children, id = 'default' }: AblyProviderP let context = getContext(id); if (!context) { - context = ctxMap[id] = React.createContext(realtime ?? 1); + context = ctxMap[id] = React.createContext(realtime); } return {children}; diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 0b31381c87..1ab6bb7f94 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -1,4 +1,4 @@ -import { Types } from '../../../../ably.js'; +import { Types } from 'ably'; export type ChannelNameAndOptions = { channelName: string; diff --git a/src/platform/react-hooks/src/hooks/useAbly.ts b/src/platform/react-hooks/src/hooks/useAbly.ts index 9ddff98321..644448c63b 100644 --- a/src/platform/react-hooks/src/hooks/useAbly.ts +++ b/src/platform/react-hooks/src/hooks/useAbly.ts @@ -1,6 +1,6 @@ import React from 'react'; import { getContext } from '../AblyProvider.js'; -import * as API from '../../../../../callbacks.js'; +import * as API from 'ably'; export function useAbly(id = 'default'): API.Types.RealtimePromise { const client = React.useContext(getContext(id)) as API.Types.RealtimePromise; diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index 2321f5e3f7..e8d45b50ae 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -1,4 +1,4 @@ -import { Types } from '../../../../../ably.js'; +import { Types } from 'ably'; import { useEffect, useMemo, useRef } from 'react'; import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts index 4c38c92a4a..dcb5d7d31e 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import { Types } from '../../../../../ably.js'; +import { Types } from 'ably'; import { ChannelNameAndId, ChannelNameAndOptions, channelOptionsWithAgent } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useEventListener } from './useEventListener.js'; diff --git a/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts b/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts index 3df2ac16b1..74b18fa718 100644 --- a/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts +++ b/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts @@ -1,4 +1,4 @@ -import { Types } from '../../../../../ably.js'; +import { Types } from 'ably'; import { useAbly } from './useAbly.js'; import { useEventListener } from './useEventListener.js'; diff --git a/src/platform/react-hooks/src/hooks/useEventListener.ts b/src/platform/react-hooks/src/hooks/useEventListener.ts index 8c3d487819..adf6476c64 100644 --- a/src/platform/react-hooks/src/hooks/useEventListener.ts +++ b/src/platform/react-hooks/src/hooks/useEventListener.ts @@ -1,4 +1,4 @@ -import { Types } from '../../../../../ably.js'; +import { Types } from 'ably'; import { useEffect, useRef } from 'react'; type EventListener = (stateChange: T) => any; diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index c328a6055b..4cf32115f9 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -1,4 +1,4 @@ -import { Types } from '../../../../../ably.js'; +import { Types } from 'ably'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; diff --git a/src/platform/react-hooks/src/hooks/useStateErrors.ts b/src/platform/react-hooks/src/hooks/useStateErrors.ts index ff991bb55e..9921b6cac0 100644 --- a/src/platform/react-hooks/src/hooks/useStateErrors.ts +++ b/src/platform/react-hooks/src/hooks/useStateErrors.ts @@ -1,4 +1,4 @@ -import { Types } from '../../../../../ably.js'; +import { Types } from 'ably'; import { useState } from 'react'; import { useConnectionStateListener } from './useConnectionStateListener.js'; import { useChannelStateListener } from './useChannelStateListener.js'; diff --git a/src/platform/react-hooks/tsconfig.json b/src/platform/react-hooks/tsconfig.json index d455d743b2..ed956b2d1d 100644 --- a/src/platform/react-hooks/tsconfig.json +++ b/src/platform/react-hooks/tsconfig.json @@ -14,6 +14,9 @@ "allowJs": true, "jsx": "react-jsx", "lib": ["DOM", "DOM.Iterable", "ESNext"], - "types": ["vitest/globals"] + "types": ["vitest/globals"], + "paths": { + "ably": ["../../../"] + } } } From a7d4b2e3e29f456f5bff919d47f73fe41d9cbddb Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Mon, 20 Nov 2023 12:43:12 +0000 Subject: [PATCH 11/16] ci: check if the types are wrong --- .github/workflows/check.yml | 3 + package-lock.json | 643 ++++++++++++++++++++++++++++++++++-- package.json | 1 + 3 files changed, 627 insertions(+), 20 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8247cb59b4..b42fa03272 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -23,4 +23,7 @@ jobs: - run: npm run format:check - run: npm run check-closure-compiler - run: npx tsc --noEmit ably.d.ts build/ably-webworker.min.d.ts + # for some reason, this doesn't work in CI using `npx attw --pack .` + - run: npm pack + - run: npx attw ably-$(npm show ably version).tgz --summary - run: npm audit --production diff --git a/package-lock.json b/package-lock.json index 4183a572d5..d0d4cfb3d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ }, "devDependencies": { "@ably/vcdiff-decoder": "1.0.4", + "@arethetypeswrong/cli": "^0.13.1", "@testing-library/react": "^13.3.0", "@types/crypto-js": "^4.0.1", "@types/jmespath": "^0.15.2", @@ -113,6 +114,83 @@ "node": ">=6.0.0" } }, + "node_modules/@andrewbranch/untar.js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz", + "integrity": "sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==", + "dev": true + }, + "node_modules/@arethetypeswrong/cli": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.13.1.tgz", + "integrity": "sha512-nmaSMnVb1iHvJIJ4UmrNMRwtxxjnuOL2IELTuYLP4QatnebrGmqeRWOzbsuwh9OT48LX4fxp+wnJMGdrUf/nvw==", + "dev": true, + "dependencies": { + "@arethetypeswrong/core": "0.13.0", + "chalk": "^4.1.2", + "cli-table3": "^0.6.3", + "commander": "^10.0.1", + "marked": "^9.1.2", + "marked-terminal": "^6.0.0", + "semver": "^7.5.4" + }, + "bin": { + "attw": "dist/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/@arethetypeswrong/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.13.0.tgz", + "integrity": "sha512-dEDo+Vq/Kt12ZZJANuUDKWZzogswcmndcY293LVgswKv69YXq9jMR8HjVTDGhwnlGJ8fAiJC+XYhg/wfTwAFCA==", + "dev": true, + "dependencies": { + "@andrewbranch/untar.js": "^1.0.3", + "fflate": "^0.7.4", + "semver": "^7.5.4", + "typescript": "^5.2.2", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -580,6 +658,16 @@ "node": ">=6.9.0" } }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.36.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", @@ -1979,6 +2067,33 @@ "node": ">=6" } }, + "node_modules/ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dev": true, + "dependencies": { + "type-fest": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2000,6 +2115,12 @@ "node": ">=4" } }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true + }, "node_modules/anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -2840,6 +2961,15 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2988,6 +3118,19 @@ } ] }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, "node_modules/chai": { "version": "4.3.8", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", @@ -3007,9 +3150,9 @@ } }, "node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -3076,6 +3219,15 @@ "node": ">=8" } }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -3203,6 +3355,50 @@ "node": ">=6" } }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cli-table3/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -4291,6 +4487,12 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true + }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -5853,6 +6055,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "dev": true + }, "node_modules/figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -8793,6 +9001,38 @@ "node": ">= 12" } }, + "node_modules/marked-terminal": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-6.1.0.tgz", + "integrity": "sha512-QaCSF6NV82oo6K0szEnmc65ooDeW0T/Adcyf0fcW+Hto2GT1VADFg8dn1zaeHqzj65fqDH1hMNChGNRaC/lbkA==", + "dev": true, + "dependencies": { + "ansi-escapes": "^6.2.0", + "cardinal": "^2.1.1", + "chalk": "^5.3.0", + "cli-table3": "^0.6.3", + "node-emoji": "^2.1.0", + "supports-hyperlinks": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <11" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -9418,6 +9658,33 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-emoji": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.1.tgz", + "integrity": "sha512-+fyi06+Z9LARCwnTmUF1sRPVQFhGlIpuye3zwlzMN8bIKou6l7k1rGV8WVOEu9EQnRLfoVOYj/p107u0CoQoKA==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^6.0.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-emoji/node_modules/@sindresorhus/is": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.1.0.tgz", + "integrity": "sha512-BuvU07zq3tQ/2SIgBsEuxKYDyDjC0n7Zir52bpHy2xnBbW81+po43aLFPLbeV3HRAheFbGud1qgcqSYfhtHMAg==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -10760,6 +11027,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "dependencies": { + "esprima": "~4.0.0" + } + }, "node_modules/reduce-flatten": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", @@ -11209,9 +11485,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -11463,6 +11739,18 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -12125,12 +12413,12 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" @@ -12178,6 +12466,40 @@ "node": ">=4" } }, + "node_modules/supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -12955,6 +13277,15 @@ "node": "*" } }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -13203,6 +13534,18 @@ "node": ">= 0.10" } }, + "node_modules/validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -14404,6 +14747,62 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@andrewbranch/untar.js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz", + "integrity": "sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==", + "dev": true + }, + "@arethetypeswrong/cli": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.13.1.tgz", + "integrity": "sha512-nmaSMnVb1iHvJIJ4UmrNMRwtxxjnuOL2IELTuYLP4QatnebrGmqeRWOzbsuwh9OT48LX4fxp+wnJMGdrUf/nvw==", + "dev": true, + "requires": { + "@arethetypeswrong/core": "0.13.0", + "chalk": "^4.1.2", + "cli-table3": "^0.6.3", + "commander": "^10.0.1", + "marked": "^9.1.2", + "marked-terminal": "^6.0.0", + "semver": "^7.5.4" + }, + "dependencies": { + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + }, + "marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true + } + } + }, + "@arethetypeswrong/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.13.0.tgz", + "integrity": "sha512-dEDo+Vq/Kt12ZZJANuUDKWZzogswcmndcY293LVgswKv69YXq9jMR8HjVTDGhwnlGJ8fAiJC+XYhg/wfTwAFCA==", + "dev": true, + "requires": { + "@andrewbranch/untar.js": "^1.0.3", + "fflate": "^0.7.4", + "semver": "^7.5.4", + "typescript": "^5.2.2", + "validate-npm-package-name": "^5.0.0" + }, + "dependencies": { + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true + } + } + }, "@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -14748,6 +15147,13 @@ "to-fast-properties": "^2.0.0" } }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true + }, "@es-joy/jsdoccomment": { "version": "0.36.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", @@ -15728,6 +16134,23 @@ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, + "ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dev": true, + "requires": { + "type-fest": "^3.0.0" + }, + "dependencies": { + "type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true + } + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -15743,6 +16166,12 @@ "color-convert": "^1.9.0" } }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -16436,6 +16865,15 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -16545,6 +16983,16 @@ "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", "dev": true }, + "cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, "chai": { "version": "4.3.8", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", @@ -16561,9 +17009,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -16611,6 +17059,12 @@ } } }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -16725,6 +17179,41 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, + "cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -17609,6 +18098,12 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true + }, "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -18752,6 +19247,12 @@ "reusify": "^1.0.4" } }, + "fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "dev": true + }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -20966,6 +21467,28 @@ "integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA==", "dev": true }, + "marked-terminal": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-6.1.0.tgz", + "integrity": "sha512-QaCSF6NV82oo6K0szEnmc65ooDeW0T/Adcyf0fcW+Hto2GT1VADFg8dn1zaeHqzj65fqDH1hMNChGNRaC/lbkA==", + "dev": true, + "requires": { + "ansi-escapes": "^6.2.0", + "cardinal": "^2.1.1", + "chalk": "^5.3.0", + "cli-table3": "^0.6.3", + "node-emoji": "^2.1.0", + "supports-hyperlinks": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + } + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -21447,6 +21970,26 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node-emoji": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.1.tgz", + "integrity": "sha512-+fyi06+Z9LARCwnTmUF1sRPVQFhGlIpuye3zwlzMN8bIKou6l7k1rGV8WVOEu9EQnRLfoVOYj/p107u0CoQoKA==", + "dev": true, + "requires": { + "@sindresorhus/is": "^6.0.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.1.0.tgz", + "integrity": "sha512-BuvU07zq3tQ/2SIgBsEuxKYDyDjC0n7Zir52bpHy2xnBbW81+po43aLFPLbeV3HRAheFbGud1qgcqSYfhtHMAg==", + "dev": true + } + } + }, "node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -22454,6 +22997,15 @@ } } }, + "redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "requires": { + "esprima": "~4.0.0" + } + }, "reduce-flatten": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", @@ -22784,9 +23336,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -23000,6 +23552,15 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "requires": { + "unicode-emoji-modifier-base": "^1.0.0" + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -23548,12 +24109,12 @@ } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom": { @@ -23583,6 +24144,33 @@ "has-flag": "^3.0.0" } }, + "supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -24169,6 +24757,12 @@ "util-deprecate": "^1.0.2" } }, + "unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -24367,6 +24961,15 @@ "homedir-polyfill": "^1.0.1" } }, + "validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "requires": { + "builtins": "^5.0.0" + } + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 1381f78e41..c708791aab 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@ably/vcdiff-decoder": "1.0.4", + "@arethetypeswrong/cli": "^0.13.1", "@testing-library/react": "^13.3.0", "@types/crypto-js": "^4.0.1", "@types/jmespath": "^0.15.2", From 64d2543a22aef2cc59bf9152197c625279400979 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Mon, 20 Nov 2023 18:19:17 +0000 Subject: [PATCH 12/16] chore: bump version for 1.2.48 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4183a572d5..29d827aa64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ably", - "version": "1.2.47", + "version": "1.2.48", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ably", - "version": "1.2.47", + "version": "1.2.48", "license": "Apache-2.0", "dependencies": { "@ably/msgpack-js": "^0.4.0", diff --git a/package.json b/package.json index 1381f78e41..e1e886150b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ably", "description": "Realtime client library for Ably, the realtime messaging service", - "version": "1.2.47", + "version": "1.2.48", "license": "Apache-2.0", "bugs": { "url": "https://github.com/ably/ably-js/issues", From 11021907266bf9de673b3a8406ea8f95d315a30a Mon Sep 17 00:00:00 2001 From: owenpearson Date: Mon, 20 Nov 2023 18:22:08 +0000 Subject: [PATCH 13/16] chore: update changelog for 1.2.48 release --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9959b2eb01..08e3038064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ This contains only the most important and/or user-facing changes; for a full changelog, see the commit history. +## [1.2.48](https://github.com/ably/ably-js/tree/1.2.48) (2023-11-20) + +- Enable 'derived' options in 'useChannel' hook (by @rustworthy) [\#1501](https://github.com/ably/ably-js/pull/1501) +- fix: use 'ably' as import path from react-hooks [\#1509](https://github.com/ably/ably-js/pull/1509) + ## [1.2.47](https://github.com/ably/ably-js/tree/1.2.47) (2023-11-02) - fix(react): fix issue where useChannel would error upon router navigation or hmr [\#1478](https://github.com/ably/ably-js/pull/1478) From a9235abb92b5e8e82ecf156319dbedf15ed2c56c Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 21 Nov 2023 14:42:37 +0000 Subject: [PATCH 14/16] chore: bump version for react-hooks --- src/platform/react-hooks/src/AblyReactHooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 1ab6bb7f94..e28e824ab8 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -18,7 +18,7 @@ export type ChannelNameAndId = { }; export type ChannelParameters = string | ChannelNameAndOptions; -export const version = '1.2.47'; +export const version = '1.2.48'; export function channelOptionsWithAgent(options?: Types.ChannelOptions) { return { From 95ef9e2094a95134e2316220e75b15289ea6b1ef Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 11 Dec 2023 12:18:35 -0300 Subject: [PATCH 15/16] Remove some redundant type annotations --- src/common/lib/client/paginatedresource.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index 4d16a3b894..73eb89872d 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -1,7 +1,7 @@ import * as Utils from '../util/utils'; import Logger from '../util/logger'; import Resource from './resource'; -import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; +import { IPartialErrorInfo } from '../types/errorinfo'; import { PaginatedResultCallback } from '../../types/utils'; import Rest from './rest'; @@ -187,7 +187,7 @@ export class PaginatedResult { const self = this; if (relParams) { if ('first' in relParams) { - this.first = function (callback: (result?: ErrorInfo | null) => void) { + this.first = function (callback) { if (!callback && self.resource.rest.options.promises) { return Utils.promisify(self, 'first', []); } @@ -195,14 +195,14 @@ export class PaginatedResult { }; } if ('current' in relParams) { - this.current = function (callback: (results?: ErrorInfo | null) => void) { + this.current = function (callback) { if (!callback && self.resource.rest.options.promises) { return Utils.promisify(self, 'current', []); } self.get(relParams.current, callback); }; } - this.next = function (callback: (results?: ErrorInfo | null) => void) { + this.next = function (callback) { if (!callback && self.resource.rest.options.promises) { return Utils.promisify(self, 'next', []); } From 8bd17068f8e444f36ad86ce13ffecf0b4ba04456 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 11 Dec 2023 12:21:33 -0300 Subject: [PATCH 16/16] Fix PaginatedResult.next() behaviour when no next page It is meant to return `null` (per TG4 and our documentation comments), but was returning `undefined`. Resolves #1542. --- ably.d.ts | 4 ++-- src/common/lib/client/paginatedresource.ts | 2 +- test/rest/history.test.js | 20 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 08088741a1..0eb8e3f4f4 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -3460,13 +3460,13 @@ declare namespace Types { * * @param results - A function which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the function will be called with information about the error. */ - next(results: paginatedResultCallback): void; + next(results: StandardCallback | null>): void; /** * Returns a new `PaginatedResult` loaded with the next page of results. If there are no further pages, then `null` is returned. * * @returns A promise which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - next(): Promise>; + next(): Promise | null>; /** * Returns the `PaginatedResult` for the current page of results. * diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index 73eb89872d..700dba83c5 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -209,7 +209,7 @@ export class PaginatedResult { if ('next' in relParams) { self.get(relParams.next, callback); } else { - callback(null); + callback(null, null); } }; diff --git a/test/rest/history.test.js b/test/rest/history.test.js index f686c0db71..50f1059072 100644 --- a/test/rest/history.test.js +++ b/test/rest/history.test.js @@ -444,6 +444,26 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { } }); + restTestOnJsonMsgpack('history_no_next_page', function (done, rest, channelName) { + const channel = rest.channels.get(channelName); + + channel.history(function (err, firstPage) { + if (err) { + done(err); + return; + } + firstPage.next(function (err, secondPage) { + if (err) { + done(err); + return; + } + + expect(secondPage).to.equal(null); + done(); + }); + }); + }); + if (typeof Promise !== 'undefined') { it('historyPromise', function (done) { var rest = helper.AblyRest({ promises: true });