@sagepilot-ai/web-events-addon 0.2.4 → 0.2.5
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 +2 -2
- package/dist/index.cjs +72 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +72 -38
- package/dist/index.js.map +1 -1
- package/dist/sagepilot-web-events.global.js +72 -38
- package/dist/sagepilot-web-events.global.js.map +1 -1
- package/package.json +1 -1
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,24 @@ 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 lead 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 result = await this.authenticated("/contact", input);
|
|
222
|
+
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.");
|
|
223
|
+
const { session, ...acknowledgement } = result;
|
|
224
|
+
if (session != null) {
|
|
225
|
+
if (result.status !== "recorded" || prior?.customer_id !== null) throw new Error("Invalid popup lead session.");
|
|
226
|
+
await this.adoptTransition(session, prior, generation, true);
|
|
227
|
+
}
|
|
228
|
+
return acknowledgement;
|
|
229
|
+
} finally {
|
|
230
|
+
this.transitioning = false;
|
|
231
|
+
}
|
|
206
232
|
}
|
|
207
233
|
/** Adopts customer authority only from the server's completed verification operation. */
|
|
208
234
|
async verifyContact(challengeId, code) {
|
|
@@ -215,7 +241,7 @@ var SagepilotInstallationClient = class {
|
|
|
215
241
|
}
|
|
216
242
|
/** Keeps a bounded credential cache separate from observations so old anonymous batches retain their owner. */
|
|
217
243
|
remember(session) {
|
|
218
|
-
this.owners.set(session.session_id, { ...session });
|
|
244
|
+
this.owners.set(session.session_id, { ...session, browser_resume_token: void 0 });
|
|
219
245
|
while (this.owners.size > 4) this.owners.delete(this.owners.keys().next().value);
|
|
220
246
|
try {
|
|
221
247
|
void Promise.resolve(this.options.storage?.setItem(this.storageKey + ":owners", JSON.stringify([...this.owners.values()]))).catch(() => {
|
|
@@ -237,24 +263,28 @@ var SagepilotInstallationClient = class {
|
|
|
237
263
|
this.transitioning = true;
|
|
238
264
|
const generation = this.generation, prior = this.current;
|
|
239
265
|
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 };
|
|
266
|
+
return await this.adoptTransition(await this.authenticated(path, body), prior, generation, identified);
|
|
254
267
|
} finally {
|
|
255
268
|
this.transitioning = false;
|
|
256
269
|
}
|
|
257
270
|
}
|
|
271
|
+
/** Installs one new server owner and retains earlier credentials unless the owner logged out. */
|
|
272
|
+
async adoptTransition(value, prior, generation, identified) {
|
|
273
|
+
const next = this.validateSession(value);
|
|
274
|
+
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.");
|
|
275
|
+
this.generation++;
|
|
276
|
+
this.cancelPending();
|
|
277
|
+
this.pending = null;
|
|
278
|
+
if (identified) this.remember(prior);
|
|
279
|
+
else this.owners.delete(prior.session_id);
|
|
280
|
+
this.current = next;
|
|
281
|
+
this.remember(next);
|
|
282
|
+
try {
|
|
283
|
+
await this.options.storage?.setItem(this.storageKey, JSON.stringify(next));
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
return { ...next };
|
|
287
|
+
}
|
|
258
288
|
/** Cancels requests on permission/ownership boundaries while preserving the current session for safe renewal. */
|
|
259
289
|
cancelPending() {
|
|
260
290
|
this.uploadRevision++;
|
|
@@ -270,10 +300,10 @@ var SagepilotInstallationClient = class {
|
|
|
270
300
|
this.startup = null;
|
|
271
301
|
this.owners.clear();
|
|
272
302
|
}
|
|
273
|
-
/** Rejects foreign
|
|
303
|
+
/** Rejects foreign or malformed session responses before credentials are used. */
|
|
274
304
|
validateSession(value) {
|
|
275
305
|
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)) {
|
|
306
|
+
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
307
|
throw new Error("SDK session response has invalid scope or ownership.");
|
|
278
308
|
}
|
|
279
309
|
const url = safeURL(session.ingestion_url);
|
|
@@ -338,7 +368,7 @@ function createSagepilotInstallationClient(options) {
|
|
|
338
368
|
|
|
339
369
|
// package.json
|
|
340
370
|
var name = "@sagepilot-ai/web-events-addon";
|
|
341
|
-
var version = "0.2.
|
|
371
|
+
var version = "0.2.5";
|
|
342
372
|
|
|
343
373
|
// src/projection.ts
|
|
344
374
|
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 +2370,6 @@ function createPopup(options) {
|
|
|
2340
2370
|
}
|
|
2341
2371
|
const result = await options.submit(contact);
|
|
2342
2372
|
if (revision !== generation) return;
|
|
2343
|
-
options.emit("popup_submitted");
|
|
2344
2373
|
for (const input of inputs.values()) input.value = "";
|
|
2345
2374
|
if (result.status === "verification_required") {
|
|
2346
2375
|
let restartForm2 = function() {
|
|
@@ -2638,13 +2667,14 @@ function createSagepilotWebEvents(options) {
|
|
|
2638
2667
|
captureBytes -= bytes;
|
|
2639
2668
|
});
|
|
2640
2669
|
};
|
|
2670
|
+
function emitPopup(action) {
|
|
2671
|
+
if (enabled()) capture("lifecycle", { action }, epoch, Date.now());
|
|
2672
|
+
}
|
|
2641
2673
|
const popup = createPopup({
|
|
2642
2674
|
client,
|
|
2643
2675
|
storage,
|
|
2644
2676
|
report,
|
|
2645
|
-
emit
|
|
2646
|
-
if (enabled()) capture("lifecycle", { action }, epoch, Date.now());
|
|
2647
|
-
},
|
|
2677
|
+
emit: emitPopup,
|
|
2648
2678
|
submit(input) {
|
|
2649
2679
|
return addon.submitContact(input);
|
|
2650
2680
|
},
|
|
@@ -2661,7 +2691,7 @@ function createSagepilotWebEvents(options) {
|
|
|
2661
2691
|
detach(true);
|
|
2662
2692
|
client.cancelPending();
|
|
2663
2693
|
try {
|
|
2664
|
-
await operation();
|
|
2694
|
+
return await operation();
|
|
2665
2695
|
} finally {
|
|
2666
2696
|
identityChanging = false;
|
|
2667
2697
|
if (!destroyed) {
|
|
@@ -2890,17 +2920,21 @@ function createSagepilotWebEvents(options) {
|
|
|
2890
2920
|
}
|
|
2891
2921
|
},
|
|
2892
2922
|
flush,
|
|
2893
|
-
/**
|
|
2923
|
+
/** Keeps contact data on the identity endpoint and fences capture while a new lead session is adopted. */
|
|
2894
2924
|
async submitContact(input) {
|
|
2895
|
-
const
|
|
2896
|
-
const result = await client.submitContact(input);
|
|
2897
|
-
if (result.status === "recorded")
|
|
2925
|
+
const session = client.session();
|
|
2926
|
+
const result = session?.popup?.verification === "none" && session.customer_id === null ? await changeIdentity(() => client.submitContact(input)) : await client.submitContact(input);
|
|
2927
|
+
if (result.status === "recorded") {
|
|
2928
|
+
emitPopup("popup_submitted");
|
|
2929
|
+
await completeVisitor(session?.anonymous_id);
|
|
2930
|
+
}
|
|
2898
2931
|
return result;
|
|
2899
2932
|
},
|
|
2900
2933
|
/** Verifies proof behind a capture fence; buffered anonymous observations remain anonymous. */
|
|
2901
2934
|
async verifyContact(challengeId, code) {
|
|
2902
2935
|
const anonymousId = client.session()?.anonymous_id;
|
|
2903
2936
|
await changeIdentity(() => client.verifyContact(challengeId, code));
|
|
2937
|
+
emitPopup("popup_submitted");
|
|
2904
2938
|
await completeVisitor(anonymousId);
|
|
2905
2939
|
},
|
|
2906
2940
|
/** Applies an explicit logout boundary without relabeling earlier observations. */
|