@knocklabs/client 0.14.5 → 0.14.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,31 +1,47 @@
1
- var a = Object.defineProperty;
2
- var c = (s, e, n) => e in s ? a(s, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : s[e] = n;
3
- var t = (s, e, n) => c(s, typeof e != "symbol" ? e + "" : e, n);
4
- import f from "./feed.mjs";
5
- class d {
1
+ var i = Object.defineProperty;
2
+ var c = (n, e, t) => e in n ? i(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t;
3
+ var s = (n, e, t) => c(n, typeof e != "symbol" ? e + "" : e, t);
4
+ import o from "./feed.mjs";
5
+ import { FeedSocketManager as r } from "./socket-manager.mjs";
6
+ class k {
6
7
  constructor(e) {
7
- t(this, "instance");
8
- t(this, "feedInstances", []);
8
+ s(this, "instance");
9
+ s(this, "feedInstances", []);
10
+ s(this, "socketManager");
9
11
  this.instance = e;
10
12
  }
11
- initialize(e, n = {}) {
12
- const i = new f(this.instance, e, n);
13
- return this.feedInstances.push(i), i;
13
+ initialize(e, t = {}) {
14
+ this.initSocketManager();
15
+ const a = new o(
16
+ this.instance,
17
+ e,
18
+ t,
19
+ this.socketManager
20
+ );
21
+ return this.feedInstances.push(a), a;
14
22
  }
15
23
  removeInstance(e) {
16
- this.feedInstances = this.feedInstances.filter((n) => n !== e);
24
+ this.feedInstances = this.feedInstances.filter((t) => t !== e);
17
25
  }
18
26
  teardownInstances() {
19
27
  for (const e of this.feedInstances)
20
28
  e.teardown();
21
29
  }
22
30
  reinitializeInstances() {
23
- for (const e of this.feedInstances)
24
- e.reinitialize();
31
+ var e;
32
+ for (const t of this.feedInstances)
33
+ (e = this.socketManager) == null || e.leave(t);
34
+ this.socketManager = void 0, this.initSocketManager();
35
+ for (const t of this.feedInstances)
36
+ t.reinitialize(this.socketManager);
37
+ }
38
+ initSocketManager() {
39
+ const e = this.instance.client().socket;
40
+ e && !this.socketManager && (this.socketManager = new r(e));
25
41
  }
26
42
  }
27
43
  export {
28
- f as Feed,
29
- d as default
44
+ o as Feed,
45
+ k as default
30
46
  };
31
47
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../../../src/clients/feed/index.ts"],"sourcesContent":["import Knock from \"../../knock\";\n\nimport Feed from \"./feed\";\nimport { FeedClientOptions } from \"./interfaces\";\n\nclass FeedClient {\n private instance: Knock;\n private feedInstances: Feed[] = [];\n\n constructor(instance: Knock) {\n this.instance = instance;\n }\n\n initialize(feedChannelId: string, options: FeedClientOptions = {}) {\n const feedInstance = new Feed(this.instance, feedChannelId, options);\n this.feedInstances.push(feedInstance);\n\n return feedInstance;\n }\n\n removeInstance(feed: Feed) {\n this.feedInstances = this.feedInstances.filter((f) => f !== feed);\n }\n\n teardownInstances() {\n for (const feed of this.feedInstances) {\n feed.teardown();\n }\n }\n\n reinitializeInstances() {\n for (const feed of this.feedInstances) {\n feed.reinitialize();\n }\n }\n}\n\nexport { Feed };\nexport default FeedClient;\n"],"names":["FeedClient","instance","__publicField","feedChannelId","options","feedInstance","Feed","feed","f"],"mappings":";;;;AAKA,MAAMA,EAAW;AAAA,EAIf,YAAYC,GAAiB;AAHrB,IAAAC,EAAA;AACA,IAAAA,EAAA,uBAAwB,CAAC;AAG/B,SAAK,WAAWD;AAAA,EAAA;AAAA,EAGlB,WAAWE,GAAuBC,IAA6B,IAAI;AACjE,UAAMC,IAAe,IAAIC,EAAK,KAAK,UAAUH,GAAeC,CAAO;AAC9D,gBAAA,cAAc,KAAKC,CAAY,GAE7BA;AAAA,EAAA;AAAA,EAGT,eAAeE,GAAY;AACzB,SAAK,gBAAgB,KAAK,cAAc,OAAO,CAACC,MAAMA,MAAMD,CAAI;AAAA,EAAA;AAAA,EAGlE,oBAAoB;AACP,eAAAA,KAAQ,KAAK;AACtB,MAAAA,EAAK,SAAS;AAAA,EAChB;AAAA,EAGF,wBAAwB;AACX,eAAAA,KAAQ,KAAK;AACtB,MAAAA,EAAK,aAAa;AAAA,EACpB;AAEJ;"}
1
+ {"version":3,"file":"index.mjs","sources":["../../../../src/clients/feed/index.ts"],"sourcesContent":["import Knock from \"../../knock\";\n\nimport Feed from \"./feed\";\nimport { FeedClientOptions } from \"./interfaces\";\nimport { FeedSocketManager } from \"./socket-manager\";\n\nclass FeedClient {\n private instance: Knock;\n private feedInstances: Feed[] = [];\n private socketManager: FeedSocketManager | undefined;\n\n constructor(instance: Knock) {\n this.instance = instance;\n }\n\n initialize(feedChannelId: string, options: FeedClientOptions = {}) {\n this.initSocketManager();\n\n const feedInstance = new Feed(\n this.instance,\n feedChannelId,\n options,\n this.socketManager,\n );\n this.feedInstances.push(feedInstance);\n return feedInstance;\n }\n\n removeInstance(feed: Feed) {\n this.feedInstances = this.feedInstances.filter((f) => f !== feed);\n }\n\n teardownInstances() {\n for (const feed of this.feedInstances) {\n feed.teardown();\n }\n }\n\n reinitializeInstances() {\n for (const feed of this.feedInstances) {\n this.socketManager?.leave(feed);\n }\n\n // The API client has a new socket once it's reinitialized,\n // so we need to set up a new socket manager\n this.socketManager = undefined;\n this.initSocketManager();\n\n for (const feed of this.feedInstances) {\n feed.reinitialize(this.socketManager);\n }\n }\n\n private initSocketManager() {\n const socket = this.instance.client().socket;\n if (socket && !this.socketManager) {\n this.socketManager = new FeedSocketManager(socket);\n }\n }\n}\n\nexport { Feed };\nexport default FeedClient;\n"],"names":["FeedClient","instance","__publicField","feedChannelId","options","feedInstance","Feed","feed","f","_a","socket","FeedSocketManager"],"mappings":";;;;;AAMA,MAAMA,EAAW;AAAA,EAKf,YAAYC,GAAiB;AAJrB,IAAAC,EAAA;AACA,IAAAA,EAAA,uBAAwB,CAAC;AACzB,IAAAA,EAAA;AAGN,SAAK,WAAWD;AAAA,EAAA;AAAA,EAGlB,WAAWE,GAAuBC,IAA6B,IAAI;AACjE,SAAK,kBAAkB;AAEvB,UAAMC,IAAe,IAAIC;AAAA,MACvB,KAAK;AAAA,MACLH;AAAA,MACAC;AAAA,MACA,KAAK;AAAA,IACP;AACK,gBAAA,cAAc,KAAKC,CAAY,GAC7BA;AAAA,EAAA;AAAA,EAGT,eAAeE,GAAY;AACzB,SAAK,gBAAgB,KAAK,cAAc,OAAO,CAACC,MAAMA,MAAMD,CAAI;AAAA,EAAA;AAAA,EAGlE,oBAAoB;AACP,eAAAA,KAAQ,KAAK;AACtB,MAAAA,EAAK,SAAS;AAAA,EAChB;AAAA,EAGF,wBAAwB;;AACX,eAAAA,KAAQ,KAAK;AACjB,OAAAE,IAAA,KAAA,kBAAA,QAAAA,EAAe,MAAMF;AAK5B,SAAK,gBAAgB,QACrB,KAAK,kBAAkB;AAEZ,eAAAA,KAAQ,KAAK;AACjB,MAAAA,EAAA,aAAa,KAAK,aAAa;AAAA,EACtC;AAAA,EAGM,oBAAoB;AAC1B,UAAMG,IAAS,KAAK,SAAS,OAAS,EAAA;AAClC,IAAAA,KAAU,CAAC,KAAK,kBACb,KAAA,gBAAgB,IAAIC,EAAkBD,CAAM;AAAA,EACnD;AAEJ;"}
@@ -0,0 +1,81 @@
1
+ var f = Object.defineProperty;
2
+ var u = (c, s, e) => s in c ? f(c, s, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[s] = e;
3
+ var h = (c, s, e) => u(c, typeof s != "symbol" ? s + "" : s, e);
4
+ import { Store as k } from "@tanstack/store";
5
+ const S = {
6
+ NewMessage: "new-message"
7
+ }, m = [S.NewMessage];
8
+ class x {
9
+ constructor(s) {
10
+ // Mapping of live channels by topic. Note, there can be one or more feed
11
+ // client(s) that can subscribe.
12
+ h(this, "channels");
13
+ // Mapping of query params for each feeds client, partitioned by reference id,
14
+ // and grouped by channel topic. It's a double nested object that looks like:
15
+ // {
16
+ // "feeds:<channel_1>:<user_1>": {
17
+ // "ref-1": {
18
+ // "tenant": "foo",
19
+ // },
20
+ // "ref-2": {
21
+ // "tenant": "bar",
22
+ // },
23
+ // },
24
+ // "feeds:<channel_2>:<user_1>": {
25
+ // "ref-3": {
26
+ // "tenant": "baz",
27
+ // },
28
+ // }
29
+ // }
30
+ //
31
+ // Each time a new feed client joins a channel, we send all cumulated
32
+ // params such that the socket API can apply filtering rules and figure out
33
+ // which feed clients should be notified based on reference ids in
34
+ // "attn" field of the event payload when sending out an event.
35
+ h(this, "params");
36
+ // A reactive store that captures a new socket event, that notifies any feed
37
+ // clients that have subscribed.
38
+ h(this, "inbox");
39
+ this.socket = s, this.channels = {}, this.params = {}, this.inbox = new k({});
40
+ }
41
+ join(s) {
42
+ const e = s.socketChannelTopic, t = s.referenceId, a = s.defaultOptions;
43
+ this.socket.isConnected() || this.socket.connect(), this.params[e] || (this.params[e] = {});
44
+ const n = this.params[e][t], p = !n || JSON.stringify(n) !== JSON.stringify(a);
45
+ if (p && (this.params[e] = { ...this.params[e], [t]: a }), !this.channels[e] || p) {
46
+ const o = this.socket.channel(e, this.params[e]);
47
+ for (const l of m)
48
+ o.on(l, (b) => this.setInbox(b));
49
+ this.channels[e] = o;
50
+ }
51
+ const i = this.channels[e];
52
+ return ["closed", "errored"].includes(i.state) && i.join(), this.inbox.subscribe(() => {
53
+ const o = this.inbox.state[t];
54
+ o && s.handleSocketEvent(o);
55
+ });
56
+ }
57
+ leave(s) {
58
+ var o;
59
+ (o = s.unsubscribeFromSocketEvents) == null || o.call(s);
60
+ const e = s.socketChannelTopic, t = s.referenceId, a = { ...this.params }, n = a[e] || {};
61
+ n[t] && delete n[t];
62
+ const i = { ...this.channels }, r = i[e];
63
+ if (r && Object.keys(n).length === 0) {
64
+ for (const l of m)
65
+ r.off(l);
66
+ r.leave(), delete i[e];
67
+ }
68
+ this.params = a, this.channels = i;
69
+ }
70
+ setInbox(s) {
71
+ const { attn: e, ...t } = s;
72
+ this.inbox.setState(
73
+ () => e.reduce((a, n) => ({ ...a, [n]: t }), {})
74
+ );
75
+ }
76
+ }
77
+ export {
78
+ x as FeedSocketManager,
79
+ S as SocketEventType
80
+ };
81
+ //# sourceMappingURL=socket-manager.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-manager.mjs","sources":["../../../../src/clients/feed/socket-manager.ts"],"sourcesContent":["import { Store } from \"@tanstack/store\";\nimport { Channel, Socket } from \"phoenix\";\n\nimport Feed from \"./feed\";\nimport type { FeedClientOptions, FeedMetadata } from \"./interfaces\";\n\nexport const SocketEventType = {\n NewMessage: \"new-message\",\n} as const;\n\nconst SOCKET_EVENT_TYPES = [SocketEventType.NewMessage];\n\ntype ClientQueryParams = FeedClientOptions;\n\n// e.g. feeds:<channel_id>:<user_id>\ntype ChannelTopic = string;\n\n// Unique reference id of a feed client\ntype ClientReferenceId = string;\n\ntype NewMessageEventPayload = {\n event: typeof SocketEventType.NewMessage;\n /**\n * @deprecated Top-level feed metadata. Exists for legacy reasons.\n */\n metadata: FeedMetadata;\n /** Feed metadata, keyed by client reference id. */\n data: Record<ClientReferenceId, { metadata: FeedMetadata }>;\n};\n\nexport type SocketEventPayload = NewMessageEventPayload;\n\n// \"attn\" field contains a list of client reference ids that should be notified\n// of a socket event.\ntype WithAttn<P> = P & { attn: ClientReferenceId[] };\n\ntype FeedSocketInbox = Record<ClientReferenceId, SocketEventPayload>;\n\n/*\n * Manages socket subscriptions for feeds, allowing multiple feed clients\n * to listen for real time updates from the socket API via a single socket\n * connection.\n */\nexport class FeedSocketManager {\n // Mapping of live channels by topic. Note, there can be one or more feed\n // client(s) that can subscribe.\n private channels: Record<ChannelTopic, Channel>;\n\n // Mapping of query params for each feeds client, partitioned by reference id,\n // and grouped by channel topic. It's a double nested object that looks like:\n // {\n // \"feeds:<channel_1>:<user_1>\": {\n // \"ref-1\": {\n // \"tenant\": \"foo\",\n // },\n // \"ref-2\": {\n // \"tenant\": \"bar\",\n // },\n // },\n // \"feeds:<channel_2>:<user_1>\": {\n // \"ref-3\": {\n // \"tenant\": \"baz\",\n // },\n // }\n // }\n //\n // Each time a new feed client joins a channel, we send all cumulated\n // params such that the socket API can apply filtering rules and figure out\n // which feed clients should be notified based on reference ids in\n // \"attn\" field of the event payload when sending out an event.\n private params: Record<\n ChannelTopic,\n Record<ClientReferenceId, ClientQueryParams>\n >;\n\n // A reactive store that captures a new socket event, that notifies any feed\n // clients that have subscribed.\n private inbox: Store<\n FeedSocketInbox,\n (cb: FeedSocketInbox) => FeedSocketInbox\n >;\n\n constructor(readonly socket: Socket) {\n this.channels = {};\n this.params = {};\n this.inbox = new Store<FeedSocketInbox>({});\n }\n\n join(feed: Feed) {\n const topic = feed.socketChannelTopic;\n const referenceId = feed.referenceId;\n const params = feed.defaultOptions;\n\n // Ensure a live socket connection if not yet connected.\n if (!this.socket.isConnected()) {\n this.socket.connect();\n }\n\n // If a new feed client joins, or has updated query params, then\n // track the updated params and (re)join with the latest query params.\n // Note, each time we send combined params of all feed clients that\n // have subscribed for a given feed channel and user, grouped by\n // client's reference id.\n if (!this.params[topic]) {\n this.params[topic] = {};\n }\n\n const maybeParams = this.params[topic][referenceId];\n const hasNewOrUpdatedParams =\n !maybeParams || JSON.stringify(maybeParams) !== JSON.stringify(params);\n\n if (hasNewOrUpdatedParams) {\n // Tracks all subscribed clients' params by reference id and by topic.\n this.params[topic] = { ...this.params[topic], [referenceId]: params };\n }\n\n if (!this.channels[topic] || hasNewOrUpdatedParams) {\n const newChannel = this.socket.channel(topic, this.params[topic]);\n for (const eventType of SOCKET_EVENT_TYPES) {\n newChannel.on(eventType, (payload) => this.setInbox(payload));\n }\n // Tracks live channels by channel topic.\n this.channels[topic] = newChannel;\n }\n\n const channel = this.channels[topic];\n\n // Join the channel if not already joined or joining or leaving.\n if ([\"closed\", \"errored\"].includes(channel.state)) {\n channel.join();\n }\n\n // Let the feed client subscribe to the \"inbox\", so it can be notified\n // when there's a new socket event that is relevant to it\n const unsub = this.inbox.subscribe(() => {\n const payload = this.inbox.state[referenceId];\n if (!payload) return;\n\n feed.handleSocketEvent(payload);\n });\n\n return unsub;\n }\n\n leave(feed: Feed) {\n feed.unsubscribeFromSocketEvents?.();\n\n const topic = feed.socketChannelTopic;\n const referenceId = feed.referenceId;\n\n const partitionedParams = { ...this.params };\n const paramsForTopic = partitionedParams[topic] || {};\n const paramsForReferenceClient = paramsForTopic[referenceId];\n\n if (paramsForReferenceClient) {\n delete paramsForTopic[referenceId];\n }\n\n const channels = { ...this.channels };\n const channelForTopic = channels[topic];\n if (channelForTopic && Object.keys(paramsForTopic).length === 0) {\n for (const eventType of SOCKET_EVENT_TYPES) {\n channelForTopic.off(eventType);\n }\n channelForTopic.leave();\n delete channels[topic];\n }\n\n this.params = partitionedParams;\n this.channels = channels;\n }\n\n private setInbox(payload: WithAttn<SocketEventPayload>) {\n const { attn, ...rest } = payload;\n\n // Set the incoming socket event into the inbox, keyed by relevant client\n // reference ids provided by the server (via attn field), so we can notify\n // only the clients that need to be notified.\n this.inbox.setState(() =>\n attn.reduce((acc, referenceId) => {\n return { ...acc, [referenceId]: rest };\n }, {}),\n );\n }\n}\n"],"names":["SocketEventType","SOCKET_EVENT_TYPES","FeedSocketManager","socket","__publicField","Store","feed","topic","referenceId","params","maybeParams","hasNewOrUpdatedParams","newChannel","eventType","payload","channel","_a","partitionedParams","paramsForTopic","channels","channelForTopic","attn","rest","acc"],"mappings":";;;;AAMO,MAAMA,IAAkB;AAAA,EAC7B,YAAY;AACd,GAEMC,IAAqB,CAACD,EAAgB,UAAU;AAiC/C,MAAME,EAAkB;AAAA,EAuC7B,YAAqBC,GAAgB;AApC7B;AAAA;AAAA,IAAAC,EAAA;AAwBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,EAAA;AAOA;AAAA;AAAA,IAAAA,EAAA;AAKa,SAAA,SAAAD,GACnB,KAAK,WAAW,CAAC,GACjB,KAAK,SAAS,CAAC,GACf,KAAK,QAAQ,IAAIE,EAAuB,EAAE;AAAA,EAAA;AAAA,EAG5C,KAAKC,GAAY;AACf,UAAMC,IAAQD,EAAK,oBACbE,IAAcF,EAAK,aACnBG,IAASH,EAAK;AAGpB,IAAK,KAAK,OAAO,iBACf,KAAK,OAAO,QAAQ,GAQjB,KAAK,OAAOC,CAAK,MACf,KAAA,OAAOA,CAAK,IAAI,CAAC;AAGxB,UAAMG,IAAc,KAAK,OAAOH,CAAK,EAAEC,CAAW,GAC5CG,IACJ,CAACD,KAAe,KAAK,UAAUA,CAAW,MAAM,KAAK,UAAUD,CAAM;AAOvE,QALIE,MAEF,KAAK,OAAOJ,CAAK,IAAI,EAAE,GAAG,KAAK,OAAOA,CAAK,GAAG,CAACC,CAAW,GAAGC,EAAO,IAGlE,CAAC,KAAK,SAASF,CAAK,KAAKI,GAAuB;AAC5C,YAAAC,IAAa,KAAK,OAAO,QAAQL,GAAO,KAAK,OAAOA,CAAK,CAAC;AAChE,iBAAWM,KAAaZ;AACtB,QAAAW,EAAW,GAAGC,GAAW,CAACC,MAAY,KAAK,SAASA,CAAO,CAAC;AAGzD,WAAA,SAASP,CAAK,IAAIK;AAAA,IAAA;AAGnB,UAAAG,IAAU,KAAK,SAASR,CAAK;AAGnC,WAAI,CAAC,UAAU,SAAS,EAAE,SAASQ,EAAQ,KAAK,KAC9CA,EAAQ,KAAK,GAKD,KAAK,MAAM,UAAU,MAAM;AACvC,YAAMD,IAAU,KAAK,MAAM,MAAMN,CAAW;AAC5C,MAAKM,KAELR,EAAK,kBAAkBQ,CAAO;AAAA,IAAA,CAC/B;AAAA,EAEM;AAAA,EAGT,MAAMR,GAAY;;AAChB,KAAAU,IAAAV,EAAK,gCAAL,QAAAU,EAAA,KAAAV;AAEA,UAAMC,IAAQD,EAAK,oBACbE,IAAcF,EAAK,aAEnBW,IAAoB,EAAE,GAAG,KAAK,OAAO,GACrCC,IAAiBD,EAAkBV,CAAK,KAAK,CAAC;AAGpD,IAFiCW,EAAeV,CAAW,KAGzD,OAAOU,EAAeV,CAAW;AAGnC,UAAMW,IAAW,EAAE,GAAG,KAAK,SAAS,GAC9BC,IAAkBD,EAASZ,CAAK;AACtC,QAAIa,KAAmB,OAAO,KAAKF,CAAc,EAAE,WAAW,GAAG;AAC/D,iBAAWL,KAAaZ;AACtB,QAAAmB,EAAgB,IAAIP,CAAS;AAE/B,MAAAO,EAAgB,MAAM,GACtB,OAAOD,EAASZ,CAAK;AAAA,IAAA;AAGvB,SAAK,SAASU,GACd,KAAK,WAAWE;AAAA,EAAA;AAAA,EAGV,SAASL,GAAuC;AACtD,UAAM,EAAE,MAAAO,GAAM,GAAGC,EAAA,IAASR;AAK1B,SAAK,MAAM;AAAA,MAAS,MAClBO,EAAK,OAAO,CAACE,GAAKf,OACT,EAAE,GAAGe,GAAK,CAACf,CAAW,GAAGc,EAAK,IACpC,CAAE,CAAA;AAAA,IACP;AAAA,EAAA;AAEJ;"}
@@ -2,25 +2,28 @@ import { GenericData } from '@knocklabs/types';
2
2
  import { StoreApi } from 'zustand';
3
3
  import { default as Knock } from '../../knock';
4
4
  import { FeedClientOptions, FetchFeedOptions } from './interfaces';
5
+ import { FeedSocketManager, SocketEventPayload } from './socket-manager';
5
6
  import { BindableFeedEvent, FeedEventCallback, FeedItemOrItems, FeedRealTimeCallback, FeedStoreState } from './types';
6
7
  declare class Feed {
7
8
  readonly knock: Knock;
8
9
  readonly feedId: string;
10
+ readonly defaultOptions: FeedClientOptions;
11
+ readonly referenceId: string;
12
+ unsubscribeFromSocketEvents: (() => void) | undefined;
13
+ private socketManager;
9
14
  private userFeedId;
10
- private channel?;
11
15
  private broadcaster;
12
- private defaultOptions;
13
16
  private broadcastChannel;
14
17
  private disconnectTimer;
15
18
  private hasSubscribedToRealTimeUpdates;
16
19
  private visibilityChangeHandler;
17
20
  private visibilityChangeListenerConnected;
18
21
  store: StoreApi<FeedStoreState>;
19
- constructor(knock: Knock, feedId: string, options: FeedClientOptions);
22
+ constructor(knock: Knock, feedId: string, options: FeedClientOptions, socketManager: FeedSocketManager | undefined);
20
23
  /**
21
24
  * Used to reinitialize a current feed instance, which is useful when reauthenticating users
22
25
  */
23
- reinitialize(): void;
26
+ reinitialize(socketManager?: FeedSocketManager): void;
24
27
  /**
25
28
  * Cleans up a feed instance by destroying the store and disconnecting
26
29
  * an open socket connection.
@@ -48,6 +51,7 @@ declare class Feed {
48
51
  data: any;
49
52
  } | undefined>;
50
53
  fetchNextPage(options?: FetchFeedOptions): Promise<void>;
54
+ get socketChannelTopic(): string;
51
55
  private broadcast;
52
56
  private onNewMessageReceived;
53
57
  private buildUserFeedId;
@@ -57,12 +61,13 @@ declare class Feed {
57
61
  private setupBroadcastChannel;
58
62
  private broadcastOverChannel;
59
63
  private initializeRealtimeConnection;
64
+ handleSocketEvent(payload: SocketEventPayload): Promise<void>;
60
65
  /**
61
66
  * Listen for changes to document visibility and automatically disconnect
62
67
  * or reconnect the socket after a delay
63
68
  */
64
- private setupAutoSocketManager;
65
- private teardownAutoSocketManager;
69
+ private setUpVisibilityListeners;
70
+ private tearDownVisibilityListeners;
66
71
  private emitEvent;
67
72
  private handleVisibilityChange;
68
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"feed.d.ts","sourceRoot":"","sources":["../../../../src/clients/feed/feed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGxC,OAAO,KAAK,MAAM,aAAa,CAAC;AAOhC,OAAO,EACL,iBAAiB,EAIjB,gBAAgB,EAEjB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,iBAAiB,EAEjB,iBAAiB,EAEjB,eAAe,EAEf,oBAAoB,EACpB,cAAc,EACf,MAAM,SAAS,CAAC;AAUjB,cAAM,IAAI;IAeN,QAAQ,CAAC,KAAK,EAAE,KAAK;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IAfzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAC,CAAU;IAC1B,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,cAAc,CAAoB;IAC1C,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,eAAe,CAA8C;IACrE,OAAO,CAAC,8BAA8B,CAAkB;IACxD,OAAO,CAAC,uBAAuB,CAAwB;IACvD,OAAO,CAAC,iCAAiC,CAAkB;IAGpD,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;gBAG5B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,iBAAiB;IAyB5B;;OAEG;IACH,YAAY;IAWZ;;;OAGG;IACH,QAAQ;IAoBR,2EAA2E;IAC3E,OAAO;IAWP,gBAAgB;IA2BhB,EAAE,CACA,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,EAAE,iBAAiB,GAAG,oBAAoB;IAKpD,GAAG,CACD,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,EAAE,iBAAiB,GAAG,oBAAoB;IAKpD,QAAQ;IAIF,UAAU,CAAC,WAAW,EAAE,eAAe;IAYvC,aAAa;IA0Cb,YAAY,CAAC,WAAW,EAAE,eAAe;IAWzC,UAAU,CAAC,WAAW,EAAE,eAAe;IAYvC,aAAa;IA0Cb,YAAY,CAAC,WAAW,EAAE,eAAe;IAWzC,gBAAgB,CACpB,WAAW,EAAE,eAAe,EAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAwB7B,cAAc,CAAC,WAAW,EAAE,eAAe;IAuE3C,iBAAiB;IA2BjB,qBAAqB;IA0CrB,gBAAgB,CAAC,WAAW,EAAE,eAAe;IAS7C,KAAK,CAAC,OAAO,GAAE,gBAAqB;;;;IA0FpC,aAAa,CAAC,OAAO,GAAE,gBAAqB;IAgBlD,OAAO,CAAC,SAAS;YAQH,oBAAoB;IAalC,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,iCAAiC;YAiD3B,gBAAgB;YAsBhB,oBAAoB;IA2BlC,OAAO,CAAC,qBAAqB;IAoC7B,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,4BAA4B;IA0BpC;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,yBAAyB;IAUjC,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,sBAAsB;CA2B/B;AAED,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"feed.d.ts","sourceRoot":"","sources":["../../../../src/clients/feed/feed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGxC,OAAO,KAAK,MAAM,aAAa,CAAC;AAOhC,OAAO,EACL,iBAAiB,EAIjB,gBAAgB,EAEjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAEnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,iBAAiB,EAEjB,iBAAiB,EAEjB,eAAe,EAEf,oBAAoB,EACpB,cAAc,EACf,MAAM,SAAS,CAAC;AAYjB,cAAM,IAAI;IAiBN,QAAQ,CAAC,KAAK,EAAE,KAAK;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IAjBzB,SAAgB,cAAc,EAAE,iBAAiB,CAAC;IAClD,SAAgB,WAAW,EAAE,MAAM,CAAC;IAC7B,2BAA2B,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAa;IACzE,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,eAAe,CAA8C;IACrE,OAAO,CAAC,8BAA8B,CAAkB;IACxD,OAAO,CAAC,uBAAuB,CAAwB;IACvD,OAAO,CAAC,iCAAiC,CAAkB;IAGpD,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;gBAG5B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,iBAAiB,EAC1B,aAAa,EAAE,iBAAiB,GAAG,SAAS;IA2B9C;;OAEG;IACH,YAAY,CAAC,aAAa,CAAC,EAAE,iBAAiB;IAa9C;;;OAGG;IACH,QAAQ;IAiBR,2EAA2E;IAC3E,OAAO;IAWP,gBAAgB;IAiBhB,EAAE,CACA,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,EAAE,iBAAiB,GAAG,oBAAoB;IAKpD,GAAG,CACD,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,EAAE,iBAAiB,GAAG,oBAAoB;IAKpD,QAAQ;IAIF,UAAU,CAAC,WAAW,EAAE,eAAe;IAYvC,aAAa;IA0Cb,YAAY,CAAC,WAAW,EAAE,eAAe;IAWzC,UAAU,CAAC,WAAW,EAAE,eAAe;IAYvC,aAAa;IA0Cb,YAAY,CAAC,WAAW,EAAE,eAAe;IAWzC,gBAAgB,CACpB,WAAW,EAAE,eAAe,EAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAwB7B,cAAc,CAAC,WAAW,EAAE,eAAe;IAuE3C,iBAAiB;IA2BjB,qBAAqB;IA0CrB,gBAAgB,CAAC,WAAW,EAAE,eAAe;IAS7C,KAAK,CAAC,OAAO,GAAE,gBAAqB;;;;IA0FpC,aAAa,CAAC,OAAO,GAAE,gBAAqB;IAgBlD,IAAI,kBAAkB,IAAI,MAAM,CAE/B;IAED,OAAO,CAAC,SAAS;YAQH,oBAAoB;IAiBlC,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,iCAAiC;YAiD3B,gBAAgB;YAsBhB,oBAAoB;IA2BlC,OAAO,CAAC,qBAAqB;IAoC7B,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,4BAA4B;IAe9B,iBAAiB,CAAC,OAAO,EAAE,kBAAkB;IAYnD;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAahC,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,sBAAsB;CA2B/B;AAED,eAAe,IAAI,CAAC"}
@@ -4,11 +4,13 @@ import { FeedClientOptions } from './interfaces';
4
4
  declare class FeedClient {
5
5
  private instance;
6
6
  private feedInstances;
7
+ private socketManager;
7
8
  constructor(instance: Knock);
8
9
  initialize(feedChannelId: string, options?: FeedClientOptions): Feed;
9
10
  removeInstance(feed: Feed): void;
10
11
  teardownInstances(): void;
11
12
  reinitializeInstances(): void;
13
+ private initSocketManager;
12
14
  }
13
15
  export { Feed };
14
16
  export default FeedClient;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/clients/feed/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,aAAa,CAAC;AAEhC,OAAO,IAAI,MAAM,QAAQ,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,cAAM,UAAU;IACd,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,aAAa,CAAc;gBAEvB,QAAQ,EAAE,KAAK;IAI3B,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB;IAOjE,cAAc,CAAC,IAAI,EAAE,IAAI;IAIzB,iBAAiB;IAMjB,qBAAqB;CAKtB;AAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAChB,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/clients/feed/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,aAAa,CAAC;AAEhC,OAAO,IAAI,MAAM,QAAQ,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD,cAAM,UAAU;IACd,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,aAAa,CAAgC;gBAEzC,QAAQ,EAAE,KAAK;IAI3B,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB;IAajE,cAAc,CAAC,IAAI,EAAE,IAAI;IAIzB,iBAAiB;IAMjB,qBAAqB;IAerB,OAAO,CAAC,iBAAiB;CAM1B;AAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAChB,eAAe,UAAU,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { Socket } from 'phoenix';
2
+ import { default as Feed } from './feed';
3
+ import { FeedMetadata } from './interfaces';
4
+ export declare const SocketEventType: {
5
+ readonly NewMessage: "new-message";
6
+ };
7
+ type ClientReferenceId = string;
8
+ type NewMessageEventPayload = {
9
+ event: typeof SocketEventType.NewMessage;
10
+ /**
11
+ * @deprecated Top-level feed metadata. Exists for legacy reasons.
12
+ */
13
+ metadata: FeedMetadata;
14
+ /** Feed metadata, keyed by client reference id. */
15
+ data: Record<ClientReferenceId, {
16
+ metadata: FeedMetadata;
17
+ }>;
18
+ };
19
+ export type SocketEventPayload = NewMessageEventPayload;
20
+ export declare class FeedSocketManager {
21
+ readonly socket: Socket;
22
+ private channels;
23
+ private params;
24
+ private inbox;
25
+ constructor(socket: Socket);
26
+ join(feed: Feed): () => void;
27
+ leave(feed: Feed): void;
28
+ private setInbox;
29
+ }
30
+ export {};
31
+ //# sourceMappingURL=socket-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-manager.d.ts","sourceRoot":"","sources":["../../../../src/clients/feed/socket-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,MAAM,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,IAAI,MAAM,QAAQ,CAAC;AAC1B,OAAO,KAAK,EAAqB,YAAY,EAAE,MAAM,cAAc,CAAC;AAEpE,eAAO,MAAM,eAAe;;CAElB,CAAC;AAUX,KAAK,iBAAiB,GAAG,MAAM,CAAC;AAEhC,KAAK,sBAAsB,GAAG;IAC5B,KAAK,EAAE,OAAO,eAAe,CAAC,UAAU,CAAC;IACzC;;OAEG;IACH,QAAQ,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC,iBAAiB,EAAE;QAAE,QAAQ,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,CAAC;AAaxD,qBAAa,iBAAiB;IAuChB,QAAQ,CAAC,MAAM,EAAE,MAAM;IApCnC,OAAO,CAAC,QAAQ,CAAgC;IAwBhD,OAAO,CAAC,MAAM,CAGZ;IAIF,OAAO,CAAC,KAAK,CAGX;gBAEmB,MAAM,EAAE,MAAM;IAMnC,IAAI,CAAC,IAAI,EAAE,IAAI;IAwDf,KAAK,CAAC,IAAI,EAAE,IAAI;IA4BhB,OAAO,CAAC,QAAQ;CAYjB"}
@@ -1,6 +1,7 @@
1
1
  import { GenericData, PageInfo } from '@knocklabs/types';
2
2
  import { NetworkStatus } from '../../networkStatus';
3
3
  import { FeedItem, FeedMetadata, FeedResponse } from './interfaces';
4
+ import { SocketEventPayload, SocketEventType } from './socket-manager';
4
5
  export type StoreFeedResultOptions = {
5
6
  shouldSetPage?: boolean;
6
7
  shouldAppend?: boolean;
@@ -17,9 +18,9 @@ export interface FeedStoreState {
17
18
  setItemAttrs: (itemIds: string[], attrs: object) => void;
18
19
  resetStore: (metadata?: FeedMetadata) => void;
19
20
  }
20
- export interface FeedMessagesReceivedPayload {
21
- metadata: FeedMetadata;
22
- }
21
+ export type FeedMessagesReceivedPayload = Extract<SocketEventPayload, {
22
+ event: typeof SocketEventType.NewMessage;
23
+ }>;
23
24
  export type FeedRealTimeEvent = "messages.new";
24
25
  export type FeedEvent = FeedRealTimeEvent | "items.received.page" | "items.received.realtime" | "items.archived" | "items.unarchived" | "items.seen" | "items.unseen" | "items.read" | "items.unread" | "items.all_archived" | "items.all_read" | "items.all_seen";
25
26
  export type BindableFeedEvent = FeedEvent | "items.received.*" | "items.*";
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/clients/feed/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEpE,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAC3E,WAAW,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;IAC9C,gBAAgB,EAAE,CAAC,aAAa,EAAE,aAAa,KAAK,IAAI,CAAC;IACzD,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,UAAU,EAAE,CAAC,QAAQ,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,YAAY,CAAC;CACxB;AAQD,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAE/C,MAAM,MAAM,SAAS,GACjB,iBAAiB,GACjB,qBAAqB,GACrB,yBAAyB,GACzB,gBAAgB,GAChB,kBAAkB,GAClB,YAAY,GACZ,cAAc,GACd,YAAY,GACZ,cAAc,GACd,oBAAoB,GACpB,gBAAgB,GAChB,gBAAgB,CAAC;AAGrB,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,kBAAkB,GAAG,SAAS,CAAC;AAE3E,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,WAAW;IAC/C,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACvC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;AAEhE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/clients/feed/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEvE,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAC3E,WAAW,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;IAC9C,gBAAgB,EAAE,CAAC,aAAa,EAAE,aAAa,KAAK,IAAI,CAAC;IACzD,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,UAAU,EAAE,CAAC,QAAQ,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;CAC/C;AAED,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAC/C,kBAAkB,EAClB;IAAE,KAAK,EAAE,OAAO,eAAe,CAAC,UAAU,CAAA;CAAE,CAC7C,CAAC;AAQF,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAE/C,MAAM,MAAM,SAAS,GACjB,iBAAiB,GACjB,qBAAqB,GACrB,yBAAyB,GACzB,gBAAgB,GAChB,kBAAkB,GAClB,YAAY,GACZ,cAAc,GACd,YAAY,GACZ,cAAc,GACd,oBAAoB,GACpB,gBAAgB,GAChB,gBAAgB,CAAC;AAGrB,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,kBAAkB,GAAG,SAAS,CAAC;AAE3E,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,WAAW;IAC/C,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACvC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;AAEhE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knocklabs/client",
3
- "version": "0.14.5",
3
+ "version": "0.14.7",
4
4
  "description": "The clientside library for interacting with Knock",
5
5
  "homepage": "https://github.com/knocklabs/javascript/tree/main/packages/client",
6
6
  "author": "@knocklabs",
@@ -52,7 +52,7 @@
52
52
  "@babel/plugin-proposal-class-properties": "^7.16.7",
53
53
  "@babel/plugin-proposal-object-rest-spread": "^7.16.7",
54
54
  "@babel/plugin-transform-runtime": "^7.25.4",
55
- "@babel/preset-env": "^7.26.0",
55
+ "@babel/preset-env": "^7.27.1",
56
56
  "@babel/preset-typescript": "^7.27.0",
57
57
  "@types/jsonwebtoken": "^9.0.9",
58
58
  "@typescript-eslint/eslint-plugin": "^8.19.1",
@@ -65,7 +65,7 @@
65
65
  "rimraf": "^6.0.1",
66
66
  "rollup": "^4.34.8",
67
67
  "typescript": "^5.8.3",
68
- "vite": "^5.4.18",
68
+ "vite": "^5.4.19",
69
69
  "vitest": "^3.1.1"
70
70
  },
71
71
  "dependencies": {
@@ -77,6 +77,7 @@
77
77
  "axios-retry": "^4.5.0",
78
78
  "eventemitter2": "^6.4.5",
79
79
  "jwt-decode": "^4.0.0",
80
+ "nanoid": "^3.3.11",
80
81
  "phoenix": "1.7.19",
81
82
  "urlpattern-polyfill": "^10.0.0",
82
83
  "zustand": "^4.5.6"
@@ -1,6 +1,6 @@
1
1
  import { GenericData } from "@knocklabs/types";
2
2
  import EventEmitter from "eventemitter2";
3
- import { Channel } from "phoenix";
3
+ import { nanoid } from "nanoid";
4
4
  import type { StoreApi } from "zustand";
5
5
 
6
6
  import { isValidUuid } from "../../helpers";
@@ -19,6 +19,11 @@ import {
19
19
  FetchFeedOptions,
20
20
  FetchFeedOptionsForRequest,
21
21
  } from "./interfaces";
22
+ import {
23
+ FeedSocketManager,
24
+ SocketEventPayload,
25
+ SocketEventType,
26
+ } from "./socket-manager";
22
27
  import createStore from "./store";
23
28
  import {
24
29
  BindableFeedEvent,
@@ -39,11 +44,15 @@ const feedClientDefaults: Pick<FeedClientOptions, "archived"> = {
39
44
 
40
45
  const DEFAULT_DISCONNECT_DELAY = 2000;
41
46
 
47
+ const CLIENT_REF_ID_PREFIX = "client_";
48
+
42
49
  class Feed {
50
+ public readonly defaultOptions: FeedClientOptions;
51
+ public readonly referenceId: string;
52
+ public unsubscribeFromSocketEvents: (() => void) | undefined = undefined;
53
+ private socketManager: FeedSocketManager | undefined;
43
54
  private userFeedId: string;
44
- private channel?: Channel;
45
55
  private broadcaster: EventEmitter;
46
- private defaultOptions: FeedClientOptions;
47
56
  private broadcastChannel!: BroadcastChannel | null;
48
57
  private disconnectTimer: ReturnType<typeof setTimeout> | null = null;
49
58
  private hasSubscribedToRealTimeUpdates: boolean = false;
@@ -57,6 +66,7 @@ class Feed {
57
66
  readonly knock: Knock,
58
67
  readonly feedId: string,
59
68
  options: FeedClientOptions,
69
+ socketManager: FeedSocketManager | undefined,
60
70
  ) {
61
71
  if (!feedId || !isValidUuid(feedId)) {
62
72
  this.knock.log(
@@ -67,6 +77,8 @@ class Feed {
67
77
 
68
78
  this.feedId = feedId;
69
79
  this.userFeedId = this.buildUserFeedId();
80
+ this.referenceId = CLIENT_REF_ID_PREFIX + nanoid();
81
+ this.socketManager = socketManager;
70
82
  this.store = createStore();
71
83
  this.broadcaster = new EventEmitter({ wildcard: true, delimiter: "." });
72
84
  this.defaultOptions = {
@@ -84,7 +96,9 @@ class Feed {
84
96
  /**
85
97
  * Used to reinitialize a current feed instance, which is useful when reauthenticating users
86
98
  */
87
- reinitialize() {
99
+ reinitialize(socketManager?: FeedSocketManager) {
100
+ this.socketManager = socketManager;
101
+
88
102
  // Reinitialize the user feed id incase the userId changed
89
103
  this.userFeedId = this.buildUserFeedId();
90
104
 
@@ -102,12 +116,9 @@ class Feed {
102
116
  teardown() {
103
117
  this.knock.log("[Feed] Tearing down feed instance");
104
118
 
105
- if (this.channel) {
106
- this.channel.leave();
107
- this.channel.off("new-message");
108
- }
119
+ this.socketManager?.leave(this);
109
120
 
110
- this.teardownAutoSocketManager();
121
+ this.tearDownVisibilityListeners();
111
122
 
112
123
  if (this.disconnectTimer) {
113
124
  clearTimeout(this.disconnectTimer);
@@ -144,17 +155,7 @@ class Feed {
144
155
  return;
145
156
  }
146
157
 
147
- const maybeSocket = this.knock.client().socket;
148
-
149
- // Connect the socket only if we don't already have a connection
150
- if (maybeSocket && !maybeSocket.isConnected()) {
151
- maybeSocket.connect();
152
- }
153
-
154
- // Only join the channel if we're not already in a joining state
155
- if (this.channel && ["closed", "errored"].includes(this.channel.state)) {
156
- this.channel.join();
157
- }
158
+ this.unsubscribeFromSocketEvents = this.socketManager?.join(this);
158
159
  }
159
160
 
160
161
  /* Binds a handler to be invoked when event occurs */
@@ -587,6 +588,10 @@ class Feed {
587
588
  });
588
589
  }
589
590
 
591
+ get socketChannelTopic(): string {
592
+ return `feeds:${this.userFeedId}`;
593
+ }
594
+
590
595
  private broadcast(
591
596
  eventName: FeedEvent,
592
597
  data: FeedResponse | FeedEventPayload,
@@ -595,15 +600,19 @@ class Feed {
595
600
  }
596
601
 
597
602
  // Invoked when a new real-time message comes in from the socket
598
- private async onNewMessageReceived({
599
- metadata,
600
- }: FeedMessagesReceivedPayload) {
603
+ private async onNewMessageReceived({ data }: FeedMessagesReceivedPayload) {
601
604
  this.knock.log("[Feed] Received new real-time message");
605
+
602
606
  // Handle the new message coming in
603
607
  const { items, ...state } = this.store.getState();
604
608
  const currentHead: FeedItem | undefined = items[0];
609
+
605
610
  // Optimistically set the badge counts
606
- state.setMetadata(metadata);
611
+ const metadata = data[this.referenceId]?.metadata;
612
+ if (metadata) {
613
+ state.setMetadata(metadata);
614
+ }
615
+
607
616
  // Fetch the items before the current head (if it exists)
608
617
  this.fetch({ before: currentHead?.__cursor, __fetchSource: "socket" });
609
618
  }
@@ -767,28 +776,29 @@ class Feed {
767
776
  }
768
777
 
769
778
  private initializeRealtimeConnection() {
770
- const { socket: maybeSocket } = this.knock.client();
771
-
772
779
  // In server environments we might not have a socket connection
773
- if (!maybeSocket) return;
774
-
775
- // Reinitialize channel connections incase the socket changed
776
- this.channel = maybeSocket.channel(
777
- `feeds:${this.userFeedId}`,
778
- this.defaultOptions,
779
- );
780
-
781
- this.channel.on("new-message", (resp) => this.onNewMessageReceived(resp));
780
+ if (!this.socketManager) return;
782
781
 
783
782
  if (this.defaultOptions.auto_manage_socket_connection) {
784
- this.setupAutoSocketManager();
783
+ this.setUpVisibilityListeners();
785
784
  }
786
785
 
787
786
  // If we're initializing but they have previously opted to listen to real-time updates
788
787
  // then we will automatically reconnect on their behalf
789
788
  if (this.hasSubscribedToRealTimeUpdates && this.knock.isAuthenticated()) {
790
- if (!maybeSocket.isConnected()) maybeSocket.connect();
791
- this.channel.join();
789
+ this.unsubscribeFromSocketEvents = this.socketManager?.join(this);
790
+ }
791
+ }
792
+
793
+ async handleSocketEvent(payload: SocketEventPayload) {
794
+ switch (payload.event) {
795
+ case SocketEventType.NewMessage:
796
+ this.onNewMessageReceived(payload);
797
+ return;
798
+ default: {
799
+ const _exhaustiveCheck: never = payload.event;
800
+ return;
801
+ }
792
802
  }
793
803
  }
794
804
 
@@ -796,7 +806,7 @@ class Feed {
796
806
  * Listen for changes to document visibility and automatically disconnect
797
807
  * or reconnect the socket after a delay
798
808
  */
799
- private setupAutoSocketManager() {
809
+ private setUpVisibilityListeners() {
800
810
  if (
801
811
  typeof document === "undefined" ||
802
812
  this.visibilityChangeListenerConnected
@@ -809,7 +819,7 @@ class Feed {
809
819
  document.addEventListener("visibilitychange", this.visibilityChangeHandler);
810
820
  }
811
821
 
812
- private teardownAutoSocketManager() {
822
+ private tearDownVisibilityListeners() {
813
823
  if (typeof document === "undefined") return;
814
824
 
815
825
  document.removeEventListener(
@@ -860,7 +870,7 @@ class Feed {
860
870
 
861
871
  // If the socket is not connected, try to reconnect
862
872
  if (!client.socket?.isConnected()) {
863
- this.initializeRealtimeConnection();
873
+ client.socket?.connect();
864
874
  }
865
875
  }
866
876
  }
@@ -2,19 +2,27 @@ import Knock from "../../knock";
2
2
 
3
3
  import Feed from "./feed";
4
4
  import { FeedClientOptions } from "./interfaces";
5
+ import { FeedSocketManager } from "./socket-manager";
5
6
 
6
7
  class FeedClient {
7
8
  private instance: Knock;
8
9
  private feedInstances: Feed[] = [];
10
+ private socketManager: FeedSocketManager | undefined;
9
11
 
10
12
  constructor(instance: Knock) {
11
13
  this.instance = instance;
12
14
  }
13
15
 
14
16
  initialize(feedChannelId: string, options: FeedClientOptions = {}) {
15
- const feedInstance = new Feed(this.instance, feedChannelId, options);
17
+ this.initSocketManager();
18
+
19
+ const feedInstance = new Feed(
20
+ this.instance,
21
+ feedChannelId,
22
+ options,
23
+ this.socketManager,
24
+ );
16
25
  this.feedInstances.push(feedInstance);
17
-
18
26
  return feedInstance;
19
27
  }
20
28
 
@@ -30,7 +38,23 @@ class FeedClient {
30
38
 
31
39
  reinitializeInstances() {
32
40
  for (const feed of this.feedInstances) {
33
- feed.reinitialize();
41
+ this.socketManager?.leave(feed);
42
+ }
43
+
44
+ // The API client has a new socket once it's reinitialized,
45
+ // so we need to set up a new socket manager
46
+ this.socketManager = undefined;
47
+ this.initSocketManager();
48
+
49
+ for (const feed of this.feedInstances) {
50
+ feed.reinitialize(this.socketManager);
51
+ }
52
+ }
53
+
54
+ private initSocketManager() {
55
+ const socket = this.instance.client().socket;
56
+ if (socket && !this.socketManager) {
57
+ this.socketManager = new FeedSocketManager(socket);
34
58
  }
35
59
  }
36
60
  }