diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de0cdea3ec..4dce45af00 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ 2. Create a new branch for the release, for example `release/1.2.3` 3. Update the CHANGELOG.md with any customer-affecting changes since the last release and add this to the git index 4. Run `npm version --no-git-tag-version` with the new version and add the changes to the git index -5. Update the version number to the new version in `src/platform/react-hooks/src/AblyProvider.tsx` +5. Update the version number to the new version in `src/platform/react-hooks/src/AblyReactHooks.ts` 6. Create a PR for the release branch 7. Once the release PR is landed to the `main` branch, checkout the `main` branch locally (remember to pull the remote changes) and run `npm run build` 8. Run `git tag ` with the new version and push the tag to git diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index ce6f50d687..615ab6a686 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -185,7 +185,20 @@ class RealtimeChannel extends Channel { } _shouldReattachToSetOptions(options?: API.Types.ChannelOptions) { - return (this.state === 'attached' || this.state === 'attaching') && (options?.params || options?.modes); + if (!(this.state === 'attached' || this.state === 'attaching')) { + return false; + } + if (options?.params) { + if (!this.params || !Utils.shallowEquals(this.params, options.params)) { + return true; + } + } + if (options?.modes) { + if (!this.modes || !Utils.arrEquals(options.modes, this.modes)) { + return true; + } + } + return false; } publish(...args: any[]): void | Promise { diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 533daef412..cbd1214fd6 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -318,10 +318,10 @@ export const arrFilter = (Array.prototype.filter as unknown) }; export const arrEvery = (Array.prototype.every as unknown) - ? function (arr: Array, fn: (value: T, index?: number, arr?: Array) => boolean) { + ? function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { return arr.every(fn); } - : function (arr: Array, fn: (value: T, index?: number, arr?: Array) => boolean) { + : function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { const len = arr.length; for (let i = 0; i < len; i++) { if (!fn(arr[i], i, arr)) { @@ -604,3 +604,12 @@ export function toBase64(str: string) { } return stringifyBase64(parseUtf8(str)); } + +export function arrEquals(a: any[], b: any[]) { + return ( + a.length === b.length && + arrEvery(a, function (val, i) { + return val === b[i]; + }) + ); +} diff --git a/src/platform/react-hooks/src/AblyProvider.tsx b/src/platform/react-hooks/src/AblyProvider.tsx index b6995c5f0e..c26f8e12a4 100644 --- a/src/platform/react-hooks/src/AblyProvider.tsx +++ b/src/platform/react-hooks/src/AblyProvider.tsx @@ -4,8 +4,6 @@ import * as Ably from 'ably'; import { Types } from '../../../../ably.js'; import React, { useMemo } from 'react'; -const version = '1.2.45'; - const canUseSymbol = typeof Symbol === 'function' && typeof Symbol.for === 'function'; interface AblyProviderProps { @@ -28,8 +26,6 @@ export function getContext(ctxId = 'default'): AblyContextType { return ctxMap[ctxId]; } -let hasSentAgent = false; - export const AblyProvider = ({ client, children, id = 'default' }: AblyProviderProps) => { if (!client) { throw new Error('AblyProvider: the `client` prop is required'); @@ -46,14 +42,5 @@ export const AblyProvider = ({ client, children, id = 'default' }: AblyProviderP context = ctxMap[id] = React.createContext(realtime ?? 1); } - React.useEffect(() => { - if (!hasSentAgent) { - hasSentAgent = true; - realtime.request('GET', '/time', null, null, { - 'Ably-Agent': `react-hooks-time-ping/${version}`, - }); - } - }); - return {children}; }; diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 0d190a818d..fac2c4c7b4 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -16,3 +16,15 @@ export type ChannelNameAndId = { id?: string; }; export type ChannelParameters = string | ChannelNameAndOptions; + +export const version = '1.2.45'; + +export function channelOptionsWithAgent(options?: Types.ChannelOptions) { + return { + ...options, + params: { + ...options?.params, + agent: `react-hooks/${version}`, + }, + }; +} diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index 49aae48e6a..d747281a7d 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -1,6 +1,6 @@ import { Types } from '../../../../../ably.js'; import { useEffect, useMemo, useRef } from 'react'; -import { ChannelParameters } from '../AblyReactHooks.js'; +import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useStateErrors } from './useStateErrors.js'; @@ -45,13 +45,16 @@ export function useChannel( const channelOptionsRef = useRef(channelOptions); const ablyMessageCallbackRef = useRef(ablyMessageCallback); - const channel = useMemo(() => ably.channels.get(channelName, channelOptionsRef.current), [ably, channelName]); + const channel = useMemo( + () => ably.channels.get(channelName, channelOptionsWithAgent(channelOptionsRef.current)), + [ably, channelName] + ); const { connectionError, channelError } = useStateErrors(channelHookOptions); useEffect(() => { if (channelOptionsRef.current !== channelOptions && channelOptions) { - channel.setOptions(channelOptions); + channel.setOptions(channelOptionsWithAgent(channelOptions)); } channelOptionsRef.current = channelOptions; }, [channel, channelOptions]); diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index e56bcdd248..c328a6055b 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -1,6 +1,6 @@ import { Types } from '../../../../../ably.js'; -import { useCallback, useEffect, useState } from 'react'; -import { ChannelParameters } from '../AblyReactHooks.js'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useStateErrors } from './useStateErrors.js'; @@ -28,11 +28,24 @@ export function usePresence( const subscribeOnly = typeof channelNameOrNameAndOptions === 'string' ? false : params.subscribeOnly; - const channel = ably.channels.get(params.channelName, params.options); + const channelOptions = params.options; + const channelOptionsRef = useRef(channelOptions); + + const channel = useMemo( + () => ably.channels.get(params.channelName, channelOptionsWithAgent(channelOptionsRef.current)), + [ably, params.channelName] + ); const skip = params.skip; const { connectionError, channelError } = useStateErrors(params); + useEffect(() => { + if (channelOptionsRef.current !== channelOptions && channelOptions) { + channel.setOptions(channelOptionsWithAgent(channelOptions)); + } + channelOptionsRef.current = channelOptions; + }, [channel, channelOptions]); + const [presenceData, updatePresenceData] = useState>>([]); const updatePresence = async (message?: Types.PresenceMessage) => { diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index 252a9b1ef6..a079b64062 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -647,7 +647,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { realtime.channels.get(testName, { - params: params, + params: { + modes: 'subscribe', + }, }); } catch (err) { try { @@ -714,7 +716,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var setOptionsReturned = false; channel.setOptions( { - params: params, + params: { + modes: 'publish', + }, }, function () { /* Wait a tick so we don' depend on whether the update event runs the @@ -743,7 +747,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var setOptionsReturned = false; channel.setOptions( { - modes: modes, + modes: ['subscribe'], }, function () { Ably.Realtime.Platform.Config.nextTick(function () {