@agent-native/core 0.205.0 → 0.206.0-nightly-20261009011246
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/dist/a2a/client.d.ts +2 -2
- package/dist/agent/engine/anthropic-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.js +23 -8
- package/dist/agent/engine/error-telemetry.d.ts +5 -0
- package/dist/agent/engine/error-telemetry.js +41 -0
- package/dist/agent/engine/registry.d.ts +1 -6
- package/dist/agent/engine/registry.js +2 -31
- package/dist/agent/model-config.d.ts +26 -14
- package/dist/agent/model-config.js +131 -8
- package/dist/agent/model-version.d.ts +19 -0
- package/dist/agent/model-version.js +153 -0
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.js +24 -6
- package/dist/agent/provider-model-selection.js +6 -4
- package/dist/agent/run-manager.js +5 -11
- package/dist/agent/thread-data-builder.js +14 -6
- package/dist/authorization/check-action.js +8 -4
- package/dist/chat-threads/store.js +22 -9
- package/dist/client/analytics.d.ts +1 -0
- package/dist/client/analytics.js +10 -2
- package/dist/client/chat/runtime.js +18 -0
- package/dist/client/chat-model-groups.d.ts +3 -0
- package/dist/client/error-format.js +10 -7
- package/dist/client/onboarding/use-onboarding.d.ts +12 -0
- package/dist/client/onboarding/use-onboarding.js +491 -4
- package/dist/client/session-replay-privacy.d.ts +8 -0
- package/dist/client/session-replay-privacy.js +8 -0
- package/dist/client/session-replay.d.ts +1 -0
- package/dist/client/session-replay.js +18 -9
- package/dist/client/sse-event-processor.js +10 -6
- package/dist/client/use-chat-models.d.ts +5 -0
- package/dist/client/use-chat-models.js +51 -5
- package/dist/client/use-session.d.ts +8 -1
- package/dist/client/use-session.js +20 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +353 -3
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/jobs/backfill-run-history-ownership.d.ts +12 -0
- package/dist/jobs/backfill-run-history-ownership.js +87 -0
- package/dist/jobs/background-automation-runner.js +3 -9
- package/dist/jobs/remote-execution.js +3 -9
- package/dist/jobs/run-history-ownership.d.ts +5 -0
- package/dist/jobs/run-history-ownership.js +16 -0
- package/dist/jobs/run-now.js +2 -3
- package/dist/localization/agent-resources-messages.d.ts +3 -0
- package/dist/localization/agent-resources-messages.js +33 -0
- package/dist/mcp/actions/create-org-service-token.js +5 -7
- package/dist/mcp/build-server.d.ts +23 -18
- package/dist/mcp/build-server.js +220 -212
- package/dist/mcp/connect-route.d.ts +11 -2
- package/dist/mcp/connect-route.js +34 -24
- package/dist/mcp/connect-store.d.ts +9 -13
- package/dist/mcp/connect-store.js +11 -24
- package/dist/mcp/credential-membership.js +17 -6
- package/dist/mcp/oauth-route.d.ts +11 -1
- package/dist/mcp/oauth-route.js +28 -6
- package/dist/mcp/oauth-token.d.ts +12 -2
- package/dist/mcp/oauth-token.js +15 -4
- package/dist/mcp/server.js +9 -5
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/posthog-ai.d.ts +1 -0
- package/dist/observability/posthog-ai.js +20 -8
- package/dist/observability/routes.d.ts +6 -6
- package/dist/observability/traces.js +61 -39
- package/dist/org/app-roles.d.ts +8 -1
- package/dist/org/app-roles.js +29 -9
- package/dist/org/context.js +24 -8
- package/dist/org/index.d.ts +2 -2
- package/dist/org/workspace-app-identity.d.ts +1 -0
- package/dist/org/workspace-app-identity.js +25 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +3 -3
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/scripts/agent-engines/list-agent-engines.js +1 -0
- package/dist/server/action-routes.js +8 -1
- package/dist/server/agent-chat-plugin.js +1 -0
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/auth.js +33 -17
- package/dist/server/bearer-credential-refusal.d.ts +23 -0
- package/dist/server/bearer-credential-refusal.js +67 -0
- package/dist/server/better-auth-instance.js +13 -6
- package/dist/server/builder-browser.d.ts +2 -0
- package/dist/server/builder-browser.js +16 -3
- package/dist/server/capture-error.d.ts +1 -0
- package/dist/server/capture-error.js +28 -4
- package/dist/server/complete-text.js +16 -3
- package/dist/server/core-routes-plugin.js +2 -0
- package/dist/server/dev-action-bridge.js +7 -1
- package/dist/server/google-oauth.js +6 -3
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/recap-image-route.js +4 -0
- package/dist/server/request-context.d.ts +16 -0
- package/dist/server/request-context.js +17 -0
- package/dist/shared/agent-chat-context.d.ts +1 -0
- package/dist/shared/agent-chat-context.js +26 -0
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/reasoning-effort.js +10 -1
- package/dist/shared/ssr-session-bootstrap.d.ts +7 -0
- package/dist/shared/ssr-session-bootstrap.js +21 -0
- package/dist/sharing/access.js +86 -5
- package/dist/templates/builder-code-starter/server/routes/index.get.ts +2 -0
- package/dist/triggers/routes.js +2 -1
- package/docs/content/automation-connectors.mdx +5 -3
- package/docs/content/automations.mdx +78 -49
- package/docs/content/observability.mdx +16 -5
- package/docs/content/organizations-teams-permissions.mdx +44 -2
- package/docs/content/recurring-jobs.mdx +4 -8
- package/docs/content/template-forms.mdx +6 -0
- package/package.json +5 -4
- package/src/templates/builder-code-starter/server/routes/index.get.ts +2 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { OnboardingAppProfile, OnboardingMethod, OnboardingStepStatus } from "../../onboarding/types.js";
|
|
2
|
+
type CustomKeyOnboardingSetupKind = "credential" | "local_endpoint";
|
|
3
|
+
type CustomKeyOnboardingOutcome = "credential_entry_started" | "credential_validated" | "credential_saved" | "credential_skipped" | "credential_abandoned" | "local_endpoint_saved" | "local_endpoint_skipped" | "local_endpoint_abandoned";
|
|
4
|
+
type CustomKeyOutcomeResult = "tracked" | "tracked_uncorrelated" | "tracked_storage_unavailable" | "pending" | "missing" | "unavailable" | "stale" | "identity_mismatch" | "duplicate";
|
|
5
|
+
export declare function createOnboardingCorrelationId(): string;
|
|
6
|
+
export declare function setCustomKeyOnboardingAttempt(id: string): Promise<"stored" | "no_session" | "unavailable">;
|
|
7
|
+
export declare function setCustomKeyOnboardingSetupKind(setupKind: CustomKeyOnboardingSetupKind): "stored" | "memory" | "missing" | "unavailable";
|
|
8
|
+
export declare function trackCustomKeyOnboardingOutcome(outcome: CustomKeyOnboardingOutcome): CustomKeyOutcomeResult;
|
|
9
|
+
export declare function withCustomKeyOnboardingCredentialSave<T>(save: () => Promise<T>): Promise<T>;
|
|
10
|
+
export declare function withCustomKeyOnboardingLocalEndpointSave<T>(save: () => Promise<T>): Promise<T>;
|
|
11
|
+
export declare function requestCustomKeyOnboardingAbandonment(): void;
|
|
12
|
+
export declare function useCustomKeyOnboardingAttemptLifecycle(): void;
|
|
2
13
|
export declare function __resetOnboardingSummaryReadsForTests(): void;
|
|
14
|
+
export declare function __resetOnboardingEventDedupeForTests(): void;
|
|
3
15
|
export declare function trackOnboardingEvent(name: string, properties: Record<string, unknown>): void;
|
|
4
16
|
export interface UseOnboardingResult {
|
|
5
17
|
steps: OnboardingStepStatus[];
|
|
@@ -1,11 +1,464 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
import { getAnalyticsIdentityKey, trackEvent } from "../analytics.js";
|
|
2
|
+
import { getAnalyticsIdentityKey, getAnalyticsSessionId, resolveAnalyticsIdentityKey, trackEvent, } from "../analytics.js";
|
|
3
3
|
import { agentNativePath } from "../api-path.js";
|
|
4
4
|
import { scheduleAfterPaint, scheduleAfterStartup, } from "../use-after-paint.js";
|
|
5
5
|
import { dispatchFirstRunOnboardingStatus, fetchFirstRunOnboardingStatus, readFirstRunOnboardingCookieState, } from "./first-run-status.js";
|
|
6
|
+
const lastOnboardingStepViews = new Map();
|
|
6
7
|
const seenOnboardingEvents = new Set();
|
|
8
|
+
const CUSTOM_KEY_ATTEMPT_STORAGE_KEY = "agent-native.onboarding.custom_keys_attempt";
|
|
9
|
+
const locallyTrackedCustomKeyOutcomes = new Set();
|
|
10
|
+
const pendingCustomKeyCredentialSaves = new Map();
|
|
11
|
+
let onboardingDocumentId = null;
|
|
12
|
+
let onboardingCorrelationSequence = 0;
|
|
7
13
|
const ONBOARDING_SUMMARY_TIMEOUT_MS = 15_000;
|
|
8
14
|
const ONBOARDING_SUMMARY_REUSE_MS = 5_000;
|
|
15
|
+
function isTerminalCustomKeyOnboardingOutcome(outcome) {
|
|
16
|
+
return (outcome === "credential_saved" ||
|
|
17
|
+
outcome === "credential_skipped" ||
|
|
18
|
+
outcome === "credential_abandoned" ||
|
|
19
|
+
outcome === "local_endpoint_saved" ||
|
|
20
|
+
outcome === "local_endpoint_skipped" ||
|
|
21
|
+
outcome === "local_endpoint_abandoned");
|
|
22
|
+
}
|
|
23
|
+
export function createOnboardingCorrelationId() {
|
|
24
|
+
const cryptoApi = globalThis.crypto;
|
|
25
|
+
if (typeof cryptoApi?.randomUUID === "function") {
|
|
26
|
+
return cryptoApi.randomUUID();
|
|
27
|
+
}
|
|
28
|
+
onboardingCorrelationSequence += 1;
|
|
29
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}-${onboardingCorrelationSequence.toString(36)}`;
|
|
30
|
+
}
|
|
31
|
+
function getOnboardingDocumentId() {
|
|
32
|
+
return (onboardingDocumentId ??= createOnboardingCorrelationId());
|
|
33
|
+
}
|
|
34
|
+
// Keep the handoff alive while the SPA opens settings before auth refresh settles.
|
|
35
|
+
let pendingCustomKeyOnboardingAttempt = null;
|
|
36
|
+
function flushPendingCustomKeyOnboardingOutcomes(pending) {
|
|
37
|
+
const outcomes = pending.outcomes.splice(0);
|
|
38
|
+
for (const outcome of outcomes) {
|
|
39
|
+
trackCustomKeyOnboardingOutcome(outcome);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function readCustomKeyOnboardingAttempt() {
|
|
43
|
+
if (typeof window === "undefined")
|
|
44
|
+
return { kind: "unavailable" };
|
|
45
|
+
const pending = pendingCustomKeyOnboardingAttempt;
|
|
46
|
+
if (pending?.attempt.documentId === getOnboardingDocumentId()) {
|
|
47
|
+
if (pending.status === "resolving" || pending.status === "unavailable") {
|
|
48
|
+
return { kind: "pending", pending };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const value = window.sessionStorage.getItem(CUSTOM_KEY_ATTEMPT_STORAGE_KEY);
|
|
53
|
+
if (!value) {
|
|
54
|
+
if (pending?.attempt.documentId === getOnboardingDocumentId() &&
|
|
55
|
+
pending.attempt.identityKey) {
|
|
56
|
+
return {
|
|
57
|
+
kind: "available",
|
|
58
|
+
attempt: pending.attempt,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { kind: "available", attempt: null };
|
|
62
|
+
}
|
|
63
|
+
const parsed = JSON.parse(value);
|
|
64
|
+
if (!parsed || typeof parsed !== "object") {
|
|
65
|
+
return { kind: "unavailable" };
|
|
66
|
+
}
|
|
67
|
+
const attempt = parsed;
|
|
68
|
+
if (typeof attempt.id !== "string")
|
|
69
|
+
return { kind: "unavailable" };
|
|
70
|
+
if (attempt.documentId !== getOnboardingDocumentId()) {
|
|
71
|
+
return { kind: "stale" };
|
|
72
|
+
}
|
|
73
|
+
if (typeof attempt.identityKey !== "string") {
|
|
74
|
+
return { kind: "unavailable" };
|
|
75
|
+
}
|
|
76
|
+
if (pending?.attempt.id === attempt.id &&
|
|
77
|
+
pending.attempt.documentId === attempt.documentId &&
|
|
78
|
+
pending.attempt.identityKey) {
|
|
79
|
+
return {
|
|
80
|
+
kind: "available",
|
|
81
|
+
attempt: pending.attempt,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
kind: "available",
|
|
86
|
+
attempt: attempt,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
if (pending?.attempt.documentId === getOnboardingDocumentId() &&
|
|
91
|
+
pending.attempt.identityKey) {
|
|
92
|
+
return {
|
|
93
|
+
kind: "available",
|
|
94
|
+
attempt: pending.attempt,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { kind: "unavailable" };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function writePendingCustomKeyOnboardingAttempt(pending, identityKey) {
|
|
101
|
+
pending.attempt.identityKey = identityKey;
|
|
102
|
+
try {
|
|
103
|
+
window.sessionStorage.setItem(CUSTOM_KEY_ATTEMPT_STORAGE_KEY, JSON.stringify(pending.attempt));
|
|
104
|
+
pending.status = "stored";
|
|
105
|
+
return "stored";
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
pending.status = "memory";
|
|
109
|
+
return "unavailable";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function setCustomKeyOnboardingAttempt(id) {
|
|
113
|
+
if (typeof window === "undefined")
|
|
114
|
+
return Promise.resolve("unavailable");
|
|
115
|
+
const sessionId = getAnalyticsSessionId();
|
|
116
|
+
const pending = {
|
|
117
|
+
attempt: {
|
|
118
|
+
id,
|
|
119
|
+
documentId: getOnboardingDocumentId(),
|
|
120
|
+
setupKind: "credential",
|
|
121
|
+
},
|
|
122
|
+
identityKeyAtStart: getAnalyticsIdentityKey(),
|
|
123
|
+
status: "resolving",
|
|
124
|
+
outcomes: [],
|
|
125
|
+
};
|
|
126
|
+
pendingCustomKeyOnboardingAttempt = pending;
|
|
127
|
+
return resolveAnalyticsIdentityKey()
|
|
128
|
+
.then((identityKey) => {
|
|
129
|
+
if (!sessionId || !identityKey) {
|
|
130
|
+
pending.status = "unavailable";
|
|
131
|
+
flushPendingCustomKeyOnboardingOutcomes(pending);
|
|
132
|
+
return "no_session";
|
|
133
|
+
}
|
|
134
|
+
const result = writePendingCustomKeyOnboardingAttempt(pending, identityKey);
|
|
135
|
+
flushPendingCustomKeyOnboardingOutcomes(pending);
|
|
136
|
+
return result;
|
|
137
|
+
})
|
|
138
|
+
.catch(() => {
|
|
139
|
+
pending.status = "unavailable";
|
|
140
|
+
flushPendingCustomKeyOnboardingOutcomes(pending);
|
|
141
|
+
return "unavailable";
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export function setCustomKeyOnboardingSetupKind(setupKind) {
|
|
145
|
+
const pending = pendingCustomKeyOnboardingAttempt;
|
|
146
|
+
if (pending && pending.attempt.documentId === getOnboardingDocumentId()) {
|
|
147
|
+
pending.attempt.setupKind = setupKind;
|
|
148
|
+
if (pending.status === "resolving")
|
|
149
|
+
return "memory";
|
|
150
|
+
if (pending.status === "unavailable")
|
|
151
|
+
return "unavailable";
|
|
152
|
+
}
|
|
153
|
+
const stored = readCustomKeyOnboardingAttempt();
|
|
154
|
+
if (stored.kind === "pending") {
|
|
155
|
+
stored.pending.attempt.setupKind = setupKind;
|
|
156
|
+
return stored.pending.status === "resolving" ? "memory" : "unavailable";
|
|
157
|
+
}
|
|
158
|
+
if (stored.kind !== "available" || !stored.attempt) {
|
|
159
|
+
return stored.kind === "available" ? "missing" : "unavailable";
|
|
160
|
+
}
|
|
161
|
+
const attempt = { ...stored.attempt, setupKind };
|
|
162
|
+
if (pending?.attempt.id === attempt.id)
|
|
163
|
+
pending.attempt.setupKind = setupKind;
|
|
164
|
+
try {
|
|
165
|
+
window.sessionStorage.setItem(CUSTOM_KEY_ATTEMPT_STORAGE_KEY, JSON.stringify(attempt));
|
|
166
|
+
return "stored";
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return "unavailable";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function trackUnavailableCustomKeyOnboardingOutcome(pending, outcome) {
|
|
173
|
+
const identityKey = pending.attempt.identityKey ?? pending.identityKeyAtStart;
|
|
174
|
+
if (identityKey !== getAnalyticsIdentityKey()) {
|
|
175
|
+
if (pendingCustomKeyOnboardingAttempt === pending) {
|
|
176
|
+
pendingCustomKeyOnboardingAttempt = null;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
window.sessionStorage.removeItem(CUSTOM_KEY_ATTEMPT_STORAGE_KEY);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return "unavailable";
|
|
183
|
+
}
|
|
184
|
+
return "identity_mismatch";
|
|
185
|
+
}
|
|
186
|
+
const attemptOutcomeKey = `${pending.attempt.id}:${outcome}`;
|
|
187
|
+
if (locallyTrackedCustomKeyOutcomes.has(attemptOutcomeKey)) {
|
|
188
|
+
return "duplicate";
|
|
189
|
+
}
|
|
190
|
+
trackOnboardingEvent("onboarding_method_outcome", {
|
|
191
|
+
flow: "first_run",
|
|
192
|
+
step_id: "choice",
|
|
193
|
+
method_id: "custom_keys",
|
|
194
|
+
onboarding_attempt_id: pending.attempt.id,
|
|
195
|
+
outcome,
|
|
196
|
+
correlation_status: "unavailable",
|
|
197
|
+
});
|
|
198
|
+
locallyTrackedCustomKeyOutcomes.add(attemptOutcomeKey);
|
|
199
|
+
if (isTerminalCustomKeyOnboardingOutcome(outcome)) {
|
|
200
|
+
if (pendingCustomKeyOnboardingAttempt === pending) {
|
|
201
|
+
pendingCustomKeyOnboardingAttempt = null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return "tracked_uncorrelated";
|
|
205
|
+
}
|
|
206
|
+
export function trackCustomKeyOnboardingOutcome(outcome) {
|
|
207
|
+
const stored = readCustomKeyOnboardingAttempt();
|
|
208
|
+
if (stored.kind === "unavailable")
|
|
209
|
+
return "unavailable";
|
|
210
|
+
if (stored.kind === "pending") {
|
|
211
|
+
if (stored.pending.status === "resolving") {
|
|
212
|
+
if (stored.pending.outcomes.includes(outcome))
|
|
213
|
+
return "duplicate";
|
|
214
|
+
stored.pending.outcomes.push(outcome);
|
|
215
|
+
return "pending";
|
|
216
|
+
}
|
|
217
|
+
return trackUnavailableCustomKeyOnboardingOutcome(stored.pending, outcome);
|
|
218
|
+
}
|
|
219
|
+
if (stored.kind === "stale") {
|
|
220
|
+
// A duplicated tab can copy sessionStorage while the original attempt is live.
|
|
221
|
+
try {
|
|
222
|
+
window.sessionStorage.removeItem(CUSTOM_KEY_ATTEMPT_STORAGE_KEY);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return "unavailable";
|
|
226
|
+
}
|
|
227
|
+
return "stale";
|
|
228
|
+
}
|
|
229
|
+
const { attempt } = stored;
|
|
230
|
+
if (!attempt)
|
|
231
|
+
return "missing";
|
|
232
|
+
if (attempt.identityKey !== getAnalyticsIdentityKey()) {
|
|
233
|
+
try {
|
|
234
|
+
window.sessionStorage.removeItem(CUSTOM_KEY_ATTEMPT_STORAGE_KEY);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
if (pendingCustomKeyOnboardingAttempt?.attempt.id === attempt.id) {
|
|
238
|
+
pendingCustomKeyOnboardingAttempt = null;
|
|
239
|
+
}
|
|
240
|
+
return "unavailable";
|
|
241
|
+
}
|
|
242
|
+
if (pendingCustomKeyOnboardingAttempt?.attempt.id === attempt.id) {
|
|
243
|
+
pendingCustomKeyOnboardingAttempt = null;
|
|
244
|
+
}
|
|
245
|
+
return "identity_mismatch";
|
|
246
|
+
}
|
|
247
|
+
const attemptOutcomeKey = `${attempt.id}:${outcome}`;
|
|
248
|
+
const alreadyTracked = (outcome === "credential_entry_started" && attempt.entryStarted) ||
|
|
249
|
+
(outcome === "credential_validated" && attempt.credentialValidated);
|
|
250
|
+
if (alreadyTracked ||
|
|
251
|
+
locallyTrackedCustomKeyOutcomes.has(attemptOutcomeKey)) {
|
|
252
|
+
if (isTerminalCustomKeyOnboardingOutcome(outcome)) {
|
|
253
|
+
try {
|
|
254
|
+
window.sessionStorage.removeItem(CUSTOM_KEY_ATTEMPT_STORAGE_KEY);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return "unavailable";
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return "duplicate";
|
|
261
|
+
}
|
|
262
|
+
trackOnboardingEvent("onboarding_method_outcome", {
|
|
263
|
+
flow: "first_run",
|
|
264
|
+
step_id: "choice",
|
|
265
|
+
method_id: "custom_keys",
|
|
266
|
+
onboarding_attempt_id: attempt.id,
|
|
267
|
+
outcome,
|
|
268
|
+
});
|
|
269
|
+
locallyTrackedCustomKeyOutcomes.add(attemptOutcomeKey);
|
|
270
|
+
if (outcome === "credential_entry_started" ||
|
|
271
|
+
outcome === "credential_validated") {
|
|
272
|
+
try {
|
|
273
|
+
window.sessionStorage.setItem(CUSTOM_KEY_ATTEMPT_STORAGE_KEY, JSON.stringify({
|
|
274
|
+
...attempt,
|
|
275
|
+
[outcome === "credential_entry_started"
|
|
276
|
+
? "entryStarted"
|
|
277
|
+
: "credentialValidated"]: true,
|
|
278
|
+
}));
|
|
279
|
+
if (pendingCustomKeyOnboardingAttempt?.attempt.id === attempt.id) {
|
|
280
|
+
pendingCustomKeyOnboardingAttempt.attempt = {
|
|
281
|
+
...pendingCustomKeyOnboardingAttempt.attempt,
|
|
282
|
+
...attempt,
|
|
283
|
+
[outcome === "credential_entry_started"
|
|
284
|
+
? "entryStarted"
|
|
285
|
+
: "credentialValidated"]: true,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
if (pendingCustomKeyOnboardingAttempt?.attempt.id !== attempt.id) {
|
|
291
|
+
return "tracked_storage_unavailable";
|
|
292
|
+
}
|
|
293
|
+
pendingCustomKeyOnboardingAttempt.status = "memory";
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (isTerminalCustomKeyOnboardingOutcome(outcome)) {
|
|
297
|
+
try {
|
|
298
|
+
window.sessionStorage.removeItem(CUSTOM_KEY_ATTEMPT_STORAGE_KEY);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
if (pendingCustomKeyOnboardingAttempt?.attempt.id !== attempt.id) {
|
|
302
|
+
return "tracked_storage_unavailable";
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (pendingCustomKeyOnboardingAttempt?.attempt.id === attempt.id) {
|
|
306
|
+
pendingCustomKeyOnboardingAttempt = null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return "tracked";
|
|
310
|
+
}
|
|
311
|
+
function trackCustomKeyOnboardingOutcomeForAttempt(attemptId, outcome) {
|
|
312
|
+
const stored = readCustomKeyOnboardingAttempt();
|
|
313
|
+
if ((stored.kind !== "available" && stored.kind !== "pending") ||
|
|
314
|
+
(stored.kind === "available" && stored.attempt?.id !== attemptId) ||
|
|
315
|
+
(stored.kind === "pending" && stored.pending.attempt.id !== attemptId) ||
|
|
316
|
+
(stored.kind === "available" &&
|
|
317
|
+
stored.attempt?.identityKey !== getAnalyticsIdentityKey()) ||
|
|
318
|
+
(stored.kind === "pending" &&
|
|
319
|
+
stored.pending.attempt.identityKey !== undefined &&
|
|
320
|
+
stored.pending.attempt.identityKey !== getAnalyticsIdentityKey())) {
|
|
321
|
+
return "missing";
|
|
322
|
+
}
|
|
323
|
+
return trackCustomKeyOnboardingOutcome(outcome);
|
|
324
|
+
}
|
|
325
|
+
function beginCustomKeyOnboardingSave(saveOutcome, abandonmentOutcome) {
|
|
326
|
+
const stored = readCustomKeyOnboardingAttempt();
|
|
327
|
+
if (stored.kind === "available" && stored.attempt) {
|
|
328
|
+
if (stored.attempt.identityKey !== getAnalyticsIdentityKey())
|
|
329
|
+
return null;
|
|
330
|
+
return beginCustomKeyOnboardingSaveForAttempt(stored.attempt.id, saveOutcome, abandonmentOutcome);
|
|
331
|
+
}
|
|
332
|
+
if (stored.kind === "pending" &&
|
|
333
|
+
stored.pending.attempt.documentId === getOnboardingDocumentId() &&
|
|
334
|
+
(stored.pending.attempt.identityKey === undefined ||
|
|
335
|
+
stored.pending.attempt.identityKey === getAnalyticsIdentityKey())) {
|
|
336
|
+
return beginCustomKeyOnboardingSaveForAttempt(stored.pending.attempt.id, saveOutcome, abandonmentOutcome);
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
function beginCustomKeyOnboardingSaveForAttempt(attemptId, saveOutcome, abandonmentOutcome) {
|
|
341
|
+
const pending = pendingCustomKeyCredentialSaves.get(attemptId) ?? {
|
|
342
|
+
count: 0,
|
|
343
|
+
abandonmentRequested: false,
|
|
344
|
+
saved: false,
|
|
345
|
+
saveOutcome,
|
|
346
|
+
abandonmentOutcome,
|
|
347
|
+
};
|
|
348
|
+
pending.count += 1;
|
|
349
|
+
pendingCustomKeyCredentialSaves.set(attemptId, pending);
|
|
350
|
+
let finished = false;
|
|
351
|
+
return {
|
|
352
|
+
finish(saved) {
|
|
353
|
+
if (finished)
|
|
354
|
+
return;
|
|
355
|
+
finished = true;
|
|
356
|
+
const current = pendingCustomKeyCredentialSaves.get(attemptId);
|
|
357
|
+
if (!current)
|
|
358
|
+
return;
|
|
359
|
+
try {
|
|
360
|
+
if (saved) {
|
|
361
|
+
const result = trackCustomKeyOnboardingOutcomeForAttempt(attemptId, current.saveOutcome);
|
|
362
|
+
if (result === "tracked" ||
|
|
363
|
+
result === "tracked_uncorrelated" ||
|
|
364
|
+
result === "tracked_storage_unavailable" ||
|
|
365
|
+
result === "duplicate") {
|
|
366
|
+
current.saved = true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
finally {
|
|
371
|
+
current.count -= 1;
|
|
372
|
+
if (current.count === 0) {
|
|
373
|
+
pendingCustomKeyCredentialSaves.delete(attemptId);
|
|
374
|
+
if (current.abandonmentRequested && !current.saved) {
|
|
375
|
+
trackCustomKeyOnboardingOutcomeForAttempt(attemptId, current.abandonmentOutcome);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
export async function withCustomKeyOnboardingCredentialSave(save) {
|
|
383
|
+
return withCustomKeyOnboardingSave(save, "credential_saved", "credential_abandoned");
|
|
384
|
+
}
|
|
385
|
+
export async function withCustomKeyOnboardingLocalEndpointSave(save) {
|
|
386
|
+
return withCustomKeyOnboardingSave(save, "local_endpoint_saved", "local_endpoint_abandoned");
|
|
387
|
+
}
|
|
388
|
+
async function withCustomKeyOnboardingSave(save, saveOutcome, abandonmentOutcome) {
|
|
389
|
+
const credentialSave = beginCustomKeyOnboardingSave(saveOutcome, abandonmentOutcome);
|
|
390
|
+
let saved = false;
|
|
391
|
+
try {
|
|
392
|
+
const result = await save();
|
|
393
|
+
saved = true;
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
finally {
|
|
397
|
+
credentialSave?.finish(saved);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function handleCustomKeyOnboardingAbandonment() {
|
|
401
|
+
const stored = readCustomKeyOnboardingAttempt();
|
|
402
|
+
const attempt = stored.kind === "available"
|
|
403
|
+
? stored.attempt
|
|
404
|
+
: stored.kind === "pending"
|
|
405
|
+
? stored.pending.attempt
|
|
406
|
+
: null;
|
|
407
|
+
if (attempt &&
|
|
408
|
+
(attempt.identityKey === undefined ||
|
|
409
|
+
attempt.identityKey === getAnalyticsIdentityKey())) {
|
|
410
|
+
const attemptId = attempt.id;
|
|
411
|
+
const completedOutcome = [
|
|
412
|
+
"credential_saved",
|
|
413
|
+
"credential_skipped",
|
|
414
|
+
"credential_abandoned",
|
|
415
|
+
"local_endpoint_saved",
|
|
416
|
+
"local_endpoint_skipped",
|
|
417
|
+
"local_endpoint_abandoned",
|
|
418
|
+
].find((outcome) => locallyTrackedCustomKeyOutcomes.has(`${attemptId}:${outcome}`));
|
|
419
|
+
if (completedOutcome) {
|
|
420
|
+
trackCustomKeyOnboardingOutcome(completedOutcome);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const pending = pendingCustomKeyCredentialSaves.get(attemptId);
|
|
424
|
+
if (pending && pending.count > 0) {
|
|
425
|
+
pending.abandonmentRequested = true;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
trackCustomKeyOnboardingOutcome(attempt.setupKind === "local_endpoint"
|
|
429
|
+
? "local_endpoint_abandoned"
|
|
430
|
+
: "credential_abandoned");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
trackCustomKeyOnboardingOutcome("credential_abandoned");
|
|
434
|
+
}
|
|
435
|
+
export function requestCustomKeyOnboardingAbandonment() {
|
|
436
|
+
handleCustomKeyOnboardingAbandonment();
|
|
437
|
+
}
|
|
438
|
+
export function useCustomKeyOnboardingAttemptLifecycle() {
|
|
439
|
+
const mountedRef = useRef(false);
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
mountedRef.current = true;
|
|
442
|
+
if (readCustomKeyOnboardingAttempt().kind === "stale") {
|
|
443
|
+
trackCustomKeyOnboardingOutcome("credential_abandoned");
|
|
444
|
+
}
|
|
445
|
+
const handlePageHide = (event) => {
|
|
446
|
+
if (!event.persisted) {
|
|
447
|
+
handleCustomKeyOnboardingAbandonment();
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
window.addEventListener("pagehide", handlePageHide);
|
|
451
|
+
return () => {
|
|
452
|
+
window.removeEventListener("pagehide", handlePageHide);
|
|
453
|
+
mountedRef.current = false;
|
|
454
|
+
queueMicrotask(() => {
|
|
455
|
+
if (!mountedRef.current) {
|
|
456
|
+
handleCustomKeyOnboardingAbandonment();
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
};
|
|
460
|
+
}, []);
|
|
461
|
+
}
|
|
9
462
|
const sharedSummaryReads = new Map();
|
|
10
463
|
/**
|
|
11
464
|
* The setup button, the checklist panel, and the first-run surface each mount
|
|
@@ -58,11 +511,17 @@ function readOnboardingSummary(url, fresh) {
|
|
|
58
511
|
export function __resetOnboardingSummaryReadsForTests() {
|
|
59
512
|
sharedSummaryReads.clear();
|
|
60
513
|
}
|
|
514
|
+
export function __resetOnboardingEventDedupeForTests() {
|
|
515
|
+
lastOnboardingStepViews.clear();
|
|
516
|
+
seenOnboardingEvents.clear();
|
|
517
|
+
}
|
|
61
518
|
export function trackOnboardingEvent(name, properties) {
|
|
62
519
|
if (typeof window === "undefined")
|
|
63
520
|
return;
|
|
64
521
|
const identityKey = getAnalyticsIdentityKey() ?? "anonymous";
|
|
522
|
+
const sessionId = getAnalyticsSessionId() ?? "unknown-session";
|
|
65
523
|
const key = [
|
|
524
|
+
sessionId,
|
|
66
525
|
identityKey,
|
|
67
526
|
name,
|
|
68
527
|
properties.flow,
|
|
@@ -70,9 +529,20 @@ export function trackOnboardingEvent(name, properties) {
|
|
|
70
529
|
properties.extension_id,
|
|
71
530
|
properties.integration_id,
|
|
72
531
|
properties.role,
|
|
532
|
+
properties.step_view_id,
|
|
533
|
+
properties.onboarding_attempt_id,
|
|
73
534
|
]
|
|
74
535
|
.map((value) => String(value ?? ""))
|
|
75
536
|
.join(":");
|
|
537
|
+
const stepViewScope = JSON.stringify([
|
|
538
|
+
sessionId,
|
|
539
|
+
identityKey,
|
|
540
|
+
properties.flow,
|
|
541
|
+
]);
|
|
542
|
+
const stepIdentity = JSON.stringify([
|
|
543
|
+
properties.step_id,
|
|
544
|
+
properties.extension_id,
|
|
545
|
+
]);
|
|
76
546
|
const isRepeatableInteraction = name.startsWith("integration_") ||
|
|
77
547
|
name === "onboarding_role_save_started" ||
|
|
78
548
|
name === "onboarding_method_clicked" ||
|
|
@@ -81,10 +551,27 @@ export function trackOnboardingEvent(name, properties) {
|
|
|
81
551
|
name === "onboarding_dismissed" ||
|
|
82
552
|
name === "onboarding_reopened" ||
|
|
83
553
|
name === "onboarding_abandoned";
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
554
|
+
if (name === "onboarding_step_viewed") {
|
|
555
|
+
if (typeof properties.step_view_id === "string" &&
|
|
556
|
+
properties.step_view_id) {
|
|
557
|
+
if (seenOnboardingEvents.has(key))
|
|
558
|
+
return;
|
|
559
|
+
seenOnboardingEvents.add(key);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
if (lastOnboardingStepViews.get(stepViewScope) === stepIdentity)
|
|
563
|
+
return;
|
|
564
|
+
lastOnboardingStepViews.set(stepViewScope, stepIdentity);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
else if (name === "onboarding_reopened") {
|
|
568
|
+
lastOnboardingStepViews.delete(stepViewScope);
|
|
569
|
+
}
|
|
570
|
+
else if (!isRepeatableInteraction) {
|
|
571
|
+
if (seenOnboardingEvents.has(key))
|
|
572
|
+
return;
|
|
87
573
|
seenOnboardingEvents.add(key);
|
|
574
|
+
}
|
|
88
575
|
trackEvent(name, properties);
|
|
89
576
|
}
|
|
90
577
|
export function useOnboarding(options = {}) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const SESSION_REPLAY_MASK_ATTRIBUTE = "data-an-mask";
|
|
2
|
+
export declare const SESSION_REPLAY_MASK_PROPS: {
|
|
3
|
+
readonly "data-an-mask": "";
|
|
4
|
+
};
|
|
5
|
+
export declare const SESSION_REPLAY_BLOCK_ATTRIBUTE = "data-an-block";
|
|
6
|
+
export declare const SESSION_REPLAY_BLOCK_PROPS: {
|
|
7
|
+
readonly "data-an-block": "";
|
|
8
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const SESSION_REPLAY_MASK_ATTRIBUTE = "data-an-mask";
|
|
2
|
+
export const SESSION_REPLAY_MASK_PROPS = {
|
|
3
|
+
[SESSION_REPLAY_MASK_ATTRIBUTE]: "",
|
|
4
|
+
};
|
|
5
|
+
export const SESSION_REPLAY_BLOCK_ATTRIBUTE = "data-an-block";
|
|
6
|
+
export const SESSION_REPLAY_BLOCK_PROPS = {
|
|
7
|
+
[SESSION_REPLAY_BLOCK_ATTRIBUTE]: "",
|
|
8
|
+
};
|
|
@@ -39,6 +39,7 @@ export interface SessionReplayOptions {
|
|
|
39
39
|
blockSelector?: string;
|
|
40
40
|
ignoreSelector?: string;
|
|
41
41
|
maskTextClass?: string | RegExp;
|
|
42
|
+
/** App selectors extend the framework's `data-an-mask` privacy marker. */
|
|
42
43
|
maskTextSelector?: string;
|
|
43
44
|
maskAllInputs?: boolean;
|
|
44
45
|
recordCanvas?: boolean;
|
|
@@ -3,6 +3,7 @@ import { resolveLaneEndpoint } from "../shared/environment-lanes.js";
|
|
|
3
3
|
import { loadOptionalPeer, OptionalPeerDependencyError, } from "../shared/optional-peer.js";
|
|
4
4
|
import { isSyntheticTrafficValue } from "../shared/test-traffic.js";
|
|
5
5
|
import { getOrCreateAnalyticsAnonymousId, getOrCreateAnalyticsSessionId, } from "./analytics-session.js";
|
|
6
|
+
import { SESSION_REPLAY_BLOCK_ATTRIBUTE, SESSION_REPLAY_MASK_ATTRIBUTE, } from "./session-replay-privacy.js";
|
|
6
7
|
import { decideReplayQuotaResponse, parseRetryAfterSeconds, } from "./session-replay-quota.js";
|
|
7
8
|
import { scrubUrl } from "./url-scrub.js";
|
|
8
9
|
function isSyntheticBrowserTraffic() {
|
|
@@ -24,7 +25,7 @@ const SESSION_REPLAY_IFRAME_BLOCK_SELECTOR = `iframe[${SESSION_REPLAY_IFRAME_ATT
|
|
|
24
25
|
const DEFAULT_BLOCK_SELECTOR = [
|
|
25
26
|
SESSION_REPLAY_IFRAME_BLOCK_SELECTOR,
|
|
26
27
|
"[data-sensitive]",
|
|
27
|
-
|
|
28
|
+
`[${SESSION_REPLAY_BLOCK_ATTRIBUTE}]`,
|
|
28
29
|
"[data-an-private]",
|
|
29
30
|
"[data-private]",
|
|
30
31
|
".an-block",
|
|
@@ -39,9 +40,18 @@ const DEFAULT_BLOCK_SELECTOR = [
|
|
|
39
40
|
"[name*='card' i]",
|
|
40
41
|
"[name*='ssn' i]",
|
|
41
42
|
].join(", ");
|
|
43
|
+
/**
|
|
44
|
+
* App block selectors extend these framework markers instead of replacing them.
|
|
45
|
+
* Append them whole: a custom selector can mention one, as in
|
|
46
|
+
* `[data-an-block] .secret`, without matching the bare marker.
|
|
47
|
+
*/
|
|
48
|
+
const REQUIRED_BLOCK_SELECTORS = [
|
|
49
|
+
SESSION_REPLAY_IFRAME_BLOCK_SELECTOR,
|
|
50
|
+
`[${SESSION_REPLAY_BLOCK_ATTRIBUTE}]`,
|
|
51
|
+
];
|
|
42
52
|
const DEFAULT_IGNORE_SELECTOR = ".an-ignore, [data-an-ignore]";
|
|
43
53
|
const DEFAULT_MASK_TEXT_CLASS = "an-mask";
|
|
44
|
-
const DEFAULT_MASK_TEXT_SELECTOR =
|
|
54
|
+
const DEFAULT_MASK_TEXT_SELECTOR = `[${SESSION_REPLAY_MASK_ATTRIBUTE}]`;
|
|
45
55
|
const DEFAULT_MASK_INPUT_OPTIONS = {
|
|
46
56
|
color: true,
|
|
47
57
|
date: true,
|
|
@@ -586,10 +596,14 @@ function normalizeOptions(options) {
|
|
|
586
596
|
checkoutEveryNth: options.checkoutEveryNth,
|
|
587
597
|
checkoutEveryNms: options.checkoutEveryNms,
|
|
588
598
|
inlineStylesheet: options.inlineStylesheet ?? true,
|
|
589
|
-
blockSelector:
|
|
599
|
+
blockSelector: options.blockSelector
|
|
600
|
+
? [options.blockSelector, ...REQUIRED_BLOCK_SELECTORS].join(", ")
|
|
601
|
+
: DEFAULT_BLOCK_SELECTOR,
|
|
590
602
|
ignoreSelector: options.ignoreSelector || DEFAULT_IGNORE_SELECTOR,
|
|
591
603
|
maskTextClass: options.maskTextClass || DEFAULT_MASK_TEXT_CLASS,
|
|
592
|
-
maskTextSelector: options.maskTextSelector
|
|
604
|
+
maskTextSelector: options.maskTextSelector
|
|
605
|
+
? `${DEFAULT_MASK_TEXT_SELECTOR}, ${options.maskTextSelector}`
|
|
606
|
+
: DEFAULT_MASK_TEXT_SELECTOR,
|
|
593
607
|
maskAllInputs: options.maskAllInputs ?? true,
|
|
594
608
|
recordCanvas: options.recordCanvas ?? false,
|
|
595
609
|
recordCrossOriginIframes: options.recordCrossOriginIframes ?? window.parent === window,
|
|
@@ -605,11 +619,6 @@ function normalizeOptions(options) {
|
|
|
605
619
|
shouldStart: options.shouldStart,
|
|
606
620
|
};
|
|
607
621
|
}
|
|
608
|
-
function mergeReplayBlockSelector(blockSelector) {
|
|
609
|
-
return blockSelector.includes(SESSION_REPLAY_IFRAME_BLOCK_SELECTOR)
|
|
610
|
-
? blockSelector
|
|
611
|
-
: `${blockSelector}, ${SESSION_REPLAY_IFRAME_BLOCK_SELECTOR}`;
|
|
612
|
-
}
|
|
613
622
|
function normalizeCaptureToggle(value, defaultMaxEvents) {
|
|
614
623
|
if (value === false)
|
|
615
624
|
return null;
|