@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
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -116,27 +116,40 @@ var SagepilotInstallationClient = class {
|
|
|
116
116
|
this.startup = startup;
|
|
117
117
|
return startup;
|
|
118
118
|
}
|
|
119
|
-
/** Renews
|
|
119
|
+
/** Renews capture, using separate browser proof to recover a linked web visitor after expiry. */
|
|
120
120
|
async refresh(recoverAnonymous = false) {
|
|
121
121
|
if (this.pending) return this.pending;
|
|
122
122
|
if (!this.anonymousId) throw new Error("Start the installation client before renewing it.");
|
|
123
123
|
const generation = this.generation, prior = this.current;
|
|
124
124
|
const pending = (async () => {
|
|
125
125
|
let renewing = prior;
|
|
126
|
+
let resumed = false;
|
|
126
127
|
let value;
|
|
127
128
|
try {
|
|
128
129
|
value = await this.requestSession(prior?.session_token);
|
|
129
130
|
} catch (error) {
|
|
130
131
|
if (!recoverAnonymous || !prior || this.options.platform !== "web" || generation !== this.generation || !(error instanceof SagepilotInstallationError) || error.status !== 401) throw error;
|
|
131
132
|
renewing = null;
|
|
132
|
-
|
|
133
|
+
if (prior.browser_resume_token) {
|
|
134
|
+
try {
|
|
135
|
+
value = await this.requestSession(prior.browser_resume_token, true);
|
|
136
|
+
resumed = true;
|
|
137
|
+
} catch (resumeError) {
|
|
138
|
+
if (generation !== this.generation || !(resumeError instanceof SagepilotInstallationError) || ![401, 403, 422].includes(resumeError.status ?? 0)) throw resumeError;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!resumed) value = await this.requestSession();
|
|
133
142
|
}
|
|
134
143
|
const next = this.validateSession(value);
|
|
135
144
|
if (generation !== this.generation) throw new Error("SDK session startup was cancelled.");
|
|
136
|
-
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)) {
|
|
145
|
+
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)) {
|
|
137
146
|
throw new Error("SDK session renewal changed capture ownership.");
|
|
138
147
|
}
|
|
139
|
-
if (
|
|
148
|
+
if (resumed && next.browser_resume_token !== prior?.browser_resume_token) {
|
|
149
|
+
throw new Error("SDK browser recovery returned invalid proof.");
|
|
150
|
+
}
|
|
151
|
+
if (renewing && !next.browser_resume_token) next.browser_resume_token = renewing.browser_resume_token;
|
|
152
|
+
if (prior && next.session_id !== prior.session_id) this.owners.delete(prior.session_id);
|
|
140
153
|
this.current = next;
|
|
141
154
|
this.remember(next);
|
|
142
155
|
try {
|
|
@@ -150,8 +163,8 @@ var SagepilotInstallationClient = class {
|
|
|
150
163
|
this.pending = pending;
|
|
151
164
|
return pending;
|
|
152
165
|
}
|
|
153
|
-
/**
|
|
154
|
-
requestSession(token) {
|
|
166
|
+
/** Sends only the capture token for renewal or the distinct credential for browser recovery. */
|
|
167
|
+
requestSession(token, resumeBrowser = false) {
|
|
155
168
|
return this.request(`${this.apiHost}/platform-api/v1/sdk/sessions`, {
|
|
156
169
|
method: "POST",
|
|
157
170
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
@@ -159,7 +172,8 @@ var SagepilotInstallationClient = class {
|
|
|
159
172
|
key: `${this.workspaceId}:${this.installationId}`,
|
|
160
173
|
anonymous_id: this.anonymousId,
|
|
161
174
|
platform: this.options.platform,
|
|
162
|
-
...this.options.appId ? { app_id: this.options.appId } : {}
|
|
175
|
+
...this.options.appId ? { app_id: this.options.appId } : {},
|
|
176
|
+
...resumeBrowser ? { resume_browser: true } : {}
|
|
163
177
|
})
|
|
164
178
|
});
|
|
165
179
|
}
|
|
@@ -197,12 +211,25 @@ var SagepilotInstallationClient = class {
|
|
|
197
211
|
if (new TextEncoder().encode(body).byteLength > 128 * 1024) throw new Error("SDK batch exceeds the upload limit.");
|
|
198
212
|
return validateSagepilotInstallationReceipt(await this.request(auth.url, { method: "POST", headers: auth.headers, body }), observations.map((item) => item.event_id));
|
|
199
213
|
}
|
|
200
|
-
/** Submits
|
|
214
|
+
/** Submits contact and adopts only a scoped customer session returned by the server. */
|
|
201
215
|
async submitContact(input) {
|
|
202
216
|
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.");
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
217
|
+
if (this.transitioning) throw new Error("An SDK identity operation is already running.");
|
|
218
|
+
this.transitioning = true;
|
|
219
|
+
const generation = this.generation, prior = this.current;
|
|
220
|
+
try {
|
|
221
|
+
const body = prior?.popup_submission_events === true ? { ...input, server_submission_event: true } : input;
|
|
222
|
+
const result = await this.authenticated("/contact", body);
|
|
223
|
+
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.");
|
|
224
|
+
const { session, ...acknowledgement } = result;
|
|
225
|
+
if (session != null) {
|
|
226
|
+
if (result.status !== "recorded" || prior?.customer_id !== null) throw new Error("Invalid popup customer session.");
|
|
227
|
+
await this.adoptTransition(session, prior, generation, true);
|
|
228
|
+
}
|
|
229
|
+
return acknowledgement;
|
|
230
|
+
} finally {
|
|
231
|
+
this.transitioning = false;
|
|
232
|
+
}
|
|
206
233
|
}
|
|
207
234
|
/** Adopts customer authority only from the server's completed verification operation. */
|
|
208
235
|
async verifyContact(challengeId, code) {
|
|
@@ -215,7 +242,7 @@ var SagepilotInstallationClient = class {
|
|
|
215
242
|
}
|
|
216
243
|
/** Keeps a bounded credential cache separate from observations so old anonymous batches retain their owner. */
|
|
217
244
|
remember(session) {
|
|
218
|
-
this.owners.set(session.session_id, { ...session });
|
|
245
|
+
this.owners.set(session.session_id, { ...session, browser_resume_token: void 0 });
|
|
219
246
|
while (this.owners.size > 4) this.owners.delete(this.owners.keys().next().value);
|
|
220
247
|
try {
|
|
221
248
|
void Promise.resolve(this.options.storage?.setItem(this.storageKey + ":owners", JSON.stringify([...this.owners.values()]))).catch(() => {
|
|
@@ -237,24 +264,28 @@ var SagepilotInstallationClient = class {
|
|
|
237
264
|
this.transitioning = true;
|
|
238
265
|
const generation = this.generation, prior = this.current;
|
|
239
266
|
try {
|
|
240
|
-
|
|
241
|
-
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.");
|
|
242
|
-
this.generation++;
|
|
243
|
-
this.cancelPending();
|
|
244
|
-
this.pending = null;
|
|
245
|
-
if (path === "/logout") this.owners.delete(prior.session_id);
|
|
246
|
-
else this.remember(prior);
|
|
247
|
-
this.current = next;
|
|
248
|
-
this.remember(next);
|
|
249
|
-
try {
|
|
250
|
-
await this.options.storage?.setItem(this.storageKey, JSON.stringify(next));
|
|
251
|
-
} catch {
|
|
252
|
-
}
|
|
253
|
-
return { ...next };
|
|
267
|
+
return await this.adoptTransition(await this.authenticated(path, body), prior, generation, identified);
|
|
254
268
|
} finally {
|
|
255
269
|
this.transitioning = false;
|
|
256
270
|
}
|
|
257
271
|
}
|
|
272
|
+
/** Installs one new server owner and retains earlier credentials unless the owner logged out. */
|
|
273
|
+
async adoptTransition(value, prior, generation, identified) {
|
|
274
|
+
const next = this.validateSession(value);
|
|
275
|
+
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.");
|
|
276
|
+
this.generation++;
|
|
277
|
+
this.cancelPending();
|
|
278
|
+
this.pending = null;
|
|
279
|
+
if (identified) this.remember(prior);
|
|
280
|
+
else this.owners.delete(prior.session_id);
|
|
281
|
+
this.current = next;
|
|
282
|
+
this.remember(next);
|
|
283
|
+
try {
|
|
284
|
+
await this.options.storage?.setItem(this.storageKey, JSON.stringify(next));
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
return { ...next };
|
|
288
|
+
}
|
|
258
289
|
/** Cancels requests on permission/ownership boundaries while preserving the current session for safe renewal. */
|
|
259
290
|
cancelPending() {
|
|
260
291
|
this.uploadRevision++;
|
|
@@ -270,10 +301,10 @@ var SagepilotInstallationClient = class {
|
|
|
270
301
|
this.startup = null;
|
|
271
302
|
this.owners.clear();
|
|
272
303
|
}
|
|
273
|
-
/** Rejects foreign
|
|
304
|
+
/** Rejects foreign or malformed session responses before credentials are used. */
|
|
274
305
|
validateSession(value) {
|
|
275
306
|
const session = value;
|
|
276
|
-
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)) {
|
|
307
|
+
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)) {
|
|
277
308
|
throw new Error("SDK session response has invalid scope or ownership.");
|
|
278
309
|
}
|
|
279
310
|
const url = safeURL(session.ingestion_url);
|
|
@@ -338,7 +369,7 @@ function createSagepilotInstallationClient(options) {
|
|
|
338
369
|
|
|
339
370
|
// package.json
|
|
340
371
|
var name = "@sagepilot-ai/web-events-addon";
|
|
341
|
-
var version = "0.2.
|
|
372
|
+
var version = "0.2.6";
|
|
342
373
|
|
|
343
374
|
// src/projection.ts
|
|
344
375
|
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;
|
|
@@ -2340,7 +2371,6 @@ function createPopup(options) {
|
|
|
2340
2371
|
}
|
|
2341
2372
|
const result = await options.submit(contact);
|
|
2342
2373
|
if (revision !== generation) return;
|
|
2343
|
-
options.emit("popup_submitted");
|
|
2344
2374
|
for (const input of inputs.values()) input.value = "";
|
|
2345
2375
|
if (result.status === "verification_required") {
|
|
2346
2376
|
let restartForm2 = function() {
|
|
@@ -2638,13 +2668,14 @@ function createSagepilotWebEvents(options) {
|
|
|
2638
2668
|
captureBytes -= bytes;
|
|
2639
2669
|
});
|
|
2640
2670
|
};
|
|
2671
|
+
function emitPopup(action) {
|
|
2672
|
+
if (enabled()) capture("lifecycle", { action }, epoch, Date.now());
|
|
2673
|
+
}
|
|
2641
2674
|
const popup = createPopup({
|
|
2642
2675
|
client,
|
|
2643
2676
|
storage,
|
|
2644
2677
|
report,
|
|
2645
|
-
emit
|
|
2646
|
-
if (enabled()) capture("lifecycle", { action }, epoch, Date.now());
|
|
2647
|
-
},
|
|
2678
|
+
emit: emitPopup,
|
|
2648
2679
|
submit(input) {
|
|
2649
2680
|
return addon.submitContact(input);
|
|
2650
2681
|
},
|
|
@@ -2661,7 +2692,7 @@ function createSagepilotWebEvents(options) {
|
|
|
2661
2692
|
detach(true);
|
|
2662
2693
|
client.cancelPending();
|
|
2663
2694
|
try {
|
|
2664
|
-
await operation();
|
|
2695
|
+
return await operation();
|
|
2665
2696
|
} finally {
|
|
2666
2697
|
identityChanging = false;
|
|
2667
2698
|
if (!destroyed) {
|
|
@@ -2890,17 +2921,21 @@ function createSagepilotWebEvents(options) {
|
|
|
2890
2921
|
}
|
|
2891
2922
|
},
|
|
2892
2923
|
flush,
|
|
2893
|
-
/**
|
|
2924
|
+
/** Keeps contact on the identity endpoint and fences capture while adopting the customer. */
|
|
2894
2925
|
async submitContact(input) {
|
|
2895
|
-
const
|
|
2896
|
-
const result = await client.submitContact(input);
|
|
2897
|
-
if (result.status === "recorded")
|
|
2926
|
+
const session = client.session();
|
|
2927
|
+
const result = session?.popup?.verification === "none" && session.customer_id === null ? await changeIdentity(() => client.submitContact(input)) : await client.submitContact(input);
|
|
2928
|
+
if (result.status === "recorded") {
|
|
2929
|
+
if (result.popup_submission_recorded !== true) emitPopup("popup_submitted");
|
|
2930
|
+
await completeVisitor(session?.anonymous_id);
|
|
2931
|
+
}
|
|
2898
2932
|
return result;
|
|
2899
2933
|
},
|
|
2900
2934
|
/** Verifies proof behind a capture fence; buffered anonymous observations remain anonymous. */
|
|
2901
2935
|
async verifyContact(challengeId, code) {
|
|
2902
2936
|
const anonymousId = client.session()?.anonymous_id;
|
|
2903
|
-
await changeIdentity(() => client.verifyContact(challengeId, code));
|
|
2937
|
+
const session = await changeIdentity(() => client.verifyContact(challengeId, code));
|
|
2938
|
+
if (session.popup_submission_recorded !== true) emitPopup("popup_submitted");
|
|
2904
2939
|
await completeVisitor(anonymousId);
|
|
2905
2940
|
},
|
|
2906
2941
|
/** Applies an explicit logout boundary without relabeling earlier observations. */
|