@loxel.dev/pharos-browser 0.7.0 → 0.8.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/README.md +45 -2
- package/TECHNICAL-DETAILS.md +24 -6
- package/dist/{index-yabengpp.js → index-temc0wzg.js} +106 -7
- package/dist/{wire-p2kvy6dd.js → index-xs32mrd9.js} +3 -8
- package/dist/index.d.ts +44 -5
- package/dist/index.js +56 -41
- package/dist/replay/index.js +9 -2
- package/dist/wire-90whxzan.js +13 -0
- package/package.json +3 -2
- package/dist/index-c3taa3cg.js +0 -9
package/README.md
CHANGED
|
@@ -127,8 +127,10 @@ targeting rules — send nothing if no rule reads it.
|
|
|
127
127
|
|
|
128
128
|
```typescript
|
|
129
129
|
// Synchronous, no network call — the value was computed server-side and is
|
|
130
|
-
// already here. Your default is returned until the bootstrap lands,
|
|
131
|
-
//
|
|
130
|
+
// already here. Your default is returned until the bootstrap lands, for any
|
|
131
|
+
// key Pharos does not know, and for a value whose type disagrees with your
|
|
132
|
+
// default — a flag sent as the string "false" is not a boolean, and reading
|
|
133
|
+
// it through a boolean gate would otherwise turn the gate ON.
|
|
132
134
|
if (client.flag("new-nav", false)) {
|
|
133
135
|
renderNewNav();
|
|
134
136
|
}
|
|
@@ -154,6 +156,13 @@ await client.identify({
|
|
|
154
156
|
});
|
|
155
157
|
```
|
|
156
158
|
|
|
159
|
+
The new user's flags arrive with it, so `identify()` fires `change` the same
|
|
160
|
+
way a server push does — a component that mounted while the user was
|
|
161
|
+
anonymous re-renders against the identified bucket without the app arranging
|
|
162
|
+
anything. If the re-bootstrap fails it still rejects (catch it), and the
|
|
163
|
+
client schedules a reconnect rather than leaving live updates dead until a
|
|
164
|
+
page reload.
|
|
165
|
+
|
|
157
166
|
### Shut down
|
|
158
167
|
|
|
159
168
|
```typescript
|
|
@@ -318,6 +327,40 @@ you can verify what a recorder would send before turning one on.
|
|
|
318
327
|
|
|
319
328
|
## Version history
|
|
320
329
|
|
|
330
|
+
- `0.8.0` — **`identify()` tells you the flags changed, and a failed one no
|
|
331
|
+
longer kills live updates** (#788).
|
|
332
|
+
|
|
333
|
+
`identify()` swaps the whole flags map and emitted nothing, so a component
|
|
334
|
+
that rendered against the anonymous bootstrap kept that value for the life
|
|
335
|
+
of its mount while the server evaluated the real user's bucket on every
|
|
336
|
+
request. It now emits `change` with the new payload. If its bootstrap
|
|
337
|
+
rejects it still rejects — an app awaiting it on login has to see that —
|
|
338
|
+
but it now also schedules a reconnect, where before the stream was left
|
|
339
|
+
dead with nothing pending and nothing reported. Reconnects back off
|
|
340
|
+
exponentially (1 s doubling to a 30 s ceiling, reset by a delivered `flags`
|
|
341
|
+
push) instead of re-bootstrapping every second for the length of an outage.
|
|
342
|
+
|
|
343
|
+
`flag(key, default)` now **returns your default when the stored value's
|
|
344
|
+
type disagrees with it**. It was a cast (`as T`), so a flag delivered as the
|
|
345
|
+
string `"false"` read as ON through a boolean gate. `null`/`undefined`
|
|
346
|
+
defaults opt out of the check, since `typeof` describes the absence of a
|
|
347
|
+
value for both.
|
|
348
|
+
|
|
349
|
+
**The replay sensitive-field word list is no longer English-only** (#823).
|
|
350
|
+
It now carries the common Spanish, French, German, Portuguese, Japanese and
|
|
351
|
+
Chinese spellings of the same fields, and normalization folds accents
|
|
352
|
+
instead of deleting them (`contraseña` → `contrasena`, `Straße` →
|
|
353
|
+
`strasse`) — without which every accented entry would have missed by a
|
|
354
|
+
character. A localised app previously got essentially nothing from this
|
|
355
|
+
layer. Annotating `autocomplete` is still the recommended integration step:
|
|
356
|
+
it is language-independent and is checked first.
|
|
357
|
+
|
|
358
|
+
**Why MINOR:** `change` now fires on a path where it did not, a
|
|
359
|
+
type-mismatched flag stops being returned, and a localised app's fields are
|
|
360
|
+
masked where they were recorded — so a pinned `collectRecordedStrings`
|
|
361
|
+
snapshot will differ. Every move is in the safe direction, but a consumer
|
|
362
|
+
sees different behaviour either way.
|
|
363
|
+
|
|
321
364
|
- `0.7.0` — **a marker on a shadow host now protects its shadow root** (#821),
|
|
322
365
|
and the verification helper sees what the recorder sees (#822).
|
|
323
366
|
|
package/TECHNICAL-DETAILS.md
CHANGED
|
@@ -185,14 +185,23 @@ documented in full in
|
|
|
185
185
|
- `flag<T>(key, defaultValue): T` — synchronous map lookup, no network call,
|
|
186
186
|
no evaluation. Returns `defaultValue` for any key not present in the
|
|
187
187
|
current flags map (unknown key, or a `server_side`-only flag the bootstrap
|
|
188
|
-
endpoint never sent)
|
|
188
|
+
endpoint never sent), and for a stored value whose `typeof` disagrees with
|
|
189
|
+
`defaultValue`'s — the string `"false"` is not a boolean and must not read
|
|
190
|
+
as one. A `null`/`undefined` default opts out of that check, because
|
|
191
|
+
`typeof` describes the absence of a value for both and checking against it
|
|
192
|
+
would return the default unconditionally.
|
|
189
193
|
- `on(event, cb): () => void` — subscribe to `"change"` (a new flags payload
|
|
190
194
|
was applied — fires exactly once per accepted server push) or `"error"`
|
|
191
195
|
(stream/bootstrap failure). Returns an unsubscribe function.
|
|
192
196
|
- `identify(context): Promise<void>` — re-bootstraps as a new context and
|
|
193
197
|
reopens the stream against the fresh `streamToken`. **This is a full HTTP
|
|
194
198
|
round trip, not a local re-key** — call it on meaningful identity changes
|
|
195
|
-
(login, plan change), not per-render.
|
|
199
|
+
(login, plan change), not per-render. It **fires `"change"`** with the new
|
|
200
|
+
`{version, flags}` once the stream is back up: it replaced the flags map,
|
|
201
|
+
and a consumer that only watched `"change"` would otherwise render the
|
|
202
|
+
previous context's values for the life of its mount. A rejected bootstrap
|
|
203
|
+
**schedules a reconnect** as well as rejecting, so a failure here costs the
|
|
204
|
+
call, not the session's live updates.
|
|
196
205
|
- `captureException(err, opts?): void` — reports one exception; see
|
|
197
206
|
"Error reporting" above. `opts` is `{site?: string, attributes?: Record<string, unknown>}`.
|
|
198
207
|
Never throws; a failed POST is reported via `on("error")`. A no-op after
|
|
@@ -295,10 +304,19 @@ own. The one case it cannot recover from is an **expired stream session**
|
|
|
295
304
|
(the server holds a 10-minute token TTL, refreshed only at connect): the
|
|
296
305
|
browser sees repeated errors and keeps retrying the same dead token forever.
|
|
297
306
|
When `EventSource` settles into its terminal `CLOSED` state, this client
|
|
298
|
-
re-bootstraps
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
307
|
+
re-bootstraps to mint a fresh token and reconnect. The same schedule covers a
|
|
308
|
+
rejected `identify()`, which is otherwise the one way to end up with no
|
|
309
|
+
stream and no `EventSource` left to report an error.
|
|
310
|
+
|
|
311
|
+
Retries **back off exponentially**: 1 s, doubling to a 30 s ceiling. It is a
|
|
312
|
+
delay cap rather than an attempt cap — giving up entirely would leave a tab
|
|
313
|
+
permanently stale after an outage it survived — and the ramp is reset by a
|
|
314
|
+
**delivered `flags` push**, not by a successful bootstrap, so a stream that
|
|
315
|
+
opens and dies before sending anything keeps backing off instead of flapping
|
|
316
|
+
at the floor delay. Against a Pharos that is present-but-down this is the
|
|
317
|
+
difference between one request per second per open tab and two per minute.
|
|
318
|
+
There is still no queue and no jitter; `flag()` keeps serving the last-known
|
|
319
|
+
values throughout, and the next successful reconnect resyncs from scratch.
|
|
302
320
|
|
|
303
321
|
Only payloads with `version >= current` are applied; stale (older-version)
|
|
304
322
|
pushes are ignored. An equal-version push (e.g. the replay a fresh stream
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
1
9
|
// src/privacy/mask.ts
|
|
2
10
|
var MASK_TOKEN = "••••";
|
|
3
11
|
function maskText(_text) {
|
|
@@ -100,11 +108,101 @@ var SENSITIVE_WORDS = [
|
|
|
100
108
|
"cvc",
|
|
101
109
|
"iban",
|
|
102
110
|
"passport",
|
|
103
|
-
"driverslicense"
|
|
111
|
+
"driverslicense",
|
|
112
|
+
"contrasena",
|
|
113
|
+
"contrasenya",
|
|
114
|
+
"usuario",
|
|
115
|
+
"secreto",
|
|
116
|
+
"correo",
|
|
117
|
+
"movil",
|
|
118
|
+
"nombre",
|
|
119
|
+
"apellido",
|
|
120
|
+
"direccion",
|
|
121
|
+
"codigopostal",
|
|
122
|
+
"nacimiento",
|
|
123
|
+
"cumpleanos",
|
|
124
|
+
"dni",
|
|
125
|
+
"seguridadsocial",
|
|
126
|
+
"tarjeta",
|
|
127
|
+
"motdepasse",
|
|
128
|
+
"utilisateur",
|
|
129
|
+
"identifiant",
|
|
130
|
+
"courriel",
|
|
131
|
+
"prenom",
|
|
132
|
+
"nomdefamille",
|
|
133
|
+
"adresse",
|
|
134
|
+
"codepostal",
|
|
135
|
+
"naissance",
|
|
136
|
+
"anniversaire",
|
|
137
|
+
"securitesociale",
|
|
138
|
+
"carteidentite",
|
|
139
|
+
"permisconduire",
|
|
140
|
+
"passwort",
|
|
141
|
+
"kennwort",
|
|
142
|
+
"benutzer",
|
|
143
|
+
"benutzername",
|
|
144
|
+
"vorname",
|
|
145
|
+
"nachname",
|
|
146
|
+
"familienname",
|
|
147
|
+
"anschrift",
|
|
148
|
+
"strasse",
|
|
149
|
+
"postleitzahl",
|
|
150
|
+
"geburt",
|
|
151
|
+
"kreditkarte",
|
|
152
|
+
"kontonummer",
|
|
153
|
+
"steuernummer",
|
|
154
|
+
"sozialversicherung",
|
|
155
|
+
"ausweis",
|
|
156
|
+
"senha",
|
|
157
|
+
"palavrapasse",
|
|
158
|
+
"correio",
|
|
159
|
+
"sobrenome",
|
|
160
|
+
"nomecompleto",
|
|
161
|
+
"endereco",
|
|
162
|
+
"nascimento",
|
|
163
|
+
"cartao",
|
|
164
|
+
"cpf",
|
|
165
|
+
"cnpj",
|
|
166
|
+
"パスワード",
|
|
167
|
+
"メール",
|
|
168
|
+
"電話",
|
|
169
|
+
"氏名",
|
|
170
|
+
"名前",
|
|
171
|
+
"住所",
|
|
172
|
+
"生年月日",
|
|
173
|
+
"誕生日",
|
|
174
|
+
"郵便番号",
|
|
175
|
+
"クレジットカード",
|
|
176
|
+
"マイナンバー",
|
|
177
|
+
"密码",
|
|
178
|
+
"密碼",
|
|
179
|
+
"口令",
|
|
180
|
+
"邮箱",
|
|
181
|
+
"郵箱",
|
|
182
|
+
"邮件",
|
|
183
|
+
"郵件",
|
|
184
|
+
"电话",
|
|
185
|
+
"手机",
|
|
186
|
+
"手機",
|
|
187
|
+
"姓名",
|
|
188
|
+
"地址",
|
|
189
|
+
"生日",
|
|
190
|
+
"出生日期",
|
|
191
|
+
"邮编",
|
|
192
|
+
"身份证",
|
|
193
|
+
"身份證",
|
|
194
|
+
"信用卡",
|
|
195
|
+
"银行卡"
|
|
104
196
|
];
|
|
105
197
|
var NAME_ATTRS = ["name", "id", "aria-label"];
|
|
106
|
-
function
|
|
107
|
-
return value.toLowerCase().replace(/[
|
|
198
|
+
function foldDiacritics(value) {
|
|
199
|
+
return value.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").normalize("NFC").replace(/ß/g, "ss");
|
|
200
|
+
}
|
|
201
|
+
function normalForms(value) {
|
|
202
|
+
const folded = foldDiacritics(value);
|
|
203
|
+
const ascii = folded.replace(/[^a-z0-9]/g, "");
|
|
204
|
+
const script = folded.replace(/[^\p{L}\p{N}]/gu, "");
|
|
205
|
+
return ascii === script ? [ascii] : [ascii, script];
|
|
108
206
|
}
|
|
109
207
|
function isSensitiveField(el) {
|
|
110
208
|
if (el.tagName.toLowerCase() === "input") {
|
|
@@ -123,9 +221,10 @@ function isSensitiveField(el) {
|
|
|
123
221
|
const raw = el.getAttribute(attr);
|
|
124
222
|
if (!raw)
|
|
125
223
|
continue;
|
|
126
|
-
const normalized
|
|
127
|
-
|
|
128
|
-
|
|
224
|
+
for (const normalized of normalForms(raw)) {
|
|
225
|
+
if (SENSITIVE_WORDS.some((word) => normalized.includes(word)))
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
129
228
|
}
|
|
130
229
|
return false;
|
|
131
230
|
}
|
|
@@ -852,4 +951,4 @@ async function clearPersisted(indexedDB = defaultFactory()) {
|
|
|
852
951
|
}
|
|
853
952
|
}
|
|
854
953
|
|
|
855
|
-
export { MASK_TOKEN, maskText, EXCLUDE_CLASS, MASK_CLASS, shouldExclude, shouldMaskByMarker, isSensitiveField, scrubUrl, scrubAttribute, decide, masksValue, MAX_BUFFER_BYTES, RECORDER_DEFAULTS, resolveConfig, ACTIVATION_EVENT_TAG, attachInteractions, EVENT_TYPE_FULL_SNAPSHOT, EVENT_TYPE_META, beginsWithSnapshot, ReplayBuffer, createDropLedger, MAX_WINDOW_BYTES, encodeWindow, randomSessionId, postEnvelope, envelopeMetaFor, encodeForUpload, createUploadSink, LOCAL_TTL_MS, quotaDroppedWindows, resetQuotaDroppedWindows, isQuotaExceeded, persistWindow, drainPersisted, clearPersisted };
|
|
954
|
+
export { __require, MASK_TOKEN, maskText, EXCLUDE_CLASS, MASK_CLASS, shouldExclude, shouldMaskByMarker, isSensitiveField, scrubUrl, scrubAttribute, decide, masksValue, MAX_BUFFER_BYTES, RECORDER_DEFAULTS, resolveConfig, ACTIVATION_EVENT_TAG, attachInteractions, EVENT_TYPE_FULL_SNAPSHOT, EVENT_TYPE_META, beginsWithSnapshot, ReplayBuffer, createDropLedger, MAX_WINDOW_BYTES, encodeWindow, randomSessionId, postEnvelope, envelopeMetaFor, encodeForUpload, createUploadSink, LOCAL_TTL_MS, quotaDroppedWindows, resetQuotaDroppedWindows, isQuotaExceeded, persistWindow, drainPersisted, clearPersisted };
|
|
@@ -26,8 +26,7 @@ import {
|
|
|
26
26
|
resolveConfig,
|
|
27
27
|
scrubAttribute,
|
|
28
28
|
scrubUrl
|
|
29
|
-
} from "./index-
|
|
30
|
-
import"./index-c3taa3cg.js";
|
|
29
|
+
} from "./index-temc0wzg.js";
|
|
31
30
|
|
|
32
31
|
// src/replay/recorder.ts
|
|
33
32
|
import { record } from "rrweb";
|
|
@@ -986,9 +985,5 @@ function startReplayUpload(opts) {
|
|
|
986
985
|
}
|
|
987
986
|
};
|
|
988
987
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
SESSION_STORAGE_KEY,
|
|
992
|
-
resolveSessionId,
|
|
993
|
-
startReplayUpload
|
|
994
|
-
};
|
|
988
|
+
|
|
989
|
+
export { startRecording, SESSION_STORAGE_KEY, resolveSessionId, SESSION_DROPS_KEY, startReplayUpload };
|
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,7 @@ export declare class PharosBrowserClient {
|
|
|
62
62
|
private listeners;
|
|
63
63
|
private closed;
|
|
64
64
|
private reconnectTimer;
|
|
65
|
+
private reconnectAttempts;
|
|
65
66
|
private errorsEnabled;
|
|
66
67
|
private errorTarget;
|
|
67
68
|
private replayHandle;
|
|
@@ -80,11 +81,22 @@ export declare class PharosBrowserClient {
|
|
|
80
81
|
* request): the browser sees this as an error and keeps retrying the same
|
|
81
82
|
* dead token forever. Heuristic: when the source has settled into CLOSED
|
|
82
83
|
* (its terminal state — EventSource does not reach CLOSED on a retryable
|
|
83
|
-
* drop), re-bootstrap
|
|
84
|
-
*
|
|
85
|
-
*
|
|
84
|
+
* drop), re-bootstrap after a backoff to mint a fresh token, then reconnect
|
|
85
|
+
* the stream. Still no queue and no jitter — the one thing it is not simple
|
|
86
|
+
* about is the ramp, for the reason on `scheduleReconnect`.
|
|
86
87
|
*/
|
|
87
88
|
private maybeReconnect;
|
|
89
|
+
/**
|
|
90
|
+
* Arms the single pending reconnect, backing off exponentially from
|
|
91
|
+
* `RECONNECT_BACKOFF_MS` to `MAX_RECONNECT_BACKOFF_MS`.
|
|
92
|
+
*
|
|
93
|
+
* The ramp is not decoration: against a Pharos that is present-but-down
|
|
94
|
+
* every re-bootstrap fails and re-arms this timer, so a fixed delay is a
|
|
95
|
+
* request per second per open tab for the length of the outage — load
|
|
96
|
+
* arriving exactly when the server can least take it. `reconnectAttempts`
|
|
97
|
+
* is reset by a delivered `flags` push, not here.
|
|
98
|
+
*/
|
|
99
|
+
private scheduleReconnect;
|
|
88
100
|
private onWindowError;
|
|
89
101
|
private onUnhandledRejection;
|
|
90
102
|
private registerErrorListeners;
|
|
@@ -206,14 +218,41 @@ export declare class PharosBrowserClient {
|
|
|
206
218
|
startReplay(options?: Omit<ReplayUploadOptions, "endpoint" | "appKey">): Promise<ReplayUploadHandle | null>;
|
|
207
219
|
/** Stops routing errors into the recorder. Does not stop the recorder itself. */
|
|
208
220
|
detachReplay(): void;
|
|
209
|
-
/**
|
|
221
|
+
/**
|
|
222
|
+
* Synchronous flag lookup; falls back to defaultValue for an unknown key,
|
|
223
|
+
* and for a value of a different type than the default (issue #788).
|
|
224
|
+
*
|
|
225
|
+
* `as T` was a cast, not a check, so a flag whose value arrived as the
|
|
226
|
+
* STRING `"false"` — truthy — read as ON through a boolean gate, while the
|
|
227
|
+
* dashboard, the payload and the code all looked right. The default carries
|
|
228
|
+
* the type the caller is prepared to handle, so a value that disagrees with
|
|
229
|
+
* it is a misconfiguration the default is the safe answer to.
|
|
230
|
+
*
|
|
231
|
+
* `null`/`undefined` defaults opt out: `typeof` describes the ABSENCE of a
|
|
232
|
+
* value for both, so checking against them would turn `flag(key, null)`
|
|
233
|
+
* into "always null" and stop returning the server's answer at all.
|
|
234
|
+
*/
|
|
210
235
|
flag<T>(key: string, defaultValue: T): T;
|
|
211
236
|
/** Subscribes to "change" (a new flags payload was applied) or "error". Returns an unsubscribe function. */
|
|
212
237
|
on(event: PharosEvent, cb: Listener): () => void;
|
|
213
238
|
private emit;
|
|
214
239
|
/**
|
|
215
240
|
* Re-identifies as a new context: closes the current stream, re-bootstraps
|
|
216
|
-
* (a full round trip — flags plus a fresh streamToken), and
|
|
241
|
+
* (a full round trip — flags plus a fresh streamToken), reconnects, and
|
|
242
|
+
* emits `change`.
|
|
243
|
+
*
|
|
244
|
+
* THE `change` IS NOT OPTIONAL (issue #788). Replacing the flags map is the
|
|
245
|
+
* whole point of this call, and the SSE handler is the only other thing that
|
|
246
|
+
* emits — so without this a component that rendered against the anonymous
|
|
247
|
+
* bootstrap kept that value for the life of its mount, while the server
|
|
248
|
+
* evaluated the real user's bucket on every request. That is not a corner
|
|
249
|
+
* case: it is every authenticated page load that mounts before login.
|
|
250
|
+
*
|
|
251
|
+
* REJECTS, AND STILL SCHEDULES A RECONNECT. An app awaiting this on login
|
|
252
|
+
* has to be able to see it fail, so the error propagates; but it was also
|
|
253
|
+
* the only way to lose the stream permanently — `eventSource` left `null`
|
|
254
|
+
* with nothing pending, since `maybeReconnect` runs only from an
|
|
255
|
+
* `es.onerror` and there is no `es` any more.
|
|
217
256
|
*/
|
|
218
257
|
identify(context: PharosContext): Promise<void>;
|
|
219
258
|
/** Closes the stream, stops any pending reconnect, and unregisters auto-capture. Terminal — construct a new client to resume. */
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
MAX_BUFFER_BYTES,
|
|
8
8
|
MAX_WINDOW_BYTES,
|
|
9
9
|
RECORDER_DEFAULTS,
|
|
10
|
+
__require,
|
|
10
11
|
beginsWithSnapshot,
|
|
11
12
|
clearPersisted,
|
|
12
13
|
createDropLedger,
|
|
@@ -27,10 +28,7 @@ import {
|
|
|
27
28
|
scrubUrl,
|
|
28
29
|
shouldExclude,
|
|
29
30
|
shouldMaskByMarker
|
|
30
|
-
} from "./index-
|
|
31
|
-
import {
|
|
32
|
-
__require
|
|
33
|
-
} from "./index-c3taa3cg.js";
|
|
31
|
+
} from "./index-temc0wzg.js";
|
|
34
32
|
|
|
35
33
|
// src/stack.ts
|
|
36
34
|
var MAX_FRAMES = 50;
|
|
@@ -276,6 +274,7 @@ function sanitizeAttrs(attrs) {
|
|
|
276
274
|
return out;
|
|
277
275
|
}
|
|
278
276
|
var RECONNECT_BACKOFF_MS = 1000;
|
|
277
|
+
var MAX_RECONNECT_BACKOFF_MS = 30000;
|
|
279
278
|
function buildBootstrapBody(context) {
|
|
280
279
|
const { contextKey, application, release, sessionId, attributes } = context;
|
|
281
280
|
const pharos = { contextKey };
|
|
@@ -303,6 +302,7 @@ class PharosBrowserClient {
|
|
|
303
302
|
};
|
|
304
303
|
closed = false;
|
|
305
304
|
reconnectTimer = null;
|
|
305
|
+
reconnectAttempts = 0;
|
|
306
306
|
errorsEnabled;
|
|
307
307
|
errorTarget;
|
|
308
308
|
replayHandle = null;
|
|
@@ -363,6 +363,7 @@ class PharosBrowserClient {
|
|
|
363
363
|
}
|
|
364
364
|
this.flags = payload.flags ?? {};
|
|
365
365
|
this.version = payload.version;
|
|
366
|
+
this.reconnectAttempts = 0;
|
|
366
367
|
this.emit("change", payload);
|
|
367
368
|
} catch (err) {
|
|
368
369
|
this.emit("error", err);
|
|
@@ -375,16 +376,21 @@ class PharosBrowserClient {
|
|
|
375
376
|
this.eventSource = es;
|
|
376
377
|
}
|
|
377
378
|
maybeReconnect(es) {
|
|
378
|
-
if (this.closed || this.reconnectTimer)
|
|
379
|
-
return;
|
|
380
379
|
if (es.readyState !== 2)
|
|
381
380
|
return;
|
|
381
|
+
this.scheduleReconnect();
|
|
382
|
+
}
|
|
383
|
+
scheduleReconnect() {
|
|
384
|
+
if (this.closed || this.reconnectTimer)
|
|
385
|
+
return;
|
|
386
|
+
const delay = Math.min(RECONNECT_BACKOFF_MS * 2 ** this.reconnectAttempts, MAX_RECONNECT_BACKOFF_MS);
|
|
387
|
+
this.reconnectAttempts += 1;
|
|
382
388
|
this.reconnectTimer = setTimeout(() => {
|
|
383
389
|
this.reconnectTimer = null;
|
|
384
390
|
if (this.closed)
|
|
385
391
|
return;
|
|
386
392
|
this.identify(this.context).catch((err) => this.emit("error", err));
|
|
387
|
-
},
|
|
393
|
+
}, delay);
|
|
388
394
|
}
|
|
389
395
|
onWindowError = (event) => {
|
|
390
396
|
const e = event;
|
|
@@ -473,7 +479,7 @@ class PharosBrowserClient {
|
|
|
473
479
|
async startReplay(options = {}) {
|
|
474
480
|
if (this.closed)
|
|
475
481
|
return null;
|
|
476
|
-
const { startReplayUpload } = await import("./wire-
|
|
482
|
+
const { startReplayUpload } = await import("./wire-90whxzan.js");
|
|
477
483
|
if (this.closed)
|
|
478
484
|
return null;
|
|
479
485
|
const handle = startReplayUpload({
|
|
@@ -496,10 +502,13 @@ class PharosBrowserClient {
|
|
|
496
502
|
this.replayErrorListeners = null;
|
|
497
503
|
}
|
|
498
504
|
flag(key, defaultValue) {
|
|
499
|
-
if (Object.prototype.hasOwnProperty.call(this.flags, key))
|
|
500
|
-
return
|
|
505
|
+
if (!Object.prototype.hasOwnProperty.call(this.flags, key))
|
|
506
|
+
return defaultValue;
|
|
507
|
+
const value = this.flags[key];
|
|
508
|
+
if (defaultValue !== null && defaultValue !== undefined && typeof value !== typeof defaultValue) {
|
|
509
|
+
return defaultValue;
|
|
501
510
|
}
|
|
502
|
-
return
|
|
511
|
+
return value;
|
|
503
512
|
}
|
|
504
513
|
on(event, cb) {
|
|
505
514
|
this.listeners[event].add(cb);
|
|
@@ -520,9 +529,15 @@ class PharosBrowserClient {
|
|
|
520
529
|
this.eventSource.close();
|
|
521
530
|
this.eventSource = null;
|
|
522
531
|
}
|
|
523
|
-
|
|
532
|
+
try {
|
|
533
|
+
await this.bootstrap();
|
|
534
|
+
} catch (err) {
|
|
535
|
+
this.scheduleReconnect();
|
|
536
|
+
throw err;
|
|
537
|
+
}
|
|
524
538
|
if (!this.closed) {
|
|
525
539
|
this.connectStream();
|
|
540
|
+
this.emit("change", { version: this.version, flags: this.flags });
|
|
526
541
|
}
|
|
527
542
|
}
|
|
528
543
|
close() {
|
|
@@ -544,34 +559,34 @@ class PharosBrowserClient {
|
|
|
544
559
|
}
|
|
545
560
|
}
|
|
546
561
|
export {
|
|
547
|
-
|
|
548
|
-
EXCLUDE_CLASS,
|
|
549
|
-
LOCAL_TTL_MS,
|
|
550
|
-
MASK_CLASS,
|
|
551
|
-
MASK_TOKEN,
|
|
552
|
-
MAX_BUFFER_BYTES,
|
|
553
|
-
MAX_WINDOW_BYTES,
|
|
554
|
-
PharosBrowserClient,
|
|
555
|
-
RECORDER_DEFAULTS,
|
|
556
|
-
beginsWithSnapshot,
|
|
557
|
-
clearPersisted,
|
|
558
|
-
collectRecordedStrings,
|
|
559
|
-
createDropLedger,
|
|
560
|
-
createUploadSink,
|
|
561
|
-
decide,
|
|
562
|
-
drainPersisted,
|
|
563
|
-
encodeWindow,
|
|
564
|
-
envelopeMetaFor,
|
|
565
|
-
isSensitiveField,
|
|
566
|
-
maskText,
|
|
567
|
-
masksValue,
|
|
568
|
-
persistWindow,
|
|
569
|
-
postEnvelope,
|
|
570
|
-
quotaDroppedWindows,
|
|
571
|
-
resetQuotaDroppedWindows,
|
|
572
|
-
resolveConfig,
|
|
573
|
-
scrubAttribute,
|
|
574
|
-
scrubUrl,
|
|
562
|
+
shouldMaskByMarker,
|
|
575
563
|
shouldExclude,
|
|
576
|
-
|
|
564
|
+
scrubUrl,
|
|
565
|
+
scrubAttribute,
|
|
566
|
+
resolveConfig,
|
|
567
|
+
resetQuotaDroppedWindows,
|
|
568
|
+
quotaDroppedWindows,
|
|
569
|
+
postEnvelope,
|
|
570
|
+
persistWindow,
|
|
571
|
+
masksValue,
|
|
572
|
+
maskText,
|
|
573
|
+
isSensitiveField,
|
|
574
|
+
envelopeMetaFor,
|
|
575
|
+
encodeWindow,
|
|
576
|
+
drainPersisted,
|
|
577
|
+
decide,
|
|
578
|
+
createUploadSink,
|
|
579
|
+
createDropLedger,
|
|
580
|
+
collectRecordedStrings,
|
|
581
|
+
clearPersisted,
|
|
582
|
+
beginsWithSnapshot,
|
|
583
|
+
RECORDER_DEFAULTS,
|
|
584
|
+
PharosBrowserClient,
|
|
585
|
+
MAX_WINDOW_BYTES,
|
|
586
|
+
MAX_BUFFER_BYTES,
|
|
587
|
+
MASK_TOKEN,
|
|
588
|
+
MASK_CLASS,
|
|
589
|
+
LOCAL_TTL_MS,
|
|
590
|
+
EXCLUDE_CLASS,
|
|
591
|
+
ACTIVATION_EVENT_TAG
|
|
577
592
|
};
|
package/dist/replay/index.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
export {
|
|
1
|
+
import {
|
|
3
2
|
SESSION_DROPS_KEY,
|
|
4
3
|
SESSION_STORAGE_KEY,
|
|
5
4
|
resolveSessionId,
|
|
6
5
|
startRecording,
|
|
7
6
|
startReplayUpload
|
|
7
|
+
} from "../index-xs32mrd9.js";
|
|
8
|
+
import"../index-temc0wzg.js";
|
|
9
|
+
export {
|
|
10
|
+
startReplayUpload,
|
|
11
|
+
startRecording,
|
|
12
|
+
resolveSessionId,
|
|
13
|
+
SESSION_STORAGE_KEY,
|
|
14
|
+
SESSION_DROPS_KEY
|
|
8
15
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SESSION_DROPS_KEY,
|
|
3
|
+
SESSION_STORAGE_KEY,
|
|
4
|
+
resolveSessionId,
|
|
5
|
+
startReplayUpload
|
|
6
|
+
} from "./index-xs32mrd9.js";
|
|
7
|
+
import"./index-temc0wzg.js";
|
|
8
|
+
export {
|
|
9
|
+
startReplayUpload,
|
|
10
|
+
resolveSessionId,
|
|
11
|
+
SESSION_STORAGE_KEY,
|
|
12
|
+
SESSION_DROPS_KEY
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loxel.dev/pharos-browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Browser client for Pharos — server-evaluated feature flags, error capture, and session replay.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
|
-
"
|
|
48
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
49
|
+
"test": "bun run typecheck && bun test",
|
|
49
50
|
"build": "bun build src/index.ts src/replay/index.ts --outdir dist --root src --splitting --target browser --format esm --external rrweb && bun run build:types",
|
|
50
51
|
"build:types": "tsc -p tsconfig.build.json",
|
|
51
52
|
"prepublishOnly": "bun run build"
|
package/dist/index-c3taa3cg.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined")
|
|
5
|
-
return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
export { __require };
|