@sagepilot-ai/web-events-addon 0.2.4 → 0.2.6
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 +5 -3
- package/dist/index.cjs +74 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +74 -39
- package/dist/index.js.map +1 -1
- package/dist/sagepilot-web-events.global.js +74 -39
- package/dist/sagepilot-web-events.global.js.map +1 -1
- package/package.json +6 -21
|
@@ -142,27 +142,40 @@ var SagepilotWebEvents = (() => {
|
|
|
142
142
|
this.startup = startup;
|
|
143
143
|
return startup;
|
|
144
144
|
}
|
|
145
|
-
/** Renews
|
|
145
|
+
/** Renews capture, using separate browser proof to recover a linked web visitor after expiry. */
|
|
146
146
|
async refresh(recoverAnonymous = false) {
|
|
147
147
|
if (this.pending) return this.pending;
|
|
148
148
|
if (!this.anonymousId) throw new Error("Start the installation client before renewing it.");
|
|
149
149
|
const generation = this.generation, prior = this.current;
|
|
150
150
|
const pending = (async () => {
|
|
151
151
|
let renewing = prior;
|
|
152
|
+
let resumed = false;
|
|
152
153
|
let value;
|
|
153
154
|
try {
|
|
154
155
|
value = await this.requestSession(prior?.session_token);
|
|
155
156
|
} catch (error) {
|
|
156
157
|
if (!recoverAnonymous || !prior || this.options.platform !== "web" || generation !== this.generation || !(error instanceof SagepilotInstallationError) || error.status !== 401) throw error;
|
|
157
158
|
renewing = null;
|
|
158
|
-
|
|
159
|
+
if (prior.browser_resume_token) {
|
|
160
|
+
try {
|
|
161
|
+
value = await this.requestSession(prior.browser_resume_token, true);
|
|
162
|
+
resumed = true;
|
|
163
|
+
} catch (resumeError) {
|
|
164
|
+
if (generation !== this.generation || !(resumeError instanceof SagepilotInstallationError) || ![401, 403, 422].includes(resumeError.status ?? 0)) throw resumeError;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (!resumed) value = await this.requestSession();
|
|
159
168
|
}
|
|
160
169
|
const next = this.validateSession(value);
|
|
161
170
|
if (generation !== this.generation) throw new Error("SDK session startup was cancelled.");
|
|
162
|
-
if (renewing && (next.session_id !== renewing.session_id || next.customer_id !== renewing.customer_id) || !renewing && (next.customer_id !== null || next.session_id === prior?.session_id)) {
|
|
171
|
+
if (renewing && (next.session_id !== renewing.session_id || next.customer_id !== renewing.customer_id) || !renewing && (next.customer_id !== null || !resumed && next.session_id === prior?.session_id)) {
|
|
163
172
|
throw new Error("SDK session renewal changed capture ownership.");
|
|
164
173
|
}
|
|
165
|
-
if (
|
|
174
|
+
if (resumed && next.browser_resume_token !== prior?.browser_resume_token) {
|
|
175
|
+
throw new Error("SDK browser recovery returned invalid proof.");
|
|
176
|
+
}
|
|
177
|
+
if (renewing && !next.browser_resume_token) next.browser_resume_token = renewing.browser_resume_token;
|
|
178
|
+
if (prior && next.session_id !== prior.session_id) this.owners.delete(prior.session_id);
|
|
166
179
|
this.current = next;
|
|
167
180
|
this.remember(next);
|
|
168
181
|
try {
|
|
@@ -176,8 +189,8 @@ var SagepilotWebEvents = (() => {
|
|
|
176
189
|
this.pending = pending;
|
|
177
190
|
return pending;
|
|
178
191
|
}
|
|
179
|
-
/**
|
|
180
|
-
requestSession(token) {
|
|
192
|
+
/** Sends only the capture token for renewal or the distinct credential for browser recovery. */
|
|
193
|
+
requestSession(token, resumeBrowser = false) {
|
|
181
194
|
return this.request(`${this.apiHost}/platform-api/v1/sdk/sessions`, {
|
|
182
195
|
method: "POST",
|
|
183
196
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
@@ -185,7 +198,8 @@ var SagepilotWebEvents = (() => {
|
|
|
185
198
|
key: `${this.workspaceId}:${this.installationId}`,
|
|
186
199
|
anonymous_id: this.anonymousId,
|
|
187
200
|
platform: this.options.platform,
|
|
188
|
-
...this.options.appId ? { app_id: this.options.appId } : {}
|
|
201
|
+
...this.options.appId ? { app_id: this.options.appId } : {},
|
|
202
|
+
...resumeBrowser ? { resume_browser: true } : {}
|
|
189
203
|
})
|
|
190
204
|
});
|
|
191
205
|
}
|
|
@@ -223,12 +237,25 @@ var SagepilotWebEvents = (() => {
|
|
|
223
237
|
if (new TextEncoder().encode(body).byteLength > 128 * 1024) throw new Error("SDK batch exceeds the upload limit.");
|
|
224
238
|
return validateSagepilotInstallationReceipt(await this.request(auth.url, { method: "POST", headers: auth.headers, body }), observations.map((item) => item.event_id));
|
|
225
239
|
}
|
|
226
|
-
/** Submits
|
|
240
|
+
/** Submits contact and adopts only a scoped customer session returned by the server. */
|
|
227
241
|
async submitContact(input) {
|
|
228
242
|
if (!UUID.test(input.submission_id) || !Number.isSafeInteger(input.popup_revision) || input.popup_revision < 1 || typeof input.marketing_consent !== "boolean") throw new Error("Invalid popup submission.");
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
243
|
+
if (this.transitioning) throw new Error("An SDK identity operation is already running.");
|
|
244
|
+
this.transitioning = true;
|
|
245
|
+
const generation = this.generation, prior = this.current;
|
|
246
|
+
try {
|
|
247
|
+
const body = prior?.popup_submission_events === true ? { ...input, server_submission_event: true } : input;
|
|
248
|
+
const result = await this.authenticated("/contact", body);
|
|
249
|
+
if (!result || !["recorded", "verification_required"].includes(result.status) || result.status === "verification_required" && (!result.challenge_id || !UUID.test(result.challenge_id) || !Number.isFinite(Date.parse(result.expires_at ?? "")))) throw new Error("Invalid popup submission response.");
|
|
250
|
+
const { session, ...acknowledgement } = result;
|
|
251
|
+
if (session != null) {
|
|
252
|
+
if (result.status !== "recorded" || prior?.customer_id !== null) throw new Error("Invalid popup customer session.");
|
|
253
|
+
await this.adoptTransition(session, prior, generation, true);
|
|
254
|
+
}
|
|
255
|
+
return acknowledgement;
|
|
256
|
+
} finally {
|
|
257
|
+
this.transitioning = false;
|
|
258
|
+
}
|
|
232
259
|
}
|
|
233
260
|
/** Adopts customer authority only from the server's completed verification operation. */
|
|
234
261
|
async verifyContact(challengeId, code) {
|
|
@@ -241,7 +268,7 @@ var SagepilotWebEvents = (() => {
|
|
|
241
268
|
}
|
|
242
269
|
/** Keeps a bounded credential cache separate from observations so old anonymous batches retain their owner. */
|
|
243
270
|
remember(session) {
|
|
244
|
-
this.owners.set(session.session_id, { ...session });
|
|
271
|
+
this.owners.set(session.session_id, { ...session, browser_resume_token: void 0 });
|
|
245
272
|
while (this.owners.size > 4) this.owners.delete(this.owners.keys().next().value);
|
|
246
273
|
try {
|
|
247
274
|
void Promise.resolve(this.options.storage?.setItem(this.storageKey + ":owners", JSON.stringify([...this.owners.values()]))).catch(() => {
|
|
@@ -263,24 +290,28 @@ var SagepilotWebEvents = (() => {
|
|
|
263
290
|
this.transitioning = true;
|
|
264
291
|
const generation = this.generation, prior = this.current;
|
|
265
292
|
try {
|
|
266
|
-
|
|
267
|
-
if (generation !== this.generation || !prior || next.session_id === prior.session_id || (identified ? next.customer_id === null : next.customer_id !== null)) throw new Error("Invalid SDK identity transition.");
|
|
268
|
-
this.generation++;
|
|
269
|
-
this.cancelPending();
|
|
270
|
-
this.pending = null;
|
|
271
|
-
if (path === "/logout") this.owners.delete(prior.session_id);
|
|
272
|
-
else this.remember(prior);
|
|
273
|
-
this.current = next;
|
|
274
|
-
this.remember(next);
|
|
275
|
-
try {
|
|
276
|
-
await this.options.storage?.setItem(this.storageKey, JSON.stringify(next));
|
|
277
|
-
} catch {
|
|
278
|
-
}
|
|
279
|
-
return { ...next };
|
|
293
|
+
return await this.adoptTransition(await this.authenticated(path, body), prior, generation, identified);
|
|
280
294
|
} finally {
|
|
281
295
|
this.transitioning = false;
|
|
282
296
|
}
|
|
283
297
|
}
|
|
298
|
+
/** Installs one new server owner and retains earlier credentials unless the owner logged out. */
|
|
299
|
+
async adoptTransition(value, prior, generation, identified) {
|
|
300
|
+
const next = this.validateSession(value);
|
|
301
|
+
if (generation !== this.generation || !prior || next.session_id === prior.session_id || (identified ? next.customer_id === null : next.customer_id !== null)) throw new Error("Invalid SDK identity transition.");
|
|
302
|
+
this.generation++;
|
|
303
|
+
this.cancelPending();
|
|
304
|
+
this.pending = null;
|
|
305
|
+
if (identified) this.remember(prior);
|
|
306
|
+
else this.owners.delete(prior.session_id);
|
|
307
|
+
this.current = next;
|
|
308
|
+
this.remember(next);
|
|
309
|
+
try {
|
|
310
|
+
await this.options.storage?.setItem(this.storageKey, JSON.stringify(next));
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
return { ...next };
|
|
314
|
+
}
|
|
284
315
|
/** Cancels requests on permission/ownership boundaries while preserving the current session for safe renewal. */
|
|
285
316
|
cancelPending() {
|
|
286
317
|
this.uploadRevision++;
|
|
@@ -296,10 +327,10 @@ var SagepilotWebEvents = (() => {
|
|
|
296
327
|
this.startup = null;
|
|
297
328
|
this.owners.clear();
|
|
298
329
|
}
|
|
299
|
-
/** Rejects foreign
|
|
330
|
+
/** Rejects foreign or malformed session responses before credentials are used. */
|
|
300
331
|
validateSession(value) {
|
|
301
332
|
const session = value;
|
|
302
|
-
if (!session || session.workspace_id !== this.workspaceId || session.installation_id !== this.installationId || session.platform !== this.options.platform || session.anonymous_id !== this.anonymousId || session.customer_id !== null && (typeof session.customer_id !== "string" || !UUID.test(session.customer_id)) || !UUID.test(session.session_id) || typeof session.session_token !== "string" || session.session_token.length > 16384 || !session.session_token || !Number.isFinite(Date.parse(session.expires_at)) || typeof session.collection_enabled !== "boolean" || session.visitor_cookie_enabled !== void 0 && typeof session.visitor_cookie_enabled !== "boolean" || !Number.isSafeInteger(session.revision)) {
|
|
333
|
+
if (!session || session.workspace_id !== this.workspaceId || session.installation_id !== this.installationId || session.platform !== this.options.platform || session.anonymous_id !== this.anonymousId || session.customer_id !== null && (typeof session.customer_id !== "string" || !UUID.test(session.customer_id)) || !UUID.test(session.session_id) || typeof session.session_token !== "string" || session.session_token.length > 16384 || !session.session_token || session.browser_resume_token != null && (session.platform !== "web" || typeof session.browser_resume_token !== "string" || !session.browser_resume_token || session.browser_resume_token.length > 8192) || !Number.isFinite(Date.parse(session.expires_at)) || typeof session.collection_enabled !== "boolean" || session.visitor_cookie_enabled !== void 0 && typeof session.visitor_cookie_enabled !== "boolean" || !Number.isSafeInteger(session.revision)) {
|
|
303
334
|
throw new Error("SDK session response has invalid scope or ownership.");
|
|
304
335
|
}
|
|
305
336
|
const url = safeURL(session.ingestion_url);
|
|
@@ -364,7 +395,7 @@ var SagepilotWebEvents = (() => {
|
|
|
364
395
|
|
|
365
396
|
// package.json
|
|
366
397
|
var name = "@sagepilot-ai/web-events-addon";
|
|
367
|
-
var version = "0.2.
|
|
398
|
+
var version = "0.2.6";
|
|
368
399
|
|
|
369
400
|
// src/projection.ts
|
|
370
401
|
var sensitive = /password|passwd|secret|token|authorization|cookie|session|email|phone|contact|address|card|payment|cvv|ssn|dob|birth|customer|user|account|person|profile|name|description|message|content|text|search|query/i;
|
|
@@ -2366,7 +2397,6 @@ var SagepilotWebEvents = (() => {
|
|
|
2366
2397
|
}
|
|
2367
2398
|
const result = await options.submit(contact);
|
|
2368
2399
|
if (revision !== generation) return;
|
|
2369
|
-
options.emit("popup_submitted");
|
|
2370
2400
|
for (const input of inputs.values()) input.value = "";
|
|
2371
2401
|
if (result.status === "verification_required") {
|
|
2372
2402
|
let restartForm2 = function() {
|
|
@@ -2664,13 +2694,14 @@ var SagepilotWebEvents = (() => {
|
|
|
2664
2694
|
captureBytes -= bytes;
|
|
2665
2695
|
});
|
|
2666
2696
|
};
|
|
2697
|
+
function emitPopup(action) {
|
|
2698
|
+
if (enabled()) capture("lifecycle", { action }, epoch, Date.now());
|
|
2699
|
+
}
|
|
2667
2700
|
const popup = createPopup({
|
|
2668
2701
|
client,
|
|
2669
2702
|
storage,
|
|
2670
2703
|
report,
|
|
2671
|
-
emit
|
|
2672
|
-
if (enabled()) capture("lifecycle", { action }, epoch, Date.now());
|
|
2673
|
-
},
|
|
2704
|
+
emit: emitPopup,
|
|
2674
2705
|
submit(input) {
|
|
2675
2706
|
return addon.submitContact(input);
|
|
2676
2707
|
},
|
|
@@ -2687,7 +2718,7 @@ var SagepilotWebEvents = (() => {
|
|
|
2687
2718
|
detach(true);
|
|
2688
2719
|
client.cancelPending();
|
|
2689
2720
|
try {
|
|
2690
|
-
await operation();
|
|
2721
|
+
return await operation();
|
|
2691
2722
|
} finally {
|
|
2692
2723
|
identityChanging = false;
|
|
2693
2724
|
if (!destroyed) {
|
|
@@ -2916,17 +2947,21 @@ var SagepilotWebEvents = (() => {
|
|
|
2916
2947
|
}
|
|
2917
2948
|
},
|
|
2918
2949
|
flush,
|
|
2919
|
-
/**
|
|
2950
|
+
/** Keeps contact on the identity endpoint and fences capture while adopting the customer. */
|
|
2920
2951
|
async submitContact(input) {
|
|
2921
|
-
const
|
|
2922
|
-
const result = await client.submitContact(input);
|
|
2923
|
-
if (result.status === "recorded")
|
|
2952
|
+
const session = client.session();
|
|
2953
|
+
const result = session?.popup?.verification === "none" && session.customer_id === null ? await changeIdentity(() => client.submitContact(input)) : await client.submitContact(input);
|
|
2954
|
+
if (result.status === "recorded") {
|
|
2955
|
+
if (result.popup_submission_recorded !== true) emitPopup("popup_submitted");
|
|
2956
|
+
await completeVisitor(session?.anonymous_id);
|
|
2957
|
+
}
|
|
2924
2958
|
return result;
|
|
2925
2959
|
},
|
|
2926
2960
|
/** Verifies proof behind a capture fence; buffered anonymous observations remain anonymous. */
|
|
2927
2961
|
async verifyContact(challengeId, code) {
|
|
2928
2962
|
const anonymousId = client.session()?.anonymous_id;
|
|
2929
|
-
await changeIdentity(() => client.verifyContact(challengeId, code));
|
|
2963
|
+
const session = await changeIdentity(() => client.verifyContact(challengeId, code));
|
|
2964
|
+
if (session.popup_submission_recorded !== true) emitPopup("popup_submitted");
|
|
2930
2965
|
await completeVisitor(anonymousId);
|
|
2931
2966
|
},
|
|
2932
2967
|
/** Applies an explicit logout boundary without relabeling earlier observations. */
|