@knocklabs/client 0.8.18 → 0.8.20

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.
Files changed (122) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/api.d.ts +25 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +84 -0
  5. package/dist/cjs/api.js +2 -133
  6. package/dist/cjs/api.js.map +1 -1
  7. package/dist/cjs/clients/feed/feed.js +2 -939
  8. package/dist/cjs/clients/feed/feed.js.map +1 -1
  9. package/dist/cjs/clients/feed/index.js +2 -34
  10. package/dist/cjs/clients/feed/index.js.map +1 -1
  11. package/dist/cjs/clients/feed/store.js +2 -111
  12. package/dist/cjs/clients/feed/store.js.map +1 -1
  13. package/dist/cjs/clients/feed/utils.js +2 -26
  14. package/dist/cjs/clients/feed/utils.js.map +1 -1
  15. package/dist/cjs/clients/preferences/index.js +2 -373
  16. package/dist/cjs/clients/preferences/index.js.map +1 -1
  17. package/dist/cjs/clients/users/index.js +2 -185
  18. package/dist/cjs/clients/users/index.js.map +1 -1
  19. package/dist/cjs/index.js +2 -102
  20. package/dist/cjs/index.js.map +1 -1
  21. package/dist/cjs/knock.js +6 -89
  22. package/dist/cjs/knock.js.map +1 -1
  23. package/dist/cjs/networkStatus.js +2 -18
  24. package/dist/cjs/networkStatus.js.map +1 -1
  25. package/dist/clients/feed/feed.d.ts +64 -0
  26. package/dist/clients/feed/feed.d.ts.map +1 -0
  27. package/dist/clients/feed/feed.js +572 -0
  28. package/dist/clients/feed/index.d.ts +15 -0
  29. package/dist/clients/feed/index.d.ts.map +1 -0
  30. package/dist/clients/feed/index.js +34 -0
  31. package/dist/clients/feed/interfaces.d.ts +60 -0
  32. package/dist/clients/feed/interfaces.d.ts.map +1 -0
  33. package/dist/clients/feed/interfaces.js +2 -0
  34. package/dist/clients/feed/store.d.ts +3 -0
  35. package/dist/clients/feed/store.d.ts.map +1 -0
  36. package/dist/clients/feed/store.js +72 -0
  37. package/dist/clients/feed/types.d.ts +34 -0
  38. package/dist/clients/feed/types.d.ts.map +1 -0
  39. package/dist/clients/feed/types.js +2 -0
  40. package/dist/clients/feed/utils.d.ts +4 -0
  41. package/dist/clients/feed/utils.d.ts.map +1 -0
  42. package/dist/clients/feed/utils.js +21 -0
  43. package/dist/clients/preferences/index.d.ts +46 -0
  44. package/dist/clients/preferences/index.d.ts.map +1 -0
  45. package/dist/clients/preferences/index.js +129 -0
  46. package/dist/clients/preferences/interfaces.d.ts +26 -0
  47. package/dist/clients/preferences/interfaces.d.ts.map +1 -0
  48. package/dist/clients/preferences/interfaces.js +2 -0
  49. package/dist/clients/users/index.d.ts +16 -0
  50. package/dist/clients/users/index.d.ts.map +1 -0
  51. package/dist/clients/users/index.js +56 -0
  52. package/dist/clients/users/interfaces.d.ts +8 -0
  53. package/dist/clients/users/interfaces.d.ts.map +1 -0
  54. package/dist/clients/users/interfaces.js +2 -0
  55. package/dist/esm/api.js +44 -84
  56. package/dist/esm/api.js.map +1 -1
  57. package/dist/esm/clients/feed/feed.js +296 -603
  58. package/dist/esm/clients/feed/feed.js.map +1 -1
  59. package/dist/esm/clients/feed/index.js +28 -12
  60. package/dist/esm/clients/feed/index.js.map +1 -1
  61. package/dist/esm/clients/feed/store.js +37 -71
  62. package/dist/esm/clients/feed/store.js.map +1 -1
  63. package/dist/esm/clients/feed/utils.js +10 -15
  64. package/dist/esm/clients/feed/utils.js.map +1 -1
  65. package/dist/esm/clients/preferences/index.js +79 -146
  66. package/dist/esm/clients/preferences/index.js.map +1 -1
  67. package/dist/esm/clients/users/index.js +52 -76
  68. package/dist/esm/clients/users/index.js.map +1 -1
  69. package/dist/esm/index.js +12 -11
  70. package/dist/esm/index.js.map +1 -1
  71. package/dist/esm/knock.js +72 -51
  72. package/dist/esm/knock.js.map +1 -1
  73. package/dist/esm/networkStatus.js +14 -10
  74. package/dist/esm/networkStatus.js.map +1 -1
  75. package/dist/index.d.ts +11 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +43 -0
  78. package/dist/interfaces.d.ts +41 -0
  79. package/dist/interfaces.d.ts.map +1 -0
  80. package/dist/interfaces.js +2 -0
  81. package/dist/knock.d.ts +30 -0
  82. package/dist/knock.d.ts.map +1 -0
  83. package/dist/knock.js +135 -0
  84. package/dist/networkStatus.d.ts +8 -0
  85. package/dist/networkStatus.d.ts.map +1 -0
  86. package/dist/networkStatus.js +18 -0
  87. package/dist/types/api.d.ts +0 -2
  88. package/dist/types/api.d.ts.map +1 -1
  89. package/dist/types/clients/feed/feed.d.ts +12 -4
  90. package/dist/types/clients/feed/feed.d.ts.map +1 -1
  91. package/dist/types/clients/feed/index.d.ts +4 -0
  92. package/dist/types/clients/feed/index.d.ts.map +1 -1
  93. package/dist/types/clients/feed/store.d.ts.map +1 -1
  94. package/dist/types/clients/feed/types.d.ts +0 -1
  95. package/dist/types/clients/feed/types.d.ts.map +1 -1
  96. package/dist/types/index.d.ts +1 -1
  97. package/dist/types/index.d.ts.map +1 -1
  98. package/dist/types/interfaces.d.ts +7 -0
  99. package/dist/types/interfaces.d.ts.map +1 -1
  100. package/dist/types/knock.d.ts +12 -4
  101. package/dist/types/knock.d.ts.map +1 -1
  102. package/package.json +11 -7
  103. package/dist/cjs/clients/feed/interfaces.js +0 -6
  104. package/dist/cjs/clients/feed/interfaces.js.map +0 -1
  105. package/dist/cjs/clients/feed/types.js +0 -6
  106. package/dist/cjs/clients/feed/types.js.map +0 -1
  107. package/dist/cjs/clients/preferences/interfaces.js +0 -6
  108. package/dist/cjs/clients/preferences/interfaces.js.map +0 -1
  109. package/dist/cjs/clients/users/interfaces.js +0 -6
  110. package/dist/cjs/clients/users/interfaces.js.map +0 -1
  111. package/dist/cjs/interfaces.js +0 -6
  112. package/dist/cjs/interfaces.js.map +0 -1
  113. package/dist/esm/clients/feed/interfaces.js +0 -2
  114. package/dist/esm/clients/feed/interfaces.js.map +0 -1
  115. package/dist/esm/clients/feed/types.js +0 -2
  116. package/dist/esm/clients/feed/types.js.map +0 -1
  117. package/dist/esm/clients/preferences/interfaces.js +0 -2
  118. package/dist/esm/clients/preferences/interfaces.js.map +0 -1
  119. package/dist/esm/clients/users/interfaces.js +0 -2
  120. package/dist/esm/clients/users/interfaces.js.map +0 -1
  121. package/dist/esm/interfaces.js +0 -2
  122. package/dist/esm/interfaces.js.map +0 -1
@@ -1,668 +1,361 @@
1
- import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
- import { EventEmitter2 as EventEmitter } from "eventemitter2";
6
- import createStore from "./store";
7
- import { isRequestInFlight, NetworkStatus } from "../../networkStatus";
8
- // Default options to apply
9
- var feedClientDefaults = {
1
+ var p = Object.defineProperty;
2
+ var _ = (m, e, t) => e in m ? p(m, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : m[e] = t;
3
+ var u = (m, e, t) => (_(m, typeof e != "symbol" ? e + "" : e, t), t);
4
+ import { EventEmitter2 as k } from "eventemitter2";
5
+ import { isRequestInFlight as S, NetworkStatus as f } from "../../networkStatus.js";
6
+ import g from "./store.js";
7
+ const v = {
10
8
  archived: "exclude"
11
- };
12
- var DEFAULT_DISCONNECT_DELAY = 2000;
13
- class Feed {
14
- constructor(knock, feedId, options) {
15
- this.knock = knock;
16
- this.feedId = feedId;
17
- _defineProperty(this, "apiClient", void 0);
18
- _defineProperty(this, "userFeedId", void 0);
19
- _defineProperty(this, "channel", void 0);
20
- _defineProperty(this, "broadcaster", void 0);
21
- _defineProperty(this, "defaultOptions", void 0);
22
- _defineProperty(this, "broadcastChannel", void 0);
23
- _defineProperty(this, "disconnectTimer", null);
9
+ }, y = 2e3;
10
+ class A {
11
+ constructor(e, t, s) {
12
+ u(this, "userFeedId");
13
+ u(this, "channel");
14
+ u(this, "broadcaster");
15
+ u(this, "defaultOptions");
16
+ u(this, "broadcastChannel");
17
+ u(this, "disconnectTimer", null);
18
+ u(this, "hasSubscribedToRealTimeUpdates", !1);
24
19
  // The raw store instance, used for binding in React and other environments
25
- _defineProperty(this, "store", void 0);
26
- this.apiClient = knock.client();
27
- this.feedId = feedId;
28
- this.userFeedId = this.buildUserFeedId();
29
- this.store = createStore();
30
- this.broadcaster = new EventEmitter({
31
- wildcard: true,
32
- delimiter: "."
33
- });
34
- this.defaultOptions = _objectSpread(_objectSpread({}, feedClientDefaults), options);
35
-
36
- // In server environments we might not have a socket connection
37
- if (this.apiClient.socket) {
38
- this.channel = this.apiClient.socket.channel("feeds:".concat(this.userFeedId), this.defaultOptions);
39
- this.channel.on("new-message", resp => this.onNewMessageReceived(resp));
40
- }
41
-
42
- // Attempt to bind to listen to other events from this feed in different tabs
43
- // Note: here we ensure `self` is available (it's not in server rendered envs)
44
- this.broadcastChannel = typeof self !== "undefined" && "BroadcastChannel" in self ? new BroadcastChannel("knock:feed:".concat(this.userFeedId)) : null;
45
- if (options.auto_manage_socket_connection && this.apiClient.socket) {
46
- this.setupAutoSocketManager(options.auto_manage_socket_connection_delay);
47
- }
20
+ u(this, "store");
21
+ this.knock = e, this.feedId = t, this.feedId = t, this.userFeedId = this.buildUserFeedId(), this.store = g(), this.broadcaster = new k({ wildcard: !0, delimiter: "." }), this.defaultOptions = { ...v, ...s }, this.knock.log(`[Feed] Initialized a feed on channel ${t}`), this.initializeRealtimeConnection(), this.setupBroadcastChannel();
22
+ }
23
+ /**
24
+ * Used to reinitialize a current feed instance, which is useful when reauthenticating users
25
+ */
26
+ reinitialize() {
27
+ this.userFeedId = this.buildUserFeedId(), this.initializeRealtimeConnection(), this.setupBroadcastChannel();
48
28
  }
49
-
50
29
  /**
51
30
  * Cleans up a feed instance by destroying the store and disconnecting
52
31
  * an open socket connection.
53
32
  */
54
33
  teardown() {
55
- if (this.channel) {
56
- this.channel.leave();
57
- this.channel.off("new-message");
58
- }
59
- this.broadcaster.removeAllListeners();
60
- this.store.destroy();
61
- if (this.broadcastChannel) {
62
- this.broadcastChannel.close();
63
- }
34
+ this.knock.log("[Feed] Tearing down feed instance"), this.channel && (this.channel.leave(), this.channel.off("new-message")), this.disconnectTimer && (clearTimeout(this.disconnectTimer), this.disconnectTimer = null), this.broadcastChannel && this.broadcastChannel.close();
35
+ }
36
+ /** Tears down an instance and removes it entirely from the feed manager */
37
+ dispose() {
38
+ this.knock.log("[Feed] Disposing of feed instance"), this.teardown(), this.broadcaster.removeAllListeners(), this.knock.feeds.removeInstance(this);
64
39
  }
65
-
66
40
  /*
67
41
  Initializes a real-time connection to Knock, connecting the websocket for the
68
42
  current ApiClient instance if the socket is not already connected.
69
43
  */
70
44
  listenForUpdates() {
71
- // Connect the socket only if we don't already have a connection
72
- if (this.apiClient.socket && !this.apiClient.socket.isConnected()) {
73
- this.apiClient.socket.connect();
74
- }
75
-
76
- // Only join the channel if we're not already in a joining state
77
- if (this.channel && ["closed", "errored"].includes(this.channel.state)) {
78
- this.channel.join();
79
- }
80
-
81
- // Opt into receiving updates from _other tabs for the same user / feed_ via the broadcast
82
- // channel (iff it's enabled and exists)
83
- if (this.broadcastChannel && this.defaultOptions.__experimentalCrossBrowserUpdates === true) {
84
- this.broadcastChannel.onmessage = e => {
85
- switch (e.data.type) {
86
- case "items:archived":
87
- case "items:unarchived":
88
- case "items:seen":
89
- case "items:unseen":
90
- case "items:read":
91
- case "items:unread":
92
- case "items:all_read":
93
- case "items:all_seen":
94
- case "items:all_archived":
95
- // When items are updated in any other tab, simply refetch to get the latest state
96
- // to make sure that the state gets updated accordingly. In the future here we could
97
- // maybe do this optimistically without the fetch.
98
- return this.fetch();
99
- break;
100
- default:
101
- return null;
102
- }
103
- };
104
- }
45
+ this.knock.log("[Feed] Connecting to real-time service"), this.hasSubscribedToRealTimeUpdates = !0;
46
+ const e = this.knock.client().socket;
47
+ e && !e.isConnected() && e.connect(), this.channel && ["closed", "errored"].includes(this.channel.state) && this.channel.join();
105
48
  }
106
-
107
49
  /* Binds a handler to be invoked when event occurs */
108
- on(eventName, callback) {
109
- this.broadcaster.on(eventName, callback);
50
+ on(e, t) {
51
+ this.broadcaster.on(e, t);
110
52
  }
111
- off(eventName, callback) {
112
- this.broadcaster.off(eventName, callback);
53
+ off(e, t) {
54
+ this.broadcaster.off(e, t);
113
55
  }
114
56
  getState() {
115
57
  return this.store.getState();
116
58
  }
117
- markAsSeen(itemOrItems) {
118
- var _this = this;
119
- return _asyncToGenerator(function* () {
120
- var now = new Date().toISOString();
121
- _this.optimisticallyPerformStatusUpdate(itemOrItems, "seen", {
122
- seen_at: now
123
- }, "unseen_count");
124
- return _this.makeStatusUpdate(itemOrItems, "seen");
125
- })();
59
+ async markAsSeen(e) {
60
+ const t = (/* @__PURE__ */ new Date()).toISOString();
61
+ return this.optimisticallyPerformStatusUpdate(
62
+ e,
63
+ "seen",
64
+ { seen_at: t },
65
+ "unseen_count"
66
+ ), this.makeStatusUpdate(e, "seen");
126
67
  }
127
- markAllAsSeen() {
128
- var _this2 = this;
129
- return _asyncToGenerator(function* () {
130
- // To mark all of the messages as seen we:
131
- // 1. Optimistically update *everything* we have in the store
132
- // 2. We decrement the `unseen_count` to zero optimistically
133
- // 3. We issue the API call to the endpoint
134
- //
135
- // Note: there is the potential for a race condition here because the bulk
136
- // update is an async method, so if a new message comes in during this window before
137
- // the update has been processed we'll effectively reset the `unseen_count` to be what it was.
138
- //
139
- // Note: here we optimistically handle the case whereby the feed is scoped to show only `unseen`
140
- // items by removing everything from view.
141
- var {
142
- getState,
143
- setState
144
- } = _this2.store;
145
- var {
146
- metadata,
147
- items
148
- } = getState();
149
- var isViewingOnlyUnseen = _this2.defaultOptions.status === "unseen";
150
-
151
- // If we're looking at the unseen view, then we want to remove all of the items optimistically
152
- // from the store given that nothing should be visible. We do this by resetting the store state
153
- // and setting the current metadata counts to 0
154
- if (isViewingOnlyUnseen) {
155
- setState(store => store.resetStore(_objectSpread(_objectSpread({}, metadata), {}, {
68
+ async markAllAsSeen() {
69
+ const { getState: e, setState: t } = this.store, { metadata: s, items: a } = e();
70
+ if (this.defaultOptions.status === "unseen")
71
+ t(
72
+ (i) => i.resetStore({
73
+ ...s,
156
74
  total_count: 0,
157
75
  unseen_count: 0
158
- })));
159
- } else {
160
- // Otherwise we want to update the metadata and mark all of the items in the store as seen
161
- setState(store => store.setMetadata(_objectSpread(_objectSpread({}, metadata), {}, {
162
- unseen_count: 0
163
- })));
164
- var attrs = {
165
- seen_at: new Date().toISOString()
166
- };
167
- var itemIds = items.map(item => item.id);
168
- setState(store => store.setItemAttrs(itemIds, attrs));
169
- }
170
-
171
- // Issue the API request to the bulk status change API
172
- var result = yield _this2.makeBulkStatusUpdate("seen");
173
- _this2.broadcaster.emit("items:all_seen", {
174
- items
175
- });
176
- _this2.broadcastOverChannel("items:all_seen", {
177
- items
178
- });
179
- return result;
180
- })();
76
+ })
77
+ );
78
+ else {
79
+ t((o) => o.setMetadata({ ...s, unseen_count: 0 }));
80
+ const i = { seen_at: (/* @__PURE__ */ new Date()).toISOString() }, l = a.map((o) => o.id);
81
+ t((o) => o.setItemAttrs(l, i));
82
+ }
83
+ const n = await this.makeBulkStatusUpdate("seen");
84
+ return this.broadcaster.emit("items:all_seen", { items: a }), this.broadcastOverChannel("items:all_seen", { items: a }), n;
181
85
  }
182
- markAsUnseen(itemOrItems) {
183
- var _this3 = this;
184
- return _asyncToGenerator(function* () {
185
- _this3.optimisticallyPerformStatusUpdate(itemOrItems, "unseen", {
186
- seen_at: null
187
- }, "unseen_count");
188
- return _this3.makeStatusUpdate(itemOrItems, "unseen");
189
- })();
86
+ async markAsUnseen(e) {
87
+ return this.optimisticallyPerformStatusUpdate(
88
+ e,
89
+ "unseen",
90
+ { seen_at: null },
91
+ "unseen_count"
92
+ ), this.makeStatusUpdate(e, "unseen");
190
93
  }
191
- markAsRead(itemOrItems) {
192
- var _this4 = this;
193
- return _asyncToGenerator(function* () {
194
- var now = new Date().toISOString();
195
- _this4.optimisticallyPerformStatusUpdate(itemOrItems, "read", {
196
- read_at: now
197
- }, "unread_count");
198
- return _this4.makeStatusUpdate(itemOrItems, "read");
199
- })();
94
+ async markAsRead(e) {
95
+ const t = (/* @__PURE__ */ new Date()).toISOString();
96
+ return this.optimisticallyPerformStatusUpdate(
97
+ e,
98
+ "read",
99
+ { read_at: t },
100
+ "unread_count"
101
+ ), this.makeStatusUpdate(e, "read");
200
102
  }
201
- markAllAsRead() {
202
- var _this5 = this;
203
- return _asyncToGenerator(function* () {
204
- // To mark all of the messages as read we:
205
- // 1. Optimistically update *everything* we have in the store
206
- // 2. We decrement the `unread_count` to zero optimistically
207
- // 3. We issue the API call to the endpoint
208
- //
209
- // Note: there is the potential for a race condition here because the bulk
210
- // update is an async method, so if a new message comes in during this window before
211
- // the update has been processed we'll effectively reset the `unread_count` to be what it was.
212
- //
213
- // Note: here we optimistically handle the case whereby the feed is scoped to show only `unread`
214
- // items by removing everything from view.
215
- var {
216
- getState,
217
- setState
218
- } = _this5.store;
219
- var {
220
- metadata,
221
- items
222
- } = getState();
223
- var isViewingOnlyUnread = _this5.defaultOptions.status === "unread";
224
-
225
- // If we're looking at the unread view, then we want to remove all of the items optimistically
226
- // from the store given that nothing should be visible. We do this by resetting the store state
227
- // and setting the current metadata counts to 0
228
- if (isViewingOnlyUnread) {
229
- setState(store => store.resetStore(_objectSpread(_objectSpread({}, metadata), {}, {
103
+ async markAllAsRead() {
104
+ const { getState: e, setState: t } = this.store, { metadata: s, items: a } = e();
105
+ if (this.defaultOptions.status === "unread")
106
+ t(
107
+ (i) => i.resetStore({
108
+ ...s,
230
109
  total_count: 0,
231
110
  unread_count: 0
232
- })));
233
- } else {
234
- // Otherwise we want to update the metadata and mark all of the items in the store as seen
235
- setState(store => store.setMetadata(_objectSpread(_objectSpread({}, metadata), {}, {
236
- unread_count: 0
237
- })));
238
- var attrs = {
239
- read_at: new Date().toISOString()
240
- };
241
- var itemIds = items.map(item => item.id);
242
- setState(store => store.setItemAttrs(itemIds, attrs));
243
- }
244
-
245
- // Issue the API request to the bulk status change API
246
- var result = yield _this5.makeBulkStatusUpdate("read");
247
- _this5.broadcaster.emit("items:all_read", {
248
- items
249
- });
250
- _this5.broadcastOverChannel("items:all_read", {
251
- items
252
- });
253
- return result;
254
- })();
111
+ })
112
+ );
113
+ else {
114
+ t((o) => o.setMetadata({ ...s, unread_count: 0 }));
115
+ const i = { read_at: (/* @__PURE__ */ new Date()).toISOString() }, l = a.map((o) => o.id);
116
+ t((o) => o.setItemAttrs(l, i));
117
+ }
118
+ const n = await this.makeBulkStatusUpdate("read");
119
+ return this.broadcaster.emit("items:all_read", { items: a }), this.broadcastOverChannel("items:all_read", { items: a }), n;
255
120
  }
256
- markAsUnread(itemOrItems) {
257
- var _this6 = this;
258
- return _asyncToGenerator(function* () {
259
- _this6.optimisticallyPerformStatusUpdate(itemOrItems, "unread", {
260
- read_at: null
261
- }, "unread_count");
262
- return _this6.makeStatusUpdate(itemOrItems, "unread");
263
- })();
121
+ async markAsUnread(e) {
122
+ return this.optimisticallyPerformStatusUpdate(
123
+ e,
124
+ "unread",
125
+ { read_at: null },
126
+ "unread_count"
127
+ ), this.makeStatusUpdate(e, "unread");
264
128
  }
265
- markAsInteracted(itemOrItems) {
266
- var _this7 = this;
267
- return _asyncToGenerator(function* () {
268
- var now = new Date().toISOString();
269
- _this7.optimisticallyPerformStatusUpdate(itemOrItems, "interacted", {
270
- read_at: now,
271
- interacted_at: now
272
- }, "unread_count");
273
- return _this7.makeStatusUpdate(itemOrItems, "interacted");
274
- })();
129
+ async markAsInteracted(e) {
130
+ const t = (/* @__PURE__ */ new Date()).toISOString();
131
+ return this.optimisticallyPerformStatusUpdate(
132
+ e,
133
+ "interacted",
134
+ {
135
+ read_at: t,
136
+ interacted_at: t
137
+ },
138
+ "unread_count"
139
+ ), this.makeStatusUpdate(e, "interacted");
275
140
  }
276
-
277
141
  /*
278
- Marking one or more items as archived should:
279
- - Decrement the badge count for any unread / unseen items
280
- - Remove the item from the feed list when the `archived` flag is "exclude" (default)
281
- TODO: how do we handle rollbacks?
282
- */
283
- markAsArchived(itemOrItems) {
284
- var _this8 = this;
285
- return _asyncToGenerator(function* () {
286
- var {
287
- getState,
288
- setState
289
- } = _this8.store;
290
- var state = getState();
291
- var shouldOptimisticallyRemoveItems = _this8.defaultOptions.archived === "exclude";
292
- var normalizedItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
293
- var itemIds = normalizedItems.map(item => item.id);
294
-
295
- /*
296
- In the code here we want to optimistically update counts and items
297
- that are persisted such that we can display updates immediately on the feed
298
- without needing to make a network request.
299
- Note: right now this does *not* take into account offline handling or any extensive retry
300
- logic, so rollbacks aren't considered. That probably needs to be a future consideration for
301
- this library.
302
- Scenarios to consider:
303
- ## Feed scope to archived *only*
304
- - Counts should not be decremented
305
- - Items should not be removed
306
- ## Feed scoped to exclude archived items (the default)
307
- - Counts should be decremented
308
- - Items should be removed
309
- ## Feed scoped to include archived items as well
310
- - Counts should not be decremented
311
- - Items should not be removed
312
- */
313
-
314
- if (shouldOptimisticallyRemoveItems) {
315
- // If any of the items are unseen or unread, then capture as we'll want to decrement
316
- // the counts for these in the metadata we have
317
- var unseenCount = normalizedItems.filter(i => !i.seen_at).length;
318
- var unreadCount = normalizedItems.filter(i => !i.read_at).length;
319
-
320
- // Build the new metadata
321
- var updatedMetadata = _objectSpread(_objectSpread({}, state.metadata), {}, {
322
- total_count: state.metadata.total_count - normalizedItems.length,
323
- unseen_count: state.metadata.unseen_count - unseenCount,
324
- unread_count: state.metadata.unread_count - unreadCount
325
- });
326
-
327
- // Remove the archiving entries
328
- var entriesToSet = state.items.filter(item => !itemIds.includes(item.id));
329
- setState(state => state.setResult({
330
- entries: entriesToSet,
331
- meta: updatedMetadata,
332
- page_info: state.pageInfo
333
- }));
334
- } else {
335
- // Mark all the entries being updated as archived either way so the state is correct
336
- state.setItemAttrs(itemIds, {
337
- archived_at: new Date().toISOString()
338
- });
339
- }
340
- return _this8.makeStatusUpdate(itemOrItems, "archived");
341
- })();
142
+ Marking one or more items as archived should:
143
+
144
+ - Decrement the badge count for any unread / unseen items
145
+ - Remove the item from the feed list when the `archived` flag is "exclude" (default)
146
+
147
+ TODO: how do we handle rollbacks?
148
+ */
149
+ async markAsArchived(e) {
150
+ const { getState: t, setState: s } = this.store, a = t(), r = this.defaultOptions.archived === "exclude", n = Array.isArray(e) ? e : [e], i = n.map((l) => l.id);
151
+ if (r) {
152
+ const l = n.filter((c) => !c.seen_at).length, o = n.filter((c) => !c.read_at).length, d = {
153
+ ...a.metadata,
154
+ total_count: a.metadata.total_count - n.length,
155
+ unseen_count: a.metadata.unseen_count - l,
156
+ unread_count: a.metadata.unread_count - o
157
+ }, h = a.items.filter(
158
+ (c) => !i.includes(c.id)
159
+ );
160
+ s(
161
+ (c) => c.setResult({
162
+ entries: h,
163
+ meta: d,
164
+ page_info: c.pageInfo
165
+ })
166
+ );
167
+ } else
168
+ a.setItemAttrs(i, { archived_at: (/* @__PURE__ */ new Date()).toISOString() });
169
+ return this.makeStatusUpdate(e, "archived");
342
170
  }
343
- markAllAsArchived() {
344
- var _this9 = this;
345
- return _asyncToGenerator(function* () {
346
- // Note: there is the potential for a race condition here because the bulk
347
- // update is an async method, so if a new message comes in during this window before
348
- // the update has been processed we'll effectively reset the `unseen_count` to be what it was.
349
- var {
350
- setState,
351
- getState
352
- } = _this9.store;
353
- var {
354
- items
355
- } = getState();
356
-
357
- // Here if we're looking at a feed that excludes all of the archived items by default then we
358
- // will want to optimistically remove all of the items from the feed as they are now all excluded
359
- var shouldOptimisticallyRemoveItems = _this9.defaultOptions.archived === "exclude";
360
- if (shouldOptimisticallyRemoveItems) {
361
- // Reset the store to clear out all of items and reset the badge count
362
- setState(store => store.resetStore());
363
- } else {
364
- // Mark all the entries being updated as archived either way so the state is correct
365
- setState(store => {
366
- var itemIds = items.map(i => i.id);
367
- store.setItemAttrs(itemIds, {
368
- archived_at: new Date().toISOString()
369
- });
370
- });
371
- }
372
-
373
- // Issue the API request to the bulk status change API
374
- var result = yield _this9.makeBulkStatusUpdate("archive");
375
- _this9.broadcaster.emit("items:all_archived", {
376
- items
377
- });
378
- _this9.broadcastOverChannel("items:all_archived", {
379
- items
380
- });
381
- return result;
382
- })();
171
+ async markAllAsArchived() {
172
+ const { setState: e, getState: t } = this.store, { items: s } = t(), a = this.defaultOptions.archived === "exclude";
173
+ e(a ? (n) => n.resetStore() : (n) => {
174
+ const i = s.map((l) => l.id);
175
+ n.setItemAttrs(i, { archived_at: (/* @__PURE__ */ new Date()).toISOString() });
176
+ });
177
+ const r = await this.makeBulkStatusUpdate("archive");
178
+ return this.broadcaster.emit("items:all_archived", { items: s }), this.broadcastOverChannel("items:all_archived", { items: s }), r;
383
179
  }
384
- markAsUnarchived(itemOrItems) {
385
- var _this10 = this;
386
- return _asyncToGenerator(function* () {
387
- _this10.optimisticallyPerformStatusUpdate(itemOrItems, "unarchived", {
388
- archived_at: null
389
- });
390
- return _this10.makeStatusUpdate(itemOrItems, "unarchived");
391
- })();
180
+ async markAsUnarchived(e) {
181
+ return this.optimisticallyPerformStatusUpdate(e, "unarchived", {
182
+ archived_at: null
183
+ }), this.makeStatusUpdate(e, "unarchived");
392
184
  }
393
-
394
185
  /* Fetches the feed content, appending it to the store */
395
- fetch() {
396
- var _arguments = arguments,
397
- _this11 = this;
398
- return _asyncToGenerator(function* () {
399
- var options = _arguments.length > 0 && _arguments[0] !== undefined ? _arguments[0] : {};
400
- var {
401
- setState,
402
- getState
403
- } = _this11.store;
404
- var {
405
- networkStatus
406
- } = getState();
407
-
408
- // If there's an existing request in flight, then do nothing
409
- if (isRequestInFlight(networkStatus)) {
410
- return;
411
- }
412
-
413
- // Set the loading type based on the request type it is
414
- setState(store => {
415
- var _options$__loadingTyp;
416
- return store.setNetworkStatus((_options$__loadingTyp = options.__loadingType) !== null && _options$__loadingTyp !== void 0 ? _options$__loadingTyp : NetworkStatus.loading);
417
- });
418
-
419
- // Always include the default params, if they have been set
420
- var queryParams = _objectSpread(_objectSpread(_objectSpread({}, _this11.defaultOptions), options), {}, {
421
- // Unset options that should not be sent to the API
422
- __loadingType: undefined,
423
- __fetchSource: undefined,
424
- __experimentalCrossBrowserUpdates: undefined,
425
- auto_manage_socket_connection: undefined,
426
- auto_manage_socket_connection_delay: undefined
427
- });
428
- var result = yield _this11.apiClient.makeRequest({
429
- method: "GET",
430
- url: "/v1/users/".concat(_this11.knock.userId, "/feeds/").concat(_this11.feedId),
431
- params: queryParams
432
- });
433
- if (result.statusCode === "error" || !result.body) {
434
- setState(store => store.setNetworkStatus(NetworkStatus.error));
435
- return {
436
- status: result.statusCode,
437
- data: result.error || result.body
438
- };
439
- }
440
- var response = {
441
- entries: result.body.entries,
442
- meta: result.body.meta,
443
- page_info: result.body.page_info
444
- };
445
- if (options.before) {
446
- var opts = {
447
- shouldSetPage: false,
448
- shouldAppend: true
449
- };
450
- setState(state => state.setResult(response, opts));
451
- } else if (options.after) {
452
- var _opts = {
453
- shouldSetPage: true,
454
- shouldAppend: true
455
- };
456
- setState(state => state.setResult(response, _opts));
457
- } else {
458
- setState(state => state.setResult(response));
459
- }
460
-
461
- // Legacy `messages.new` event, should be removed in a future version
462
- _this11.broadcast("messages.new", response);
463
-
464
- // Broadcast the appropriate event type depending on the fetch source
465
- var feedEventType = options.__fetchSource === "socket" ? "items.received.realtime" : "items.received.page";
466
- var eventPayload = {
467
- items: response.entries,
468
- metadata: response.meta,
469
- event: feedEventType
470
- };
471
- _this11.broadcast(eventPayload.event, eventPayload);
472
- return {
473
- data: response,
474
- status: result.statusCode
186
+ async fetch(e = {}) {
187
+ const { setState: t, getState: s } = this.store, { networkStatus: a } = s();
188
+ if (S(a))
189
+ return;
190
+ t(
191
+ (d) => d.setNetworkStatus(e.__loadingType ?? f.loading)
192
+ );
193
+ const r = {
194
+ ...this.defaultOptions,
195
+ ...e,
196
+ // Unset options that should not be sent to the API
197
+ __loadingType: void 0,
198
+ __fetchSource: void 0,
199
+ __experimentalCrossBrowserUpdates: void 0,
200
+ auto_manage_socket_connection: void 0,
201
+ auto_manage_socket_connection_delay: void 0
202
+ }, n = await this.knock.client().makeRequest({
203
+ method: "GET",
204
+ url: `/v1/users/${this.knock.userId}/feeds/${this.feedId}`,
205
+ params: r
206
+ });
207
+ if (n.statusCode === "error" || !n.body)
208
+ return t((d) => d.setNetworkStatus(f.error)), {
209
+ status: n.statusCode,
210
+ data: n.error || n.body
475
211
  };
476
- })();
212
+ const i = {
213
+ entries: n.body.entries,
214
+ meta: n.body.meta,
215
+ page_info: n.body.page_info
216
+ };
217
+ if (e.before) {
218
+ const d = { shouldSetPage: !1, shouldAppend: !0 };
219
+ t((h) => h.setResult(i, d));
220
+ } else if (e.after) {
221
+ const d = { shouldSetPage: !0, shouldAppend: !0 };
222
+ t((h) => h.setResult(i, d));
223
+ } else
224
+ t((d) => d.setResult(i));
225
+ this.broadcast("messages.new", i);
226
+ const l = e.__fetchSource === "socket" ? "items.received.realtime" : "items.received.page", o = {
227
+ items: i.entries,
228
+ metadata: i.meta,
229
+ event: l
230
+ };
231
+ return this.broadcast(o.event, o), { data: i, status: n.statusCode };
477
232
  }
478
- fetchNextPage() {
479
- var _this12 = this;
480
- return _asyncToGenerator(function* () {
481
- // Attempts to fetch the next page of results (if we have any)
482
- var {
483
- getState
484
- } = _this12.store;
485
- var {
486
- pageInfo
487
- } = getState();
488
- if (!pageInfo.after) {
489
- // Nothing more to fetch
490
- return;
491
- }
492
- _this12.fetch({
493
- after: pageInfo.after,
494
- __loadingType: NetworkStatus.fetchMore
495
- });
496
- })();
233
+ async fetchNextPage() {
234
+ const { getState: e } = this.store, { pageInfo: t } = e();
235
+ t.after && this.fetch({
236
+ after: t.after,
237
+ __loadingType: f.fetchMore
238
+ });
497
239
  }
498
- broadcast(eventName, data) {
499
- this.broadcaster.emit(eventName, data);
240
+ broadcast(e, t) {
241
+ this.broadcaster.emit(e, t);
500
242
  }
501
-
502
243
  // Invoked when a new real-time message comes in from the socket
503
- onNewMessageReceived(_ref) {
504
- var _this13 = this;
505
- return _asyncToGenerator(function* () {
506
- var {
507
- metadata
508
- } = _ref;
509
- // Handle the new message coming in
510
- var {
511
- getState,
512
- setState
513
- } = _this13.store;
514
- var {
515
- items
516
- } = getState();
517
- var currentHead = items[0];
518
- // Optimistically set the badge counts
519
- setState(state => state.setMetadata(metadata));
520
- // Fetch the items before the current head (if it exists)
521
- _this13.fetch({
522
- before: currentHead === null || currentHead === void 0 ? void 0 : currentHead.__cursor,
523
- __fetchSource: "socket"
524
- });
525
- })();
244
+ async onNewMessageReceived({
245
+ metadata: e
246
+ }) {
247
+ this.knock.log("[Feed] Received new real-time message");
248
+ const { getState: t, setState: s } = this.store, { items: a } = t(), r = a[0];
249
+ s((n) => n.setMetadata(e)), this.fetch({ before: r == null ? void 0 : r.__cursor, __fetchSource: "socket" });
526
250
  }
527
251
  buildUserFeedId() {
528
- return "".concat(this.feedId, ":").concat(this.knock.userId);
252
+ return `${this.feedId}:${this.knock.userId}`;
529
253
  }
530
- optimisticallyPerformStatusUpdate(itemOrItems, type, attrs, badgeCountAttr) {
531
- var {
532
- getState,
533
- setState
534
- } = this.store;
535
- var normalizedItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
536
- var itemIds = normalizedItems.map(item => item.id);
537
- if (badgeCountAttr) {
538
- var {
539
- metadata
540
- } = getState();
541
-
542
- // We only want to update the counts of items that have not already been counted towards the
543
- // badge count total to avoid updating the badge count unnecessarily.
544
- var itemsToUpdate = normalizedItems.filter(item => {
545
- switch (type) {
254
+ optimisticallyPerformStatusUpdate(e, t, s, a) {
255
+ const { getState: r, setState: n } = this.store, i = Array.isArray(e) ? e : [e], l = i.map((o) => o.id);
256
+ if (a) {
257
+ const { metadata: o } = r(), d = i.filter((c) => {
258
+ switch (t) {
546
259
  case "seen":
547
- return item.seen_at === null;
260
+ return c.seen_at === null;
548
261
  case "unseen":
549
- return item.seen_at !== null;
262
+ return c.seen_at !== null;
550
263
  case "read":
551
264
  case "interacted":
552
- return item.read_at === null;
265
+ return c.read_at === null;
553
266
  case "unread":
554
- return item.read_at !== null;
267
+ return c.read_at !== null;
555
268
  default:
556
- return true;
269
+ return !0;
557
270
  }
558
- });
559
-
560
- // Tnis is a hack to determine the direction of whether we're
561
- // adding or removing from the badge count
562
- var direction = type.startsWith("un") ? itemsToUpdate.length : -itemsToUpdate.length;
563
- setState(store => store.setMetadata(_objectSpread(_objectSpread({}, metadata), {}, {
564
- [badgeCountAttr]: Math.max(0, metadata[badgeCountAttr] + direction)
565
- })));
271
+ }), h = t.startsWith("un") ? d.length : -d.length;
272
+ n(
273
+ (c) => c.setMetadata({
274
+ ...o,
275
+ [a]: Math.max(0, o[a] + h)
276
+ })
277
+ );
566
278
  }
567
-
568
- // Update the items with the given attributes
569
- setState(store => store.setItemAttrs(itemIds, attrs));
279
+ n((o) => o.setItemAttrs(l, s));
570
280
  }
571
- makeStatusUpdate(itemOrItems, type) {
572
- var _this14 = this;
573
- return _asyncToGenerator(function* () {
574
- // Always treat items as a batch to use the corresponding batch endpoint
575
- var items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
576
- var itemIds = items.map(item => item.id);
577
- var result = yield _this14.apiClient.makeRequest({
578
- method: "POST",
579
- url: "/v1/messages/batch/".concat(type),
580
- data: {
581
- message_ids: itemIds
582
- }
583
- });
584
-
585
- // Emit the event that these items had their statuses changed
586
- // Note: we do this after the update to ensure that the server event actually completed
587
- _this14.broadcaster.emit("items:".concat(type), {
588
- items
589
- });
590
- _this14.broadcastOverChannel("items:".concat(type), {
591
- items
592
- });
593
- return result;
594
- })();
281
+ async makeStatusUpdate(e, t) {
282
+ const s = Array.isArray(e) ? e : [e], a = s.map((n) => n.id), r = await this.knock.client().makeRequest({
283
+ method: "POST",
284
+ url: `/v1/messages/batch/${t}`,
285
+ data: { message_ids: a }
286
+ });
287
+ return this.broadcaster.emit(`items:${t}`, { items: s }), this.broadcastOverChannel(`items:${t}`, { items: s }), r;
595
288
  }
596
- makeBulkStatusUpdate(type) {
597
- var _this15 = this;
598
- return _asyncToGenerator(function* () {
599
- // The base scope for the call should take into account all of the options currently
600
- // set on the feed, as well as being scoped for the current user. We do this so that
601
- // we ONLY make changes to the messages that are currently in view on this feed, and not
602
- // all messages that exist.
603
- var options = {
604
- user_ids: [_this15.knock.userId],
605
- engagement_status: _this15.defaultOptions.status !== "all" ? _this15.defaultOptions.status : undefined,
606
- archived: _this15.defaultOptions.archived,
607
- has_tenant: _this15.defaultOptions.has_tenant,
608
- tenants: _this15.defaultOptions.tenant ? [_this15.defaultOptions.tenant] : undefined
609
- };
610
- return yield _this15.apiClient.makeRequest({
611
- method: "POST",
612
- url: "/v1/channels/".concat(_this15.feedId, "/messages/bulk/").concat(type),
613
- data: options
614
- });
615
- })();
289
+ async makeBulkStatusUpdate(e) {
290
+ const t = {
291
+ user_ids: [this.knock.userId],
292
+ engagement_status: this.defaultOptions.status !== "all" ? this.defaultOptions.status : void 0,
293
+ archived: this.defaultOptions.archived,
294
+ has_tenant: this.defaultOptions.has_tenant,
295
+ tenants: this.defaultOptions.tenant ? [this.defaultOptions.tenant] : void 0
296
+ };
297
+ return await this.knock.client().makeRequest({
298
+ method: "POST",
299
+ url: `/v1/channels/${this.feedId}/messages/bulk/${e}`,
300
+ data: t
301
+ });
616
302
  }
617
- broadcastOverChannel(type, payload) {
618
- // The broadcastChannel may not be available in non-browser environments
619
- if (!this.broadcastChannel) {
620
- return;
621
- }
622
-
623
- // Here we stringify our payload and try and send as JSON such that we
624
- // don't get any `An object could not be cloned` errors when trying to broadcast
625
- try {
626
- var stringifiedPayload = JSON.parse(JSON.stringify(payload));
627
- this.broadcastChannel.postMessage({
628
- type,
629
- payload: stringifiedPayload
630
- });
631
- } catch (e) {
632
- console.warn("Could not broadcast ".concat(type, ", got error: ").concat(e));
633
- }
303
+ setupBroadcastChannel() {
304
+ this.broadcastChannel = typeof self < "u" && "BroadcastChannel" in self ? new BroadcastChannel(`knock:feed:${this.userFeedId}`) : null, this.broadcastChannel && this.defaultOptions.__experimentalCrossBrowserUpdates === !0 && (this.broadcastChannel.onmessage = (e) => {
305
+ switch (e.data.type) {
306
+ case "items:archived":
307
+ case "items:unarchived":
308
+ case "items:seen":
309
+ case "items:unseen":
310
+ case "items:read":
311
+ case "items:unread":
312
+ case "items:all_read":
313
+ case "items:all_seen":
314
+ case "items:all_archived":
315
+ return this.fetch();
316
+ default:
317
+ return null;
318
+ }
319
+ });
320
+ }
321
+ broadcastOverChannel(e, t) {
322
+ if (this.broadcastChannel)
323
+ try {
324
+ const s = JSON.parse(JSON.stringify(t));
325
+ this.broadcastChannel.postMessage({
326
+ type: e,
327
+ payload: s
328
+ });
329
+ } catch (s) {
330
+ console.warn(`Could not broadcast ${e}, got error: ${s}`);
331
+ }
332
+ }
333
+ initializeRealtimeConnection() {
334
+ const { socket: e } = this.knock.client();
335
+ e && (this.channel = e.channel(
336
+ `feeds:${this.userFeedId}`,
337
+ this.defaultOptions
338
+ ), this.channel.on("new-message", (t) => this.onNewMessageReceived(t)), this.defaultOptions.auto_manage_socket_connection && this.setupAutoSocketManager(
339
+ this.defaultOptions.auto_manage_socket_connection_delay
340
+ ), this.hasSubscribedToRealTimeUpdates && (e.isConnected() || e.connect(), this.channel.join()));
634
341
  }
635
-
636
342
  /**
637
343
  * Listen for changes to document visibility and automatically disconnect
638
344
  * or reconnect the socket after a delay
639
345
  */
640
- setupAutoSocketManager(autoManageSocketConnectionDelay) {
641
- var disconnectDelay = autoManageSocketConnectionDelay !== null && autoManageSocketConnectionDelay !== void 0 ? autoManageSocketConnectionDelay : DEFAULT_DISCONNECT_DELAY;
346
+ setupAutoSocketManager(e) {
347
+ const t = e ?? y;
642
348
  document.addEventListener("visibilitychange", () => {
643
- if (document.visibilityState === "hidden") {
644
- // When the tab is hidden, clean up the socket connection after a delay
645
- this.disconnectTimer = setTimeout(() => {
646
- this.apiClient.disconnectSocket();
647
- this.disconnectTimer = null;
648
- }, disconnectDelay);
649
- } else if (document.visibilityState === "visible") {
650
- var _this$apiClient$socke;
651
- // When the tab is visible, clear the disconnect timer if active to cancel disconnecting
652
- // This handles cases where the tab is only briefly hidden to avoid unnecessary disconnects
653
- if (this.disconnectTimer) {
654
- clearTimeout(this.disconnectTimer);
655
- this.disconnectTimer = null;
656
- }
657
-
658
- // If the socket is not connected, try to reconnect
659
- if (!((_this$apiClient$socke = this.apiClient.socket) !== null && _this$apiClient$socke !== void 0 && _this$apiClient$socke.isConnected())) {
660
- this.apiClient.reconnectSocket();
661
- this.fetch();
662
- }
663
- }
349
+ var a;
350
+ const s = this.knock.client();
351
+ document.visibilityState === "hidden" ? this.disconnectTimer = setTimeout(() => {
352
+ var r;
353
+ (r = s.socket) == null || r.disconnect(), this.disconnectTimer = null;
354
+ }, t) : document.visibilityState === "visible" && (this.disconnectTimer && (clearTimeout(this.disconnectTimer), this.disconnectTimer = null), (a = s.socket) != null && a.isConnected() || this.initializeRealtimeConnection());
664
355
  });
665
356
  }
666
357
  }
667
- export default Feed;
668
- //# sourceMappingURL=feed.js.map
358
+ export {
359
+ A as default
360
+ };
361
+ //# sourceMappingURL=feed.js.map