@repzo/widget-react-native 1.4.0 → 1.5.0
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 +36 -0
- package/README.md +36 -2
- package/lib/commonjs/api/client.js +29 -5
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/state/user-update-queue.js +168 -0
- package/lib/commonjs/state/user-update-queue.js.map +1 -0
- package/lib/commonjs/state/widget-store.js +27 -4
- package/lib/commonjs/state/widget-store.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.js +14 -1
- package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/module/api/client.js +27 -4
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/state/user-update-queue.js +163 -0
- package/lib/module/state/user-update-queue.js.map +1 -0
- package/lib/module/state/widget-store.js +27 -4
- package/lib/module/state/widget-store.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.js +14 -1
- package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/typescript/commonjs/api/client.d.ts +8 -2
- package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +5 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/state/user-update-queue.d.ts +62 -0
- package/lib/typescript/commonjs/state/user-update-queue.d.ts.map +1 -0
- package/lib/typescript/commonjs/state/widget-store.d.ts +4 -0
- package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +12 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/module/api/client.d.ts +8 -2
- package/lib/typescript/module/api/client.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +5 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/state/user-update-queue.d.ts +62 -0
- package/lib/typescript/module/state/user-update-queue.d.ts.map +1 -0
- package/lib/typescript/module/state/widget-store.d.ts +4 -0
- package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +12 -0
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/client.ts +33 -4
- package/src/index.tsx +5 -0
- package/src/state/user-update-queue.ts +178 -0
- package/src/state/widget-store.ts +25 -2
- package/src/types.ts +12 -0
- package/src/ui/screens/chat/ChatView.tsx +14 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Paces `updateUser()` the way Intercom paces its web `update` (20 calls per
|
|
5
|
+
* 30 minutes), so a host that calls it on a timer or on every GPS fix can't
|
|
6
|
+
* flood Workstation.
|
|
7
|
+
*
|
|
8
|
+
* - Values that match what Workstation already has are not sent again.
|
|
9
|
+
* - Up to `burst` sends go out straight away; after that one more is allowed
|
|
10
|
+
* every `windowMs / burst` (90 s by default).
|
|
11
|
+
* - While a send is in flight, rate limited or out of budget, new values wait
|
|
12
|
+
* and merge into one bag, newest value per key. Nothing is lost: the latest
|
|
13
|
+
* of every key goes out on the next allowed send. Workstation merges the bag
|
|
14
|
+
* into the saved attributes per key, so sending only what changed is enough.
|
|
15
|
+
* - A 429 holds sends until its `Retry-After`. A failed connection or a 5xx
|
|
16
|
+
* keeps the values and tries again later.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { RepzoWidgetError } from "../api/client.js";
|
|
20
|
+
export const USER_UPDATE_BURST = 20;
|
|
21
|
+
export const USER_UPDATE_WINDOW_MS = 30 * 60 * 1000;
|
|
22
|
+
/** Wait after a 429 without a usable `Retry-After`, or after a failed connection / 5xx. */
|
|
23
|
+
const DEFAULT_HOLD_MS = 60 * 1000;
|
|
24
|
+
export class UserUpdateQueue {
|
|
25
|
+
/** Values Workstation accepted for the current identity. */
|
|
26
|
+
sent = {};
|
|
27
|
+
/** Values waiting for the next allowed send. */
|
|
28
|
+
pending = null;
|
|
29
|
+
inFlight = false;
|
|
30
|
+
holdUntil = 0;
|
|
31
|
+
timer = null;
|
|
32
|
+
/** Bumped by reset(): a send that settles after it writes nothing back. */
|
|
33
|
+
generation = 0;
|
|
34
|
+
constructor(send, options = {}) {
|
|
35
|
+
this.send = send;
|
|
36
|
+
this.burst = Math.max(1, options.burst ?? USER_UPDATE_BURST);
|
|
37
|
+
this.refillMs = (options.windowMs ?? USER_UPDATE_WINDOW_MS) / this.burst;
|
|
38
|
+
this.now = options.now ?? Date.now;
|
|
39
|
+
this.tokens = this.burst;
|
|
40
|
+
this.refilledAt = this.now();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolves once the values are sent, or straight away when nothing changed
|
|
45
|
+
* or they were held for a later send. Rejects only when a send made right
|
|
46
|
+
* now fails with something other than a 429, as `updateUser()` always has.
|
|
47
|
+
*/
|
|
48
|
+
update(attributes) {
|
|
49
|
+
const changes = {};
|
|
50
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
51
|
+
const known = this.pending && key in this.pending ? this.pending[key] : this.sent[key];
|
|
52
|
+
if (known !== value) changes[key] = value;
|
|
53
|
+
}
|
|
54
|
+
if (Object.keys(changes).length === 0) return Promise.resolve();
|
|
55
|
+
this.pending = {
|
|
56
|
+
...this.pending,
|
|
57
|
+
...changes
|
|
58
|
+
};
|
|
59
|
+
if (this.inFlight || this.now() < this.holdUntil || !this.takeToken()) {
|
|
60
|
+
this.schedule();
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
}
|
|
63
|
+
return this.sendPending();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The visitor's identity changed (anonymous → signed in): Workstation may hold other values now. */
|
|
67
|
+
forgetSent() {
|
|
68
|
+
this.sent = {};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Sign-out or a different user: drop everything waiting so it can't reach the next visitor. */
|
|
72
|
+
reset() {
|
|
73
|
+
this.generation += 1;
|
|
74
|
+
this.clearTimer();
|
|
75
|
+
this.sent = {};
|
|
76
|
+
this.pending = null;
|
|
77
|
+
this.inFlight = false;
|
|
78
|
+
this.holdUntil = 0;
|
|
79
|
+
}
|
|
80
|
+
destroy() {
|
|
81
|
+
this.reset();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The send budget is per device, not per visitor, so reset() leaves it alone. */
|
|
85
|
+
takeToken() {
|
|
86
|
+
this.refill();
|
|
87
|
+
if (this.tokens < 1) return false;
|
|
88
|
+
this.tokens -= 1;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
refill() {
|
|
92
|
+
const now = this.now();
|
|
93
|
+
if (this.tokens >= this.burst) {
|
|
94
|
+
this.refilledAt = now;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const earned = Math.floor((now - this.refilledAt) / this.refillMs);
|
|
98
|
+
if (earned <= 0) return;
|
|
99
|
+
this.tokens = Math.min(this.burst, this.tokens + earned);
|
|
100
|
+
this.refilledAt = this.tokens === this.burst ? now : this.refilledAt + earned * this.refillMs;
|
|
101
|
+
}
|
|
102
|
+
async sendPending() {
|
|
103
|
+
const attributes = this.pending ?? {};
|
|
104
|
+
const generation = this.generation;
|
|
105
|
+
this.pending = null;
|
|
106
|
+
this.inFlight = true;
|
|
107
|
+
try {
|
|
108
|
+
await this.send(attributes);
|
|
109
|
+
if (generation !== this.generation) return;
|
|
110
|
+
this.sent = {
|
|
111
|
+
...this.sent,
|
|
112
|
+
...attributes
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (generation !== this.generation) throw error;
|
|
116
|
+
const status = error instanceof RepzoWidgetError ? error.status : 0;
|
|
117
|
+
const retryable = status === 429 || status === 0 || status >= 500;
|
|
118
|
+
if (retryable) {
|
|
119
|
+
// Newer values that arrived meanwhile win over the ones that failed.
|
|
120
|
+
this.pending = {
|
|
121
|
+
...attributes,
|
|
122
|
+
...(this.pending ?? {})
|
|
123
|
+
};
|
|
124
|
+
const retryAfter = error instanceof RepzoWidgetError ? error.retryAfter : undefined;
|
|
125
|
+
const holdMs = status === 429 && retryAfter && retryAfter > 0 ? retryAfter * 1000 : DEFAULT_HOLD_MS;
|
|
126
|
+
this.holdUntil = Math.max(this.holdUntil, this.now() + holdMs);
|
|
127
|
+
}
|
|
128
|
+
// Rate limited: the values are kept and go out later, so the call succeeded.
|
|
129
|
+
if (status !== 429) throw error;
|
|
130
|
+
} finally {
|
|
131
|
+
if (generation === this.generation) {
|
|
132
|
+
this.inFlight = false;
|
|
133
|
+
if (this.pending) this.schedule();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
schedule() {
|
|
138
|
+
if (this.timer || this.inFlight || !this.pending) return;
|
|
139
|
+
this.refill();
|
|
140
|
+
const now = this.now();
|
|
141
|
+
const tokenAt = this.tokens >= 1 ? now : this.refilledAt + this.refillMs;
|
|
142
|
+
const delay = Math.max(0, this.holdUntil - now, tokenAt - now);
|
|
143
|
+
this.timer = setTimeout(() => {
|
|
144
|
+
this.timer = null;
|
|
145
|
+
this.flush();
|
|
146
|
+
}, delay);
|
|
147
|
+
}
|
|
148
|
+
flush() {
|
|
149
|
+
if (this.inFlight || !this.pending) return;
|
|
150
|
+
if (this.now() < this.holdUntil || !this.takeToken()) {
|
|
151
|
+
this.schedule();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.sendPending().catch(error => {
|
|
155
|
+
console.warn('[repzo-widget] updateUser failed', error);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
clearTimer() {
|
|
159
|
+
if (this.timer) clearTimeout(this.timer);
|
|
160
|
+
this.timer = null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=user-update-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["RepzoWidgetError","USER_UPDATE_BURST","USER_UPDATE_WINDOW_MS","DEFAULT_HOLD_MS","UserUpdateQueue","sent","pending","inFlight","holdUntil","timer","generation","constructor","send","options","burst","Math","max","refillMs","windowMs","now","Date","tokens","refilledAt","update","attributes","changes","key","value","Object","entries","known","keys","length","Promise","resolve","takeToken","schedule","sendPending","forgetSent","reset","clearTimer","destroy","refill","earned","floor","min","error","status","retryable","retryAfter","undefined","holdMs","tokenAt","delay","setTimeout","flush","catch","console","warn","clearTimeout"],"sourceRoot":"../../../src","sources":["state/user-update-queue.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -24,9 +24,10 @@ import { clearRetainedState } from "../ui/hooks/use-retained-state.js";
|
|
|
24
24
|
import { canRetryMessage, isLocalMessage } from "../ui/utils/conversations.js";
|
|
25
25
|
import { stripHtml } from "../ui/utils/html.js";
|
|
26
26
|
import { setOpenUrlHandler } from "../ui/utils/safe-url.js";
|
|
27
|
+
import { UserUpdateQueue } from "./user-update-queue.js";
|
|
27
28
|
|
|
28
29
|
/** Sent on every init; must match `package.json` (tests/version.test.js checks). */
|
|
29
|
-
const SDK_VERSION = '@repzo/widget-react-native@1.
|
|
30
|
+
const SDK_VERSION = '@repzo/widget-react-native@1.5.0';
|
|
30
31
|
|
|
31
32
|
/** The platform this session reports — mobile sessions get the Mobile SDKs overrides. */
|
|
32
33
|
function sessionPlatform() {
|
|
@@ -124,6 +125,11 @@ const USER_SCOPED_STATE = {
|
|
|
124
125
|
};
|
|
125
126
|
export class WidgetStore {
|
|
126
127
|
client = new WidgetApiClient();
|
|
128
|
+
|
|
129
|
+
/** Paces `updateUser()` — see `boot({ throttleUpdateUser })`. */
|
|
130
|
+
userUpdates = new UserUpdateQueue(customAttributes => this.client.identify({
|
|
131
|
+
customAttributes
|
|
132
|
+
}));
|
|
127
133
|
events = new EventEmitter();
|
|
128
134
|
push = new PushManager(this.client, conversationId => {
|
|
129
135
|
void this.openConversation(conversationId);
|
|
@@ -144,6 +150,8 @@ export class WidgetStore {
|
|
|
144
150
|
photoAlbumsEnabled = true;
|
|
145
151
|
/** `boot({ youPrefix })` — "You:" before the visitor's own newest message in the lists. */
|
|
146
152
|
youPrefixEnabled = true;
|
|
153
|
+
/** `boot({ throttleUpdateUser })` — skip unchanged `updateUser()` values and pace the rest. */
|
|
154
|
+
throttleUpdateUserEnabled = true;
|
|
147
155
|
|
|
148
156
|
/** Posts whose view was reported this session (the server also dedupes per day). */
|
|
149
157
|
viewedPostIds = new Set();
|
|
@@ -356,6 +364,9 @@ export class WidgetStore {
|
|
|
356
364
|
}
|
|
357
365
|
if (options.photoAlbums !== undefined) this.photoAlbumsEnabled = options.photoAlbums !== false;
|
|
358
366
|
if (options.youPrefix !== undefined) this.youPrefixEnabled = options.youPrefix !== false;
|
|
367
|
+
if (options.throttleUpdateUser !== undefined) {
|
|
368
|
+
this.throttleUpdateUserEnabled = options.throttleUpdateUser !== false;
|
|
369
|
+
}
|
|
359
370
|
if (options.posts !== undefined) {
|
|
360
371
|
const wasEnabled = this.postsEnabled;
|
|
361
372
|
this.postsEnabled = options.posts !== false;
|
|
@@ -526,6 +537,10 @@ export class WidgetStore {
|
|
|
526
537
|
const onScreenId = this.state.activeConversationId;
|
|
527
538
|
if (onScreenId && this.isThreadOnScreen(onScreenId)) delete unreadByConversation[onScreenId];
|
|
528
539
|
this.persistUnread(unreadByConversation);
|
|
540
|
+
|
|
541
|
+
// A new identity (anonymous → signed in) may hold other attributes, so
|
|
542
|
+
// nothing is skipped as "already sent" for it.
|
|
543
|
+
if (result.identityId !== this.state.identityId) this.userUpdates.forgetSent();
|
|
529
544
|
this.setState(current => ({
|
|
530
545
|
booted: true,
|
|
531
546
|
loading: false,
|
|
@@ -754,9 +769,13 @@ export class WidgetStore {
|
|
|
754
769
|
if (!this.state.booted) await this.bootPromise;
|
|
755
770
|
const customAttributes = sanitizeAttributes(attributes);
|
|
756
771
|
if (Object.keys(customAttributes).length === 0) return;
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
772
|
+
if (!this.throttleUpdateUserEnabled) {
|
|
773
|
+
await this.client.identify({
|
|
774
|
+
customAttributes
|
|
775
|
+
});
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
await this.userUpdates.update(customAttributes);
|
|
760
779
|
}
|
|
761
780
|
|
|
762
781
|
/**
|
|
@@ -768,6 +787,7 @@ export class WidgetStore {
|
|
|
768
787
|
this.loggedOut = true;
|
|
769
788
|
this.currentUser = null;
|
|
770
789
|
this.pendingConversationId = null;
|
|
790
|
+
this.userUpdates.reset();
|
|
771
791
|
this.realtime.close();
|
|
772
792
|
this.clearTypingTimer();
|
|
773
793
|
this.resetThreadTracking();
|
|
@@ -799,6 +819,8 @@ export class WidgetStore {
|
|
|
799
819
|
}
|
|
800
820
|
async wipeSessionUnguarded() {
|
|
801
821
|
this.realtime.close();
|
|
822
|
+
// Attributes waiting for the old visitor must never reach the next one.
|
|
823
|
+
this.userUpdates.reset();
|
|
802
824
|
this.clearTypingTimer();
|
|
803
825
|
this.resetThreadTracking();
|
|
804
826
|
this.appStateSubscription?.remove();
|
|
@@ -2609,6 +2631,7 @@ export class WidgetStore {
|
|
|
2609
2631
|
// ─── Teardown (tests / hot reload) ──────────────────────────────────────
|
|
2610
2632
|
|
|
2611
2633
|
destroy() {
|
|
2634
|
+
this.userUpdates.destroy();
|
|
2612
2635
|
this.clearPostRecheck();
|
|
2613
2636
|
this.clearPostThanks();
|
|
2614
2637
|
this.realtime.close();
|