@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.
- package/CHANGELOG.md +18 -0
- package/dist/cjs/clients/feed/feed.js +1 -1
- package/dist/cjs/clients/feed/feed.js.map +1 -1
- package/dist/cjs/clients/feed/index.js +1 -1
- package/dist/cjs/clients/feed/index.js.map +1 -1
- package/dist/cjs/clients/feed/socket-manager.js +2 -0
- package/dist/cjs/clients/feed/socket-manager.js.map +1 -0
- package/dist/esm/clients/feed/feed.mjs +117 -102
- package/dist/esm/clients/feed/feed.mjs.map +1 -1
- package/dist/esm/clients/feed/index.mjs +31 -15
- package/dist/esm/clients/feed/index.mjs.map +1 -1
- package/dist/esm/clients/feed/socket-manager.mjs +81 -0
- package/dist/esm/clients/feed/socket-manager.mjs.map +1 -0
- package/dist/types/clients/feed/feed.d.ts +11 -6
- package/dist/types/clients/feed/feed.d.ts.map +1 -1
- package/dist/types/clients/feed/index.d.ts +2 -0
- package/dist/types/clients/feed/index.d.ts.map +1 -1
- package/dist/types/clients/feed/socket-manager.d.ts +31 -0
- package/dist/types/clients/feed/socket-manager.d.ts.map +1 -0
- package/dist/types/clients/feed/types.d.ts +4 -3
- package/dist/types/clients/feed/types.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/clients/feed/feed.ts +51 -41
- package/src/clients/feed/index.ts +27 -3
- package/src/clients/feed/socket-manager.ts +185 -0
- package/src/clients/feed/types.ts +5 -3
|
@@ -1,31 +1,47 @@
|
|
|
1
|
-
var
|
|
2
|
-
var c = (
|
|
3
|
-
var
|
|
4
|
-
import
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
8
|
+
s(this, "instance");
|
|
9
|
+
s(this, "feedInstances", []);
|
|
10
|
+
s(this, "socketManager");
|
|
9
11
|
this.instance = e;
|
|
10
12
|
}
|
|
11
|
-
initialize(e,
|
|
12
|
-
|
|
13
|
-
|
|
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((
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
|
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
|
|
65
|
-
private
|
|
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;
|
|
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;
|
|
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
|
|
21
|
-
|
|
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;
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|
package/src/clients/feed/feed.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GenericData } from "@knocklabs/types";
|
|
2
2
|
import EventEmitter from "eventemitter2";
|
|
3
|
-
import {
|
|
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
|
-
|
|
106
|
-
this.channel.leave();
|
|
107
|
-
this.channel.off("new-message");
|
|
108
|
-
}
|
|
119
|
+
this.socketManager?.leave(this);
|
|
109
120
|
|
|
110
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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.
|
|
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
|
-
|
|
791
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|