@agent-native/core 0.201.1 → 0.201.2-nightly-20261006010750
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/automation/index.d.ts +1 -1
- package/dist/client/analytics/index.d.ts +1 -1
- package/dist/client/analytics/index.js +1 -1
- package/dist/client/analytics.d.ts +23 -3
- package/dist/client/analytics.js +189 -46
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +11 -11
- package/dist/server/agent-access.js +7 -3
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/server/attribution.d.ts +33 -1
- package/dist/server/attribution.js +93 -21
- package/dist/server/auth.js +4 -10
- package/dist/server/magic-link-attribution.js +2 -1
- package/dist/server/short-lived-token.d.ts +7 -0
- package/dist/server/short-lived-token.js +47 -0
- package/dist/shared/analytics-anonymous-id.d.ts +1 -0
- package/dist/shared/analytics-anonymous-id.js +1 -1
- package/dist/shared/attribution-source.d.ts +30 -0
- package/dist/shared/attribution-source.js +65 -0
- package/package.json +3 -3
|
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
|
|
|
139
139
|
duplicate?: undefined;
|
|
140
140
|
eventId?: undefined;
|
|
141
141
|
} | {
|
|
142
|
+
error?: undefined;
|
|
142
143
|
accepted: boolean;
|
|
143
144
|
duplicate: boolean;
|
|
144
145
|
eventId: string;
|
|
145
|
-
error?: undefined;
|
|
146
146
|
}>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureAttribution, captureClientException, captureError, captureException, captureMessage, clearAnalyticsSessionId, configureTracking, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, getSessionReplayContext, getSessionReplayUrl, isErrorCaptureInstalled, maybeStartSessionReplay, setAnalyticsSessionId, setSentryUser, setTrackingContentCaptureEnabled, setTrackingIdentity, startSessionReplay, stopSessionReplay, trackAgentChatLifecycle, trackAnonymousEvent, trackEvent, trackLifecycleEvent, trackSessionStatus, type AgentChatLifecycleEvent, type CaptureExceptionContext, type CapturedExceptionEvent, type ClientCaptureContext, type ConfigureTrackingOptions, type ErrorCaptureConfigOptions, type ExceptionBreadcrumb, type ExceptionLevel, type FirstTouchAttribution, type SessionReplayContext, type SessionReplayLinkOptions, type SessionReplayOptions, type SessionReplayStartResult, type SessionReplayUrlMatcher, type TrackingIdentityUser, } from "../analytics.js";
|
|
1
|
+
export { AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureAttribution, captureClientException, captureError, captureException, captureMessage, clearAnalyticsSessionId, configureTracking, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, getLastTouchAttribution, getSessionReplayContext, getSessionReplayUrl, isErrorCaptureInstalled, maybeStartSessionReplay, setAnalyticsSessionId, setSentryUser, setTrackingContentCaptureEnabled, setTrackingIdentity, startSessionReplay, stopSessionReplay, trackAgentChatLifecycle, trackAnonymousEvent, trackEvent, trackLifecycleEvent, trackSessionStatus, type AgentChatLifecycleEvent, type CaptureExceptionContext, type CapturedExceptionEvent, type ClientCaptureContext, type ConfigureTrackingOptions, type ErrorCaptureConfigOptions, type ExceptionBreadcrumb, type ExceptionLevel, type FirstTouchAttribution, type LastTouchAttribution, type SessionReplayContext, type SessionReplayLinkOptions, type SessionReplayOptions, type SessionReplayStartResult, type SessionReplayUrlMatcher, type TrackingIdentityUser, } from "../analytics.js";
|
|
2
2
|
export { track } from "../track.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureAttribution, captureClientException, captureError, captureException, captureMessage, clearAnalyticsSessionId, configureTracking, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, getSessionReplayContext, getSessionReplayUrl, isErrorCaptureInstalled, maybeStartSessionReplay, setAnalyticsSessionId, setSentryUser, setTrackingContentCaptureEnabled, setTrackingIdentity, startSessionReplay, stopSessionReplay, trackAgentChatLifecycle, trackAnonymousEvent, trackEvent, trackLifecycleEvent, trackSessionStatus, } from "../analytics.js";
|
|
1
|
+
export { AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureAttribution, captureClientException, captureError, captureException, captureMessage, clearAnalyticsSessionId, configureTracking, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, getLastTouchAttribution, getSessionReplayContext, getSessionReplayUrl, isErrorCaptureInstalled, maybeStartSessionReplay, setAnalyticsSessionId, setSentryUser, setTrackingContentCaptureEnabled, setTrackingIdentity, startSessionReplay, stopSessionReplay, trackAgentChatLifecycle, trackAnonymousEvent, trackEvent, trackLifecycleEvent, trackSessionStatus, } from "../analytics.js";
|
|
2
2
|
export { track } from "../track.js";
|
|
@@ -86,14 +86,34 @@ export interface FirstTouchAttribution {
|
|
|
86
86
|
landed_at?: string;
|
|
87
87
|
capture_truncated?: string;
|
|
88
88
|
}
|
|
89
|
+
/** The latest visit that had a source; see `captureLastTouchAttribution`. */
|
|
90
|
+
export interface LastTouchAttribution {
|
|
91
|
+
ref?: string;
|
|
92
|
+
via?: string;
|
|
93
|
+
utm_source?: string;
|
|
94
|
+
utm_medium?: string;
|
|
95
|
+
utm_campaign?: string;
|
|
96
|
+
utm_content?: string;
|
|
97
|
+
utm_term?: string;
|
|
98
|
+
gclid?: string;
|
|
99
|
+
msclkid?: string;
|
|
100
|
+
vector_source?: string;
|
|
101
|
+
landing_referrer?: string;
|
|
102
|
+
site_referrer?: string;
|
|
103
|
+
site_landing_path?: string;
|
|
104
|
+
landing_path?: string;
|
|
105
|
+
touched_at?: string;
|
|
106
|
+
capture_truncated?: string;
|
|
107
|
+
}
|
|
89
108
|
export declare function getAnalyticsIdentityKey(): string | undefined;
|
|
90
109
|
export declare function getAnalyticsAnonymousId(): string | undefined;
|
|
91
110
|
export declare function getAnalyticsSessionId(): string | undefined;
|
|
92
111
|
export declare function getFirstTouchAttribution(): FirstTouchAttribution | null;
|
|
112
|
+
export declare function getLastTouchAttribution(): LastTouchAttribution | null;
|
|
93
113
|
/**
|
|
94
|
-
* Store the visitor's first touch now instead of in
|
|
95
|
-
* for a page that reads
|
|
96
|
-
* load, so tracking's own capture is then a no-op.
|
|
114
|
+
* Store the visitor's first and last touch now instead of in
|
|
115
|
+
* `configureTracking()`, for a page that reads them before tracking starts.
|
|
116
|
+
* It runs once per page load, so tracking's own capture is then a no-op.
|
|
97
117
|
*/
|
|
98
118
|
export declare function captureAttribution(): void;
|
|
99
119
|
export declare function setSentryUser(user: TrackingIdentityUser | null, orgId?: string | null): void;
|
package/dist/client/analytics.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { recordTrackingEvent } from "../observability/tracing.js";
|
|
2
2
|
import { AGENT_NATIVE_LIFECYCLE_EVENTS, canonicalTrackingEvent, legacyLifecycleEvent, normalizeTrackingDimension, withCanonicalTrackingProperties, } from "../shared/analytics-events.js";
|
|
3
3
|
import { ANALYTICS_CLIENT_PLATFORM_PROPERTY, } from "../shared/analytics-platform.js";
|
|
4
|
+
import { hasAttributionSource } from "../shared/attribution-source.js";
|
|
4
5
|
import { resolveLaneEndpoint } from "../shared/environment-lanes.js";
|
|
5
6
|
import { classifyErrorNoise, } from "../shared/error-noise.js";
|
|
6
7
|
import { llmConnectionTrackingProperties, } from "../shared/llm-connection.js";
|
|
@@ -101,6 +102,72 @@ const FIRST_TOUCH_COOKIE_FIELD_PRIORITY = [
|
|
|
101
102
|
"site_landing_path",
|
|
102
103
|
"landed_at",
|
|
103
104
|
];
|
|
105
|
+
const LAST_TOUCH_STORAGE_KEY = "an_last_touch";
|
|
106
|
+
const LAST_TOUCH_COOKIE_NAME = "an_lt";
|
|
107
|
+
// Small enough that both cookies still fit the signup handoff header.
|
|
108
|
+
const LAST_TOUCH_MAX_COOKIE_BYTES = 700;
|
|
109
|
+
// What a sourced visit keeps as last touch, besides its path and time.
|
|
110
|
+
const LAST_TOUCH_SOURCE_FIELDS = [
|
|
111
|
+
"ref",
|
|
112
|
+
"via",
|
|
113
|
+
"utm_source",
|
|
114
|
+
"utm_medium",
|
|
115
|
+
"utm_campaign",
|
|
116
|
+
"utm_content",
|
|
117
|
+
"utm_term",
|
|
118
|
+
"gclid",
|
|
119
|
+
"msclkid",
|
|
120
|
+
"vector_source",
|
|
121
|
+
"landing_referrer",
|
|
122
|
+
"site_referrer",
|
|
123
|
+
];
|
|
124
|
+
// Which fields the cookie keeps first when they don't all fit.
|
|
125
|
+
const LAST_TOUCH_COOKIE_FIELD_PRIORITY = [
|
|
126
|
+
"ref",
|
|
127
|
+
"gclid",
|
|
128
|
+
"msclkid",
|
|
129
|
+
"vector_source",
|
|
130
|
+
"utm_source",
|
|
131
|
+
"utm_medium",
|
|
132
|
+
"utm_campaign",
|
|
133
|
+
"via",
|
|
134
|
+
"utm_content",
|
|
135
|
+
"utm_term",
|
|
136
|
+
"landing_referrer",
|
|
137
|
+
"site_referrer",
|
|
138
|
+
"landing_path",
|
|
139
|
+
"touched_at",
|
|
140
|
+
"site_landing_path",
|
|
141
|
+
];
|
|
142
|
+
// The marketing site forwards its own last touch under these names when it
|
|
143
|
+
// differs from the first touch it forwards as plain campaign params.
|
|
144
|
+
const FORWARDED_LAST_TOUCH_FIELDS = {
|
|
145
|
+
last_ref: "ref",
|
|
146
|
+
last_via: "via",
|
|
147
|
+
last_utm_source: "utm_source",
|
|
148
|
+
last_utm_medium: "utm_medium",
|
|
149
|
+
last_utm_campaign: "utm_campaign",
|
|
150
|
+
last_utm_content: "utm_content",
|
|
151
|
+
last_utm_term: "utm_term",
|
|
152
|
+
last_gclid: "gclid",
|
|
153
|
+
last_msclkid: "msclkid",
|
|
154
|
+
last_vector_source: "vector_source",
|
|
155
|
+
last_referrer: "site_referrer",
|
|
156
|
+
last_landing_path: "site_landing_path",
|
|
157
|
+
};
|
|
158
|
+
// When the site's latest sourced visit happened, so an older site visit can't
|
|
159
|
+
// replace a newer one the app already has.
|
|
160
|
+
const FORWARDED_LAST_TOUCH_AT_PARAM = "last_at";
|
|
161
|
+
const FIRST_TOUCH_COOKIE = {
|
|
162
|
+
name: FIRST_TOUCH_COOKIE_NAME,
|
|
163
|
+
maxBytes: FIRST_TOUCH_MAX_COOKIE_BYTES,
|
|
164
|
+
priority: FIRST_TOUCH_COOKIE_FIELD_PRIORITY,
|
|
165
|
+
};
|
|
166
|
+
const LAST_TOUCH_COOKIE = {
|
|
167
|
+
name: LAST_TOUCH_COOKIE_NAME,
|
|
168
|
+
maxBytes: LAST_TOUCH_MAX_COOKIE_BYTES,
|
|
169
|
+
priority: LAST_TOUCH_COOKIE_FIELD_PRIORITY,
|
|
170
|
+
};
|
|
104
171
|
let _firstTouchCaptured = false;
|
|
105
172
|
function safeStorageGet(key) {
|
|
106
173
|
try {
|
|
@@ -390,7 +457,7 @@ function buildFirstTouchAttribution() {
|
|
|
390
457
|
attribution.landed_at = new Date().toISOString();
|
|
391
458
|
return attribution;
|
|
392
459
|
}
|
|
393
|
-
function
|
|
460
|
+
function readAttributionCookie(cookieName) {
|
|
394
461
|
if (typeof document === "undefined")
|
|
395
462
|
return null;
|
|
396
463
|
try {
|
|
@@ -400,7 +467,7 @@ function readFirstTouchCookie() {
|
|
|
400
467
|
if (eq === -1)
|
|
401
468
|
continue;
|
|
402
469
|
const name = part.slice(0, eq).trim();
|
|
403
|
-
if (name ===
|
|
470
|
+
if (name === cookieName) {
|
|
404
471
|
return part.slice(eq + 1).trim();
|
|
405
472
|
}
|
|
406
473
|
}
|
|
@@ -410,15 +477,16 @@ function readFirstTouchCookie() {
|
|
|
410
477
|
}
|
|
411
478
|
return null;
|
|
412
479
|
}
|
|
413
|
-
function
|
|
414
|
-
return (`${
|
|
480
|
+
function attributionCookieAssignment(cookieName, encodedValue) {
|
|
481
|
+
return (`${cookieName}=${encodedValue}; path=/; ` +
|
|
415
482
|
`max-age=${FIRST_TOUCH_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`);
|
|
416
483
|
}
|
|
417
|
-
function
|
|
484
|
+
function fitAttributionCookieValue(value, spec) {
|
|
418
485
|
const source = JSON.parse(value);
|
|
419
486
|
const compact = {};
|
|
420
487
|
let truncated = false;
|
|
421
|
-
|
|
488
|
+
const fits = (encoded) => attributionCookieAssignment(spec.name, encoded).length <= spec.maxBytes;
|
|
489
|
+
for (const field of spec.priority) {
|
|
422
490
|
const rawValue = source[field];
|
|
423
491
|
if (typeof rawValue !== "string" || !rawValue)
|
|
424
492
|
continue;
|
|
@@ -426,9 +494,8 @@ function fitFirstTouchCookieValue(value) {
|
|
|
426
494
|
...compact,
|
|
427
495
|
[field]: rawValue.slice(0, FIRST_TOUCH_MAX_FIELD_LENGTH),
|
|
428
496
|
};
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
Object.assign(compact, { [field]: candidate[field] });
|
|
497
|
+
if (fits(encodeURIComponent(JSON.stringify(candidate)))) {
|
|
498
|
+
compact[field] = candidate[field];
|
|
432
499
|
}
|
|
433
500
|
else {
|
|
434
501
|
truncated = true;
|
|
@@ -437,38 +504,63 @@ function fitFirstTouchCookieValue(value) {
|
|
|
437
504
|
if (truncated) {
|
|
438
505
|
compact.capture_truncated = "1";
|
|
439
506
|
// Keep the auth handoff under its 4 KB header limit after re-encoding.
|
|
440
|
-
for (const field of [...
|
|
507
|
+
for (const field of [...spec.priority].reverse()) {
|
|
441
508
|
const encoded = encodeURIComponent(JSON.stringify(compact));
|
|
442
|
-
if (
|
|
443
|
-
FIRST_TOUCH_MAX_COOKIE_BYTES) {
|
|
509
|
+
if (fits(encoded))
|
|
444
510
|
return encoded;
|
|
445
|
-
}
|
|
446
511
|
delete compact[field];
|
|
447
512
|
}
|
|
448
513
|
}
|
|
449
514
|
const encoded = encodeURIComponent(JSON.stringify(compact));
|
|
450
|
-
if (
|
|
451
|
-
throw new Error(
|
|
515
|
+
if (!fits(encoded)) {
|
|
516
|
+
throw new Error(`Attribution exceeded the ${spec.name} cookie budget`);
|
|
452
517
|
}
|
|
453
518
|
return encoded;
|
|
454
519
|
}
|
|
455
|
-
function
|
|
520
|
+
function writeAttributionCookie(value, spec) {
|
|
456
521
|
if (typeof document === "undefined")
|
|
457
522
|
return;
|
|
458
|
-
const encodedValue =
|
|
523
|
+
const encodedValue = fitAttributionCookieValue(value, spec);
|
|
459
524
|
try {
|
|
460
|
-
document.cookie =
|
|
525
|
+
document.cookie = attributionCookieAssignment(spec.name, encodedValue);
|
|
461
526
|
}
|
|
462
527
|
catch {
|
|
463
528
|
// best-effort
|
|
464
529
|
}
|
|
465
530
|
}
|
|
531
|
+
function storeAttribution(storageKey, spec, attribution) {
|
|
532
|
+
const json = JSON.stringify(attribution);
|
|
533
|
+
safeStorageSet(storageKey, json);
|
|
534
|
+
writeAttributionCookie(json, spec);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Restore a cookie that expired or was cleared from its stored value, so the
|
|
538
|
+
* signup boundary still sees it.
|
|
539
|
+
*/
|
|
540
|
+
function backfillAttributionCookie(storageKey, spec) {
|
|
541
|
+
if (readAttributionCookie(spec.name))
|
|
542
|
+
return;
|
|
543
|
+
const stored = safeStorageGet(storageKey);
|
|
544
|
+
if (!stored)
|
|
545
|
+
return;
|
|
546
|
+
try {
|
|
547
|
+
writeAttributionCookie(stored, spec);
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
// coercion-ok: localStorage still holds the value; only this page's
|
|
551
|
+
// signup handoff goes without it.
|
|
552
|
+
}
|
|
553
|
+
}
|
|
466
554
|
/**
|
|
467
|
-
* Capture the visitor's
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
555
|
+
* Capture the visitor's referral attribution once per page load, into both
|
|
556
|
+
* `localStorage` and a first-party cookie the signup boundary reads. Fully
|
|
557
|
+
* defensive and SSR-safe — any failure is swallowed so it can never break app
|
|
558
|
+
* boot.
|
|
559
|
+
*
|
|
560
|
+
* First touch (`an_attribution` / `an_ft`) is first-write-wins, except over a
|
|
561
|
+
* visit that had no source: the first visit that says where this person came
|
|
562
|
+
* from replaces it. Without that, an untagged first visit would hide every
|
|
563
|
+
* tagged visit after it.
|
|
472
564
|
*/
|
|
473
565
|
function captureFirstTouchAttribution() {
|
|
474
566
|
if (_firstTouchCaptured)
|
|
@@ -477,35 +569,80 @@ function captureFirstTouchAttribution() {
|
|
|
477
569
|
if (typeof window === "undefined")
|
|
478
570
|
return;
|
|
479
571
|
try {
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
catch {
|
|
490
|
-
// ignore
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return;
|
|
572
|
+
const current = buildFirstTouchAttribution();
|
|
573
|
+
const existing = getFirstTouchAttribution();
|
|
574
|
+
if (existing &&
|
|
575
|
+
(hasAttributionSource(existing) || !hasAttributionSource(current))) {
|
|
576
|
+
backfillAttributionCookie(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE);
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
storeAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE, current);
|
|
494
580
|
}
|
|
495
|
-
|
|
496
|
-
const json = JSON.stringify(attribution);
|
|
497
|
-
safeStorageSet(FIRST_TOUCH_STORAGE_KEY, json);
|
|
498
|
-
writeFirstTouchCookie(json);
|
|
581
|
+
captureLastTouchAttribution(current);
|
|
499
582
|
}
|
|
500
583
|
catch {
|
|
501
584
|
// Attribution is best-effort telemetry; never let it break boot.
|
|
502
585
|
}
|
|
503
586
|
}
|
|
504
|
-
|
|
587
|
+
/**
|
|
588
|
+
* Last touch (`an_last_touch` / `an_lt`) is the latest visit that had a
|
|
589
|
+
* source. When the marketing site forwards its own last touch, that visit is
|
|
590
|
+
* the one to keep, not the site's first touch riding on the same link. A site
|
|
591
|
+
* visit keeps the time it happened, and loses to a newer visit the app has
|
|
592
|
+
* already recorded.
|
|
593
|
+
*/
|
|
594
|
+
function captureLastTouchAttribution(current) {
|
|
595
|
+
const forwarded = readForwardedLastTouch();
|
|
596
|
+
const source = forwarded ?? (hasAttributionSource(current) ? current : null);
|
|
597
|
+
const forwardedAt = readForwardedLastTouchAt();
|
|
598
|
+
const existing = getLastTouchAttribution();
|
|
599
|
+
const existingAt = Date.parse(existing?.touched_at ?? "");
|
|
600
|
+
if (!source ||
|
|
601
|
+
(forwardedAt &&
|
|
602
|
+
hasAttributionSource(existing) &&
|
|
603
|
+
existingAt > Date.parse(forwardedAt))) {
|
|
604
|
+
backfillAttributionCookie(LAST_TOUCH_STORAGE_KEY, LAST_TOUCH_COOKIE);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const lastTouch = {};
|
|
608
|
+
for (const field of LAST_TOUCH_SOURCE_FIELDS) {
|
|
609
|
+
const value = source[field];
|
|
610
|
+
if (value)
|
|
611
|
+
lastTouch[field] = value;
|
|
612
|
+
}
|
|
613
|
+
// As for first touch, `landing_path` is where the visitor entered this app
|
|
614
|
+
// and `site_landing_path` the marketing-site page the touch landed on.
|
|
615
|
+
if (current.landing_path)
|
|
616
|
+
lastTouch.landing_path = current.landing_path;
|
|
617
|
+
if (source.site_landing_path) {
|
|
618
|
+
lastTouch.site_landing_path = source.site_landing_path;
|
|
619
|
+
}
|
|
620
|
+
lastTouch.touched_at = forwardedAt ?? current.landed_at;
|
|
621
|
+
storeAttribution(LAST_TOUCH_STORAGE_KEY, LAST_TOUCH_COOKIE, lastTouch);
|
|
622
|
+
}
|
|
623
|
+
function readForwardedLastTouchAt() {
|
|
624
|
+
const raw = new URLSearchParams(window.location.search).get(FORWARDED_LAST_TOUCH_AT_PARAM);
|
|
625
|
+
const time = Date.parse(raw ?? "");
|
|
626
|
+
// A visit can't be in the future; a bad clock or value means "now".
|
|
627
|
+
if (!Number.isFinite(time) || time > Date.now())
|
|
628
|
+
return undefined;
|
|
629
|
+
return new Date(time).toISOString();
|
|
630
|
+
}
|
|
631
|
+
function readForwardedLastTouch() {
|
|
632
|
+
const params = new URLSearchParams(window.location.search);
|
|
633
|
+
const forwarded = {};
|
|
634
|
+
for (const [param, field] of Object.entries(FORWARDED_LAST_TOUCH_FIELDS)) {
|
|
635
|
+
const value = truncateFirstTouchField(params.get(param));
|
|
636
|
+
if (value)
|
|
637
|
+
forwarded[field] = value;
|
|
638
|
+
}
|
|
639
|
+
return hasAttributionSource(forwarded) ? forwarded : null;
|
|
640
|
+
}
|
|
641
|
+
function readStoredAttribution(storageKey) {
|
|
505
642
|
if (typeof window === "undefined")
|
|
506
643
|
return null;
|
|
507
644
|
try {
|
|
508
|
-
const raw = safeStorageGet(
|
|
645
|
+
const raw = safeStorageGet(storageKey);
|
|
509
646
|
if (!raw)
|
|
510
647
|
return null;
|
|
511
648
|
const parsed = JSON.parse(raw);
|
|
@@ -518,10 +655,16 @@ export function getFirstTouchAttribution() {
|
|
|
518
655
|
return null;
|
|
519
656
|
}
|
|
520
657
|
}
|
|
658
|
+
export function getFirstTouchAttribution() {
|
|
659
|
+
return readStoredAttribution(FIRST_TOUCH_STORAGE_KEY);
|
|
660
|
+
}
|
|
661
|
+
export function getLastTouchAttribution() {
|
|
662
|
+
return readStoredAttribution(LAST_TOUCH_STORAGE_KEY);
|
|
663
|
+
}
|
|
521
664
|
/**
|
|
522
|
-
* Store the visitor's first touch now instead of in
|
|
523
|
-
* for a page that reads
|
|
524
|
-
* load, so tracking's own capture is then a no-op.
|
|
665
|
+
* Store the visitor's first and last touch now instead of in
|
|
666
|
+
* `configureTracking()`, for a page that reads them before tracking starts.
|
|
667
|
+
* It runs once per page load, so tracking's own capture is then a no-op.
|
|
525
668
|
*/
|
|
526
669
|
export function captureAttribution() {
|
|
527
670
|
if (isSyntheticBrowserTraffic())
|
|
@@ -140,13 +140,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
140
140
|
clientHint?: undefined;
|
|
141
141
|
timestamp?: undefined;
|
|
142
142
|
chapterId?: undefined;
|
|
143
|
+
count?: undefined;
|
|
143
144
|
startAt?: undefined;
|
|
144
145
|
endAt?: undefined;
|
|
145
146
|
reason?: undefined;
|
|
146
147
|
includeMicrophone?: undefined;
|
|
147
148
|
includeSystemAudio?: undefined;
|
|
148
149
|
requestId?: undefined;
|
|
149
|
-
count?: undefined;
|
|
150
150
|
};
|
|
151
151
|
required?: undefined;
|
|
152
152
|
};
|
|
@@ -171,13 +171,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
171
171
|
clientHint?: undefined;
|
|
172
172
|
timestamp?: undefined;
|
|
173
173
|
chapterId?: undefined;
|
|
174
|
+
count?: undefined;
|
|
174
175
|
startAt?: undefined;
|
|
175
176
|
endAt?: undefined;
|
|
176
177
|
reason?: undefined;
|
|
177
178
|
includeMicrophone?: undefined;
|
|
178
179
|
includeSystemAudio?: undefined;
|
|
179
180
|
requestId?: undefined;
|
|
180
|
-
count?: undefined;
|
|
181
181
|
};
|
|
182
182
|
required?: undefined;
|
|
183
183
|
};
|
|
@@ -196,13 +196,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
196
196
|
clientHint?: undefined;
|
|
197
197
|
timestamp?: undefined;
|
|
198
198
|
chapterId?: undefined;
|
|
199
|
+
count?: undefined;
|
|
199
200
|
startAt?: undefined;
|
|
200
201
|
endAt?: undefined;
|
|
201
202
|
reason?: undefined;
|
|
202
203
|
includeMicrophone?: undefined;
|
|
203
204
|
includeSystemAudio?: undefined;
|
|
204
205
|
requestId?: undefined;
|
|
205
|
-
count?: undefined;
|
|
206
206
|
};
|
|
207
207
|
required?: undefined;
|
|
208
208
|
};
|
|
@@ -231,13 +231,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
231
231
|
};
|
|
232
232
|
timestamp?: undefined;
|
|
233
233
|
chapterId?: undefined;
|
|
234
|
+
count?: undefined;
|
|
234
235
|
startAt?: undefined;
|
|
235
236
|
endAt?: undefined;
|
|
236
237
|
reason?: undefined;
|
|
237
238
|
includeMicrophone?: undefined;
|
|
238
239
|
includeSystemAudio?: undefined;
|
|
239
240
|
requestId?: undefined;
|
|
240
|
-
count?: undefined;
|
|
241
241
|
};
|
|
242
242
|
};
|
|
243
243
|
} | {
|
|
@@ -260,12 +260,12 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
260
260
|
description: string;
|
|
261
261
|
};
|
|
262
262
|
chapterId?: undefined;
|
|
263
|
+
count?: undefined;
|
|
263
264
|
startAt?: undefined;
|
|
264
265
|
endAt?: undefined;
|
|
265
266
|
includeMicrophone?: undefined;
|
|
266
267
|
includeSystemAudio?: undefined;
|
|
267
268
|
requestId?: undefined;
|
|
268
|
-
count?: undefined;
|
|
269
269
|
};
|
|
270
270
|
};
|
|
271
271
|
} | {
|
|
@@ -317,6 +317,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
317
317
|
clientHint?: undefined;
|
|
318
318
|
timestamp?: undefined;
|
|
319
319
|
chapterId?: undefined;
|
|
320
|
+
count?: undefined;
|
|
320
321
|
startAt: {
|
|
321
322
|
type: string;
|
|
322
323
|
description: string;
|
|
@@ -336,7 +337,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
336
337
|
type: string;
|
|
337
338
|
};
|
|
338
339
|
requestId?: undefined;
|
|
339
|
-
count?: undefined;
|
|
340
340
|
};
|
|
341
341
|
};
|
|
342
342
|
} | {
|
|
@@ -352,6 +352,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
352
352
|
clientHint?: undefined;
|
|
353
353
|
timestamp?: undefined;
|
|
354
354
|
chapterId?: undefined;
|
|
355
|
+
count?: undefined;
|
|
355
356
|
startAt?: undefined;
|
|
356
357
|
endAt?: undefined;
|
|
357
358
|
reason?: undefined;
|
|
@@ -360,7 +361,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
360
361
|
requestId: {
|
|
361
362
|
type: string;
|
|
362
363
|
};
|
|
363
|
-
count?: undefined;
|
|
364
364
|
};
|
|
365
365
|
};
|
|
366
366
|
})[];
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
2
2
|
count: number;
|
|
3
3
|
updated?: undefined;
|
|
4
|
-
ok?: undefined;
|
|
5
4
|
error?: undefined;
|
|
5
|
+
ok?: undefined;
|
|
6
6
|
} | {
|
|
7
7
|
count?: undefined;
|
|
8
8
|
updated: number;
|
|
9
|
-
ok?: undefined;
|
|
10
9
|
error?: undefined;
|
|
10
|
+
ok?: undefined;
|
|
11
11
|
} | {
|
|
12
12
|
count?: undefined;
|
|
13
13
|
updated?: undefined;
|
|
@@ -16,6 +16,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
|
|
|
16
16
|
} | {
|
|
17
17
|
count?: undefined;
|
|
18
18
|
updated?: undefined;
|
|
19
|
-
ok: boolean;
|
|
20
19
|
error?: undefined;
|
|
20
|
+
ok: boolean;
|
|
21
21
|
}>>;
|
|
@@ -27,38 +27,38 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
27
27
|
thumbsUpRate: number;
|
|
28
28
|
avgEvalScore: number;
|
|
29
29
|
} | {
|
|
30
|
+
ok?: undefined;
|
|
30
31
|
error: string;
|
|
31
32
|
message: string;
|
|
32
33
|
summary?: undefined;
|
|
33
34
|
spans?: undefined;
|
|
34
35
|
id?: undefined;
|
|
35
|
-
ok?: undefined;
|
|
36
36
|
} | {
|
|
37
|
+
error?: undefined;
|
|
38
|
+
ok?: undefined;
|
|
37
39
|
message?: undefined;
|
|
38
40
|
summary: import("./types.js").TraceSummary;
|
|
39
41
|
spans: import("./types.js").TraceSpan[];
|
|
40
42
|
id?: undefined;
|
|
43
|
+
} | {
|
|
41
44
|
error?: undefined;
|
|
42
45
|
ok?: undefined;
|
|
43
|
-
} | {
|
|
44
46
|
message?: undefined;
|
|
45
47
|
summary?: undefined;
|
|
46
48
|
spans?: undefined;
|
|
47
49
|
id: string;
|
|
48
|
-
error?: undefined;
|
|
49
|
-
ok?: undefined;
|
|
50
50
|
} | {
|
|
51
|
+
ok?: undefined;
|
|
51
52
|
message?: undefined;
|
|
52
53
|
summary?: undefined;
|
|
53
54
|
spans?: undefined;
|
|
54
55
|
id?: undefined;
|
|
55
56
|
error: any;
|
|
56
|
-
ok?: undefined;
|
|
57
57
|
} | {
|
|
58
|
+
error?: undefined;
|
|
58
59
|
message?: undefined;
|
|
59
60
|
summary?: undefined;
|
|
60
61
|
spans?: undefined;
|
|
61
62
|
id?: undefined;
|
|
62
|
-
error?: undefined;
|
|
63
63
|
ok: boolean;
|
|
64
64
|
}>>;
|
|
@@ -39,8 +39,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
|
|
|
39
39
|
error: string;
|
|
40
40
|
}>;
|
|
41
41
|
export declare function handleDeleteResource(event: any): Promise<{
|
|
42
|
-
error: string;
|
|
43
42
|
ok?: undefined;
|
|
43
|
+
error: string;
|
|
44
44
|
} | {
|
|
45
45
|
error?: undefined;
|
|
46
46
|
ok: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -58,47 +58,47 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
58
58
|
error: string;
|
|
59
59
|
errorCode?: undefined;
|
|
60
60
|
retryable?: undefined;
|
|
61
|
-
status?: undefined;
|
|
62
61
|
ok?: undefined;
|
|
62
|
+
status?: undefined;
|
|
63
63
|
} | {
|
|
64
64
|
error: string;
|
|
65
65
|
errorCode: string;
|
|
66
66
|
retryable?: undefined;
|
|
67
|
-
status?: undefined;
|
|
68
67
|
ok?: undefined;
|
|
68
|
+
status?: undefined;
|
|
69
69
|
} | {
|
|
70
70
|
error: string;
|
|
71
71
|
errorCode: string;
|
|
72
72
|
retryable: boolean;
|
|
73
|
-
status?: undefined;
|
|
74
73
|
ok?: undefined;
|
|
74
|
+
status?: undefined;
|
|
75
75
|
} | {
|
|
76
|
+
error?: undefined;
|
|
76
77
|
errorCode?: undefined;
|
|
77
78
|
retryable?: undefined;
|
|
78
79
|
ok: boolean;
|
|
79
80
|
status: string;
|
|
80
|
-
error?: undefined;
|
|
81
81
|
} | {
|
|
82
|
+
ok?: undefined;
|
|
82
83
|
error: string;
|
|
83
84
|
removed?: undefined;
|
|
84
|
-
ok?: undefined;
|
|
85
85
|
} | {
|
|
86
|
+
error?: undefined;
|
|
86
87
|
ok: boolean;
|
|
87
88
|
removed: boolean;
|
|
88
|
-
error?: undefined;
|
|
89
89
|
}>>;
|
|
90
90
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
91
|
+
ok?: undefined;
|
|
91
92
|
error: string;
|
|
92
93
|
note?: undefined;
|
|
93
|
-
ok?: undefined;
|
|
94
94
|
} | {
|
|
95
|
+
error?: undefined;
|
|
95
96
|
ok: boolean;
|
|
96
97
|
note?: undefined;
|
|
97
|
-
error?: undefined;
|
|
98
98
|
} | {
|
|
99
|
+
error?: undefined;
|
|
99
100
|
ok: boolean;
|
|
100
101
|
note: string;
|
|
101
|
-
error?: undefined;
|
|
102
102
|
} | {
|
|
103
103
|
note?: undefined;
|
|
104
104
|
ok: boolean;
|
|
@@ -126,11 +126,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
|
|
|
126
126
|
} | {
|
|
127
127
|
error: string;
|
|
128
128
|
} | {
|
|
129
|
+
error?: undefined;
|
|
129
130
|
ok: boolean;
|
|
130
131
|
key: string;
|
|
131
|
-
error?: undefined;
|
|
132
132
|
} | {
|
|
133
|
+
error?: undefined;
|
|
133
134
|
ok: boolean;
|
|
134
135
|
removed: boolean;
|
|
135
|
-
error?: undefined;
|
|
136
136
|
}>>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DEFAULT_AGENT_ACCESS_TTL_SECONDS, scopedAgentAccessResourceId, } from "../shared/agent-access.js";
|
|
2
|
-
import {
|
|
2
|
+
import { signCompactShortLivedToken, verifyCompactShortLivedToken, verifyShortLivedToken, } from "./short-lived-token.js";
|
|
3
3
|
export { AGENT_ACCESS_PARAM, DEFAULT_AGENT_ACCESS_TTL_SECONDS, appendAgentAccessParam, buildAgentAccessApiUrl, buildAgentAccessUrl, normalizeAgentAccessBasePath, normalizeAgentAccessOrigin, scopedAgentAccessResourceId, toAgentAccessUrl, } from "../shared/agent-access.js";
|
|
4
4
|
export function signScopedAgentAccessToken({ resourceKind, resourceId, viewerEmail, agentLabel, ttlSeconds = DEFAULT_AGENT_ACCESS_TTL_SECONDS, }) {
|
|
5
|
-
return
|
|
5
|
+
return signCompactShortLivedToken({
|
|
6
6
|
resourceId: scopedAgentAccessResourceId(resourceKind, resourceId),
|
|
7
7
|
viewerEmail,
|
|
8
8
|
agentLabel,
|
|
@@ -12,7 +12,11 @@ export function signScopedAgentAccessToken({ resourceKind, resourceId, viewerEma
|
|
|
12
12
|
export function verifyScopedAgentAccessToken(token, scope) {
|
|
13
13
|
if (!token)
|
|
14
14
|
return { ok: false, reason: "missing" };
|
|
15
|
-
|
|
15
|
+
const resourceId = scopedAgentAccessResourceId(scope.resourceKind, scope.resourceId);
|
|
16
|
+
const compact = verifyCompactShortLivedToken(token, resourceId);
|
|
17
|
+
if (compact.ok || compact.reason !== "bad_signature")
|
|
18
|
+
return compact;
|
|
19
|
+
return verifyShortLivedToken(token, resourceId);
|
|
16
20
|
}
|
|
17
21
|
export function createScopedAgentAccessGrant(options) {
|
|
18
22
|
const ttlSeconds = options.ttlSeconds ?? DEFAULT_AGENT_ACCESS_TTL_SECONDS;
|
|
@@ -16,9 +16,9 @@ export declare function createAgentEngineDisconnectHandler(): import("h3").Event
|
|
|
16
16
|
error: any;
|
|
17
17
|
scope?: undefined;
|
|
18
18
|
} | {
|
|
19
|
+
error?: undefined;
|
|
19
20
|
ok: boolean;
|
|
20
21
|
scope: "org" | "user";
|
|
21
|
-
error?: undefined;
|
|
22
22
|
}>>;
|
|
23
23
|
export interface DefaultModelSelectionRequest {
|
|
24
24
|
engine: string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export declare function createAgentEngineOllamaModelsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
2
2
|
error: string;
|
|
3
|
-
models?: undefined;
|
|
4
3
|
ok?: undefined;
|
|
4
|
+
models?: undefined;
|
|
5
5
|
} | {
|
|
6
|
+
error?: undefined;
|
|
6
7
|
ok: boolean;
|
|
7
8
|
models: string[];
|
|
8
|
-
error?: undefined;
|
|
9
9
|
}>>;
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
* request and enriches the canonical server-side `signup` event so we can
|
|
9
9
|
* measure where new users came from and how apps spread (virality).
|
|
10
10
|
*
|
|
11
|
+
* The browser also keeps the visitor's *last* visit that had a source in an
|
|
12
|
+
* `an_lt` cookie. First touch answers "how did they find us?" and credits
|
|
13
|
+
* acquisition; last touch answers "what brought them to sign up?" and credits
|
|
14
|
+
* the post, video, or person that converted them.
|
|
15
|
+
*
|
|
11
16
|
* This module is intentionally pure and dependency-free so it is trivially
|
|
12
17
|
* unit-testable and can never throw into the signup path. The single hard rule:
|
|
13
18
|
* parsing untrusted cookie input must NEVER throw — every accessor is defensive.
|
|
@@ -36,6 +41,25 @@ export interface FirstTouchAttribution {
|
|
|
36
41
|
landed_at?: string;
|
|
37
42
|
capture_truncated?: string;
|
|
38
43
|
}
|
|
44
|
+
/** The decoded `an_lt` cookie: the latest visit that had a source. */
|
|
45
|
+
export interface LastTouchAttribution {
|
|
46
|
+
ref?: string;
|
|
47
|
+
via?: string;
|
|
48
|
+
utm_source?: string;
|
|
49
|
+
utm_medium?: string;
|
|
50
|
+
utm_campaign?: string;
|
|
51
|
+
utm_content?: string;
|
|
52
|
+
utm_term?: string;
|
|
53
|
+
gclid?: string;
|
|
54
|
+
msclkid?: string;
|
|
55
|
+
vector_source?: string;
|
|
56
|
+
landing_referrer?: string;
|
|
57
|
+
site_referrer?: string;
|
|
58
|
+
site_landing_path?: string;
|
|
59
|
+
landing_path?: string;
|
|
60
|
+
touched_at?: string;
|
|
61
|
+
capture_truncated?: string;
|
|
62
|
+
}
|
|
39
63
|
export type SignupOrigin = "browser_signup"
|
|
40
64
|
/** The framework's Google OAuth callback owns this event. */
|
|
41
65
|
| "google_oauth"
|
|
@@ -47,20 +71,28 @@ export interface SignupAttributionContext {
|
|
|
47
71
|
}
|
|
48
72
|
export declare const SIGNUP_ATTRIBUTION_HEADER_NAME = "x-agent-native-signup-attribution";
|
|
49
73
|
export declare const FIRST_TOUCH_COOKIE_NAME = "an_ft";
|
|
74
|
+
export declare const LAST_TOUCH_COOKIE_NAME = "an_lt";
|
|
50
75
|
/**
|
|
51
76
|
* Parse a raw `Cookie:` header into a flat name→value map. Tolerates missing
|
|
52
77
|
* input, extra whitespace, `=` inside values, and malformed pairs. Never throws.
|
|
53
78
|
*/
|
|
54
79
|
export declare function parseCookieHeader(cookieHeader: string | null | undefined): Record<string, string>;
|
|
55
80
|
export declare function decodeFirstTouchValue(value: string | null | undefined): FirstTouchAttribution | null;
|
|
81
|
+
export declare function decodeLastTouchValue(value: string | null | undefined): LastTouchAttribution | null;
|
|
56
82
|
/**
|
|
57
83
|
* Read the `an_ft` first-touch attribution out of a raw `Cookie:` header.
|
|
58
84
|
* Returns `null` when the cookie is absent or unparseable. Never throws.
|
|
59
85
|
*/
|
|
60
86
|
export declare function readFirstTouchAttribution(cookieHeader: string | null | undefined): FirstTouchAttribution | null;
|
|
87
|
+
/**
|
|
88
|
+
* Read the `an_lt` last-touch attribution out of a raw `Cookie:` header.
|
|
89
|
+
* Returns `null` when the cookie is absent or unparseable. Never throws.
|
|
90
|
+
*/
|
|
91
|
+
export declare function readLastTouchAttribution(cookieHeader: string | null | undefined): LastTouchAttribution | null;
|
|
61
92
|
export declare function readAnalyticsAnonymousId(cookieHeader: string | null | undefined): string | undefined;
|
|
62
|
-
export declare function deriveReferralSource(ft: FirstTouchAttribution | null): string;
|
|
93
|
+
export declare function deriveReferralSource(ft: FirstTouchAttribution | LastTouchAttribution | null): string;
|
|
63
94
|
export declare function deriveSignupAttribution(ft: FirstTouchAttribution | null): Record<string, string>;
|
|
95
|
+
export declare function deriveLastTouchAttribution(lt: LastTouchAttribution | null): Record<string, string>;
|
|
64
96
|
/**
|
|
65
97
|
* Convenience: read the cookie header and derive signup attribution in one
|
|
66
98
|
* call. Never throws; falls back to `{ referral_source: "direct" }` on any
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { ANALYTICS_ANONYMOUS_ID_COOKIE_NAME, normalizeAnalyticsAnonymousId, } from "../shared/analytics-anonymous-id.js";
|
|
1
|
+
import { ANALYTICS_ANONYMOUS_ID_COOKIE_NAME, ANALYTICS_ANONYMOUS_ID_MAX_LENGTH, normalizeAnalyticsAnonymousId, } from "../shared/analytics-anonymous-id.js";
|
|
2
|
+
import { isSourceReferrerHost, shareLandingSource, } from "../shared/attribution-source.js";
|
|
2
3
|
export const SIGNUP_ATTRIBUTION_HEADER_NAME = "x-agent-native-signup-attribution";
|
|
3
4
|
const SIGNUP_ATTRIBUTION_HEADER_MAX_LENGTH = 4096;
|
|
4
5
|
export const FIRST_TOUCH_COOKIE_NAME = "an_ft";
|
|
6
|
+
export const LAST_TOUCH_COOKIE_NAME = "an_lt";
|
|
5
7
|
const STRING_FIELDS = [
|
|
6
8
|
"ref",
|
|
7
9
|
"via",
|
|
@@ -20,6 +22,24 @@ const STRING_FIELDS = [
|
|
|
20
22
|
"landed_at",
|
|
21
23
|
"capture_truncated",
|
|
22
24
|
];
|
|
25
|
+
const LAST_TOUCH_STRING_FIELDS = [
|
|
26
|
+
"ref",
|
|
27
|
+
"via",
|
|
28
|
+
"utm_source",
|
|
29
|
+
"utm_medium",
|
|
30
|
+
"utm_campaign",
|
|
31
|
+
"utm_content",
|
|
32
|
+
"utm_term",
|
|
33
|
+
"gclid",
|
|
34
|
+
"msclkid",
|
|
35
|
+
"vector_source",
|
|
36
|
+
"landing_referrer",
|
|
37
|
+
"site_referrer",
|
|
38
|
+
"site_landing_path",
|
|
39
|
+
"landing_path",
|
|
40
|
+
"touched_at",
|
|
41
|
+
"capture_truncated",
|
|
42
|
+
];
|
|
23
43
|
/**
|
|
24
44
|
* Parse a raw `Cookie:` header into a flat name→value map. Tolerates missing
|
|
25
45
|
* input, extra whitespace, `=` inside values, and malformed pairs. Never throws.
|
|
@@ -44,6 +64,12 @@ export function parseCookieHeader(cookieHeader) {
|
|
|
44
64
|
return out;
|
|
45
65
|
}
|
|
46
66
|
export function decodeFirstTouchValue(value) {
|
|
67
|
+
return decodeAttributionCookieValue(value, STRING_FIELDS);
|
|
68
|
+
}
|
|
69
|
+
export function decodeLastTouchValue(value) {
|
|
70
|
+
return decodeAttributionCookieValue(value, LAST_TOUCH_STRING_FIELDS);
|
|
71
|
+
}
|
|
72
|
+
function decodeAttributionCookieValue(value, fields) {
|
|
47
73
|
if (!value || typeof value !== "string")
|
|
48
74
|
return null;
|
|
49
75
|
let decoded = value;
|
|
@@ -66,7 +92,7 @@ export function decodeFirstTouchValue(value) {
|
|
|
66
92
|
const source = parsed;
|
|
67
93
|
const result = {};
|
|
68
94
|
let any = false;
|
|
69
|
-
for (const field of
|
|
95
|
+
for (const field of fields) {
|
|
70
96
|
const raw = source[field];
|
|
71
97
|
if (typeof raw === "string" && raw.length > 0) {
|
|
72
98
|
result[field] = raw.slice(0, 120);
|
|
@@ -88,6 +114,14 @@ export function readFirstTouchAttribution(cookieHeader) {
|
|
|
88
114
|
return null;
|
|
89
115
|
}
|
|
90
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Read the `an_lt` last-touch attribution out of a raw `Cookie:` header.
|
|
119
|
+
* Returns `null` when the cookie is absent or unparseable. Never throws.
|
|
120
|
+
*/
|
|
121
|
+
export function readLastTouchAttribution(cookieHeader) {
|
|
122
|
+
const cookies = parseCookieHeader(cookieHeader);
|
|
123
|
+
return decodeLastTouchValue(cookies[LAST_TOUCH_COOKIE_NAME]);
|
|
124
|
+
}
|
|
91
125
|
export function readAnalyticsAnonymousId(cookieHeader) {
|
|
92
126
|
try {
|
|
93
127
|
return normalizeAnalyticsAnonymousId(parseCookieHeader(cookieHeader)[ANALYTICS_ANONYMOUS_ID_COOKIE_NAME]);
|
|
@@ -96,25 +130,14 @@ export function readAnalyticsAnonymousId(cookieHeader) {
|
|
|
96
130
|
return undefined;
|
|
97
131
|
}
|
|
98
132
|
}
|
|
99
|
-
function isExternalReferrerHost(host) {
|
|
100
|
-
const trimmed = host?.trim();
|
|
101
|
-
return !!trimmed && trimmed.length > 0;
|
|
102
|
-
}
|
|
103
133
|
export function deriveReferralSource(ft) {
|
|
104
134
|
if (ft?.ref && ft.ref.trim())
|
|
105
135
|
return ft.ref.trim();
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
return
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
path.includes("/plans/") ||
|
|
112
|
-
path.includes("/recaps/") ||
|
|
113
|
-
path.includes("/share-plan/")) {
|
|
114
|
-
return "plan_share";
|
|
115
|
-
}
|
|
116
|
-
if (isExternalReferrerHost(ft?.landing_referrer) ||
|
|
117
|
-
isExternalReferrerHost(ft?.site_referrer)) {
|
|
136
|
+
const shareSource = shareLandingSource(ft?.landing_path);
|
|
137
|
+
if (shareSource)
|
|
138
|
+
return shareSource;
|
|
139
|
+
if (isSourceReferrerHost(ft?.landing_referrer) ||
|
|
140
|
+
isSourceReferrerHost(ft?.site_referrer)) {
|
|
118
141
|
return "external";
|
|
119
142
|
}
|
|
120
143
|
return "direct";
|
|
@@ -149,6 +172,54 @@ export function deriveSignupAttribution(ft) {
|
|
|
149
172
|
out.attribution_truncated = "true";
|
|
150
173
|
return out;
|
|
151
174
|
}
|
|
175
|
+
export function deriveLastTouchAttribution(lt) {
|
|
176
|
+
if (!lt)
|
|
177
|
+
return {};
|
|
178
|
+
const out = {
|
|
179
|
+
last_touch_source: deriveReferralSource(lt),
|
|
180
|
+
};
|
|
181
|
+
const setIf = (key, value) => {
|
|
182
|
+
const trimmed = value?.trim();
|
|
183
|
+
if (trimmed)
|
|
184
|
+
out[key] = trimmed;
|
|
185
|
+
};
|
|
186
|
+
setIf("last_touch_ref", lt.ref);
|
|
187
|
+
setIf("last_touch_via", lt.via);
|
|
188
|
+
setIf("last_touch_utm_source", lt.utm_source);
|
|
189
|
+
setIf("last_touch_utm_medium", lt.utm_medium);
|
|
190
|
+
setIf("last_touch_utm_campaign", lt.utm_campaign);
|
|
191
|
+
setIf("last_touch_utm_content", lt.utm_content);
|
|
192
|
+
setIf("last_touch_utm_term", lt.utm_term);
|
|
193
|
+
setIf("last_touch_gclid", lt.gclid);
|
|
194
|
+
setIf("last_touch_msclkid", lt.msclkid);
|
|
195
|
+
setIf("last_touch_vector_source", lt.vector_source);
|
|
196
|
+
setIf("last_touch_referrer", lt.landing_referrer);
|
|
197
|
+
setIf("last_touch_site_referrer", lt.site_referrer);
|
|
198
|
+
setIf("last_touch_path", lt.landing_path);
|
|
199
|
+
setIf("last_touch_site_path", lt.site_landing_path);
|
|
200
|
+
setIf("last_touch_at", lt.touched_at);
|
|
201
|
+
// The browser dropped fields to fit the cookie.
|
|
202
|
+
if (lt.capture_truncated === "1")
|
|
203
|
+
out.last_touch_truncated = "true";
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Add last touch only while the handoff still fits its header. An oversized
|
|
208
|
+
* header is dropped whole, which would lose first touch with it.
|
|
209
|
+
*/
|
|
210
|
+
function withLastTouchAttribution(attribution, lt) {
|
|
211
|
+
const lastTouch = deriveLastTouchAttribution(lt);
|
|
212
|
+
if (Object.keys(lastTouch).length === 0)
|
|
213
|
+
return attribution;
|
|
214
|
+
const merged = { ...attribution, ...lastTouch };
|
|
215
|
+
const largest = encodeSignupAttributionContext({
|
|
216
|
+
attribution: merged,
|
|
217
|
+
anonymousId: "a".repeat(ANALYTICS_ANONYMOUS_ID_MAX_LENGTH),
|
|
218
|
+
});
|
|
219
|
+
if (largest.length <= SIGNUP_ATTRIBUTION_HEADER_MAX_LENGTH)
|
|
220
|
+
return merged;
|
|
221
|
+
return { ...attribution, last_touch_truncated: "true" };
|
|
222
|
+
}
|
|
152
223
|
/**
|
|
153
224
|
* Convenience: read the cookie header and derive signup attribution in one
|
|
154
225
|
* call. Never throws; falls back to `{ referral_source: "direct" }` on any
|
|
@@ -156,7 +227,7 @@ export function deriveSignupAttribution(ft) {
|
|
|
156
227
|
*/
|
|
157
228
|
export function signupAttributionFromCookieHeader(cookieHeader) {
|
|
158
229
|
try {
|
|
159
|
-
return deriveSignupAttribution(readFirstTouchAttribution(cookieHeader));
|
|
230
|
+
return withLastTouchAttribution(deriveSignupAttribution(readFirstTouchAttribution(cookieHeader)), readLastTouchAttribution(cookieHeader));
|
|
160
231
|
}
|
|
161
232
|
catch {
|
|
162
233
|
return { referral_source: "direct" };
|
|
@@ -176,11 +247,12 @@ export function signupAttributionFromCookieHeader(cookieHeader) {
|
|
|
176
247
|
*/
|
|
177
248
|
export function signupAttributionContextFromCookieHeader(cookieHeader) {
|
|
178
249
|
const firstTouch = readFirstTouchAttribution(cookieHeader);
|
|
250
|
+
const lastTouch = readLastTouchAttribution(cookieHeader);
|
|
179
251
|
const anonymousId = readAnalyticsAnonymousId(cookieHeader);
|
|
180
|
-
if (!firstTouch && !anonymousId)
|
|
252
|
+
if (!firstTouch && !lastTouch && !anonymousId)
|
|
181
253
|
return undefined;
|
|
182
254
|
return {
|
|
183
|
-
attribution: deriveSignupAttribution(firstTouch),
|
|
255
|
+
attribution: withLastTouchAttribution(deriveSignupAttribution(firstTouch), lastTouch),
|
|
184
256
|
...(anonymousId ? { anonymousId } : {}),
|
|
185
257
|
};
|
|
186
258
|
}
|
package/dist/server/auth.js
CHANGED
|
@@ -62,7 +62,7 @@ import { injectAnalyticsIntoHtml } from "./analytics.js";
|
|
|
62
62
|
import { getConfiguredAppBasePath, stripAppBasePath as stripConfiguredAppBasePath, } from "./app-base-path.js";
|
|
63
63
|
import { getAppOriginClientConfigScript } from "./app-origin-config.js";
|
|
64
64
|
import { getAppProductionUrl } from "./app-url.js";
|
|
65
|
-
import { addSignupAttributionHeader, readAnalyticsAnonymousId,
|
|
65
|
+
import { addSignupAttributionHeader, readAnalyticsAnonymousId, signupAttributionContextFromCookieHeader, signupAttributionFromCookieHeader, } from "./attribution.js";
|
|
66
66
|
import { getAuthLoginMode } from "./auth-login-mode.js";
|
|
67
67
|
import { injectBetaOptOutPersistence } from "./beta-opt-out-html.js";
|
|
68
68
|
import { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, getBetterAuthInternalAdapter, getBetterAuthUserIdForEmail, getAuthSecret, getBetterAuth, getBetterAuthSync, isDeployPreview, } from "./better-auth-instance.js";
|
|
@@ -4948,15 +4948,9 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
4948
4948
|
callbackPath = withDesktopMagicLinkFlow(callbackPath, desktopFlow);
|
|
4949
4949
|
}
|
|
4950
4950
|
const callbackURL = betterAuthCallbackURL(callbackPath, true, event);
|
|
4951
|
-
const
|
|
4952
|
-
const
|
|
4953
|
-
|
|
4954
|
-
const hasSignupAttribution = !!readFirstTouchAttribution(cookieHeader) || !!signupAnonymousId;
|
|
4955
|
-
const attributionToken = hasSignupAttribution
|
|
4956
|
-
? encodeMagicLinkSignupAttribution({
|
|
4957
|
-
attribution: signupAttribution,
|
|
4958
|
-
anonymousId: signupAnonymousId,
|
|
4959
|
-
}, getAuthSecret())
|
|
4951
|
+
const signupContext = signupAttributionContextFromCookieHeader(getHeader(event, "cookie") ?? null);
|
|
4952
|
+
const attributionToken = signupContext
|
|
4953
|
+
? encodeMagicLinkSignupAttribution(signupContext, getAuthSecret())
|
|
4960
4954
|
: undefined;
|
|
4961
4955
|
const newUserCallbackUrl = new URL(`${getAppBasePath()}${publicFrameworkPath("/_agent-native/auth/magic-link/new-user")}?return=${encodeURIComponent(callbackPath)}`, getOrigin(event));
|
|
4962
4956
|
if (attributionToken) {
|
|
@@ -2,7 +2,8 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import { normalizeAnalyticsAnonymousId } from "../shared/analytics-anonymous-id.js";
|
|
3
3
|
export const MAGIC_LINK_ATTRIBUTION_PARAM = "signup_attribution";
|
|
4
4
|
const MAGIC_LINK_ATTRIBUTION_TTL_SECONDS = 10 * 60;
|
|
5
|
-
|
|
5
|
+
// A signup carries up to 17 first-touch and 16 last-touch properties.
|
|
6
|
+
const MAX_ATTRIBUTION_FIELDS = 48;
|
|
6
7
|
const MAX_ATTRIBUTION_VALUE_LENGTH = 200;
|
|
7
8
|
function sanitizeAttribution(value) {
|
|
8
9
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -31,6 +31,13 @@ export declare function signShortLivedToken(claims: ShortLivedTokenClaims): stri
|
|
|
31
31
|
* not surface the reason to viewers (it's useful for server-side logs only).
|
|
32
32
|
*/
|
|
33
33
|
export declare function verifyShortLivedToken(token: string, expectedResourceId: string): VerifyResult;
|
|
34
|
+
export declare function signCompactShortLivedToken(claims: ShortLivedTokenClaims): string;
|
|
35
|
+
/**
|
|
36
|
+
* Verify a token produced by {@link signCompactShortLivedToken}. A token for a
|
|
37
|
+
* different resource, and any legacy token, fails as `bad_signature` — the id
|
|
38
|
+
* is not in the payload, so a mismatch is indistinguishable from tampering.
|
|
39
|
+
*/
|
|
40
|
+
export declare function verifyCompactShortLivedToken(token: string, expectedResourceId: string): VerifyResult;
|
|
34
41
|
/** Payload `typ` discriminator for realtime subscribe tokens. */
|
|
35
42
|
export declare const REALTIME_SUBSCRIBE_TOKEN_TYPE = "rt-subscribe";
|
|
36
43
|
export interface RealtimeSubscribeClaims {
|
|
@@ -94,6 +94,53 @@ export function verifyShortLivedToken(token, expectedResourceId) {
|
|
|
94
94
|
agentLabel: claims.agentLabel,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
+
function compactSignature(resourceId, payloadStr) {
|
|
98
|
+
return hmacB64(`agent-access\n${resourceId}\n${payloadStr}`, getSigningKey());
|
|
99
|
+
}
|
|
100
|
+
export function signCompactShortLivedToken(claims) {
|
|
101
|
+
const ttl = claims.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
102
|
+
const payload = {
|
|
103
|
+
e: Math.floor(Date.now() / 1000) + ttl,
|
|
104
|
+
};
|
|
105
|
+
if (claims.viewerEmail)
|
|
106
|
+
payload.v = claims.viewerEmail;
|
|
107
|
+
if (claims.agentLabel)
|
|
108
|
+
payload.l = claims.agentLabel;
|
|
109
|
+
const payloadStr = base64UrlEncode(JSON.stringify(payload));
|
|
110
|
+
return `${payloadStr}.${compactSignature(claims.resourceId, payloadStr)}`;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Verify a token produced by {@link signCompactShortLivedToken}. A token for a
|
|
114
|
+
* different resource, and any legacy token, fails as `bad_signature` — the id
|
|
115
|
+
* is not in the payload, so a mismatch is indistinguishable from tampering.
|
|
116
|
+
*/
|
|
117
|
+
export function verifyCompactShortLivedToken(token, expectedResourceId) {
|
|
118
|
+
if (typeof token !== "string" || !token.includes(".")) {
|
|
119
|
+
return { ok: false, reason: "malformed" };
|
|
120
|
+
}
|
|
121
|
+
const [payloadStr, sig] = token.split(".", 2);
|
|
122
|
+
if (!payloadStr || !sig)
|
|
123
|
+
return { ok: false, reason: "malformed" };
|
|
124
|
+
if (!timingSafeEqualB64(sig, compactSignature(expectedResourceId, payloadStr))) {
|
|
125
|
+
return { ok: false, reason: "bad_signature" };
|
|
126
|
+
}
|
|
127
|
+
let claims;
|
|
128
|
+
try {
|
|
129
|
+
claims = JSON.parse(base64UrlDecode(payloadStr).toString("utf8"));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return { ok: false, reason: "bad_payload" };
|
|
133
|
+
}
|
|
134
|
+
if (typeof claims.e !== "number")
|
|
135
|
+
return { ok: false, reason: "bad_payload" };
|
|
136
|
+
if ((claims.v !== undefined && typeof claims.v !== "string") ||
|
|
137
|
+
(claims.l !== undefined && typeof claims.l !== "string")) {
|
|
138
|
+
return { ok: false, reason: "bad_payload" };
|
|
139
|
+
}
|
|
140
|
+
if (claims.e * 1000 < Date.now())
|
|
141
|
+
return { ok: false, reason: "expired" };
|
|
142
|
+
return { ok: true, viewerEmail: claims.v, agentLabel: claims.l };
|
|
143
|
+
}
|
|
97
144
|
// ── Realtime subscribe tokens ────────────────────────────────────────────────
|
|
98
145
|
//
|
|
99
146
|
// An identity-bearing extension of the same HMAC discipline, used by the hosted
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const ANALYTICS_ANONYMOUS_ID_COOKIE_NAME = "an_aid";
|
|
2
2
|
export declare const ANALYTICS_ANONYMOUS_ID_COOKIE_MAX_AGE_SECONDS: number;
|
|
3
|
+
export declare const ANALYTICS_ANONYMOUS_ID_MAX_LENGTH = 128;
|
|
3
4
|
export declare function normalizeAnalyticsAnonymousId(value: unknown): string | undefined;
|
|
4
5
|
export declare function serializeAnalyticsAnonymousIdCookie(value: unknown): string | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const ANALYTICS_ANONYMOUS_ID_COOKIE_NAME = "an_aid";
|
|
2
2
|
export const ANALYTICS_ANONYMOUS_ID_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
|
|
3
|
-
const ANALYTICS_ANONYMOUS_ID_MAX_LENGTH = 128;
|
|
3
|
+
export const ANALYTICS_ANONYMOUS_ID_MAX_LENGTH = 128;
|
|
4
4
|
const ANALYTICS_ANONYMOUS_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
5
5
|
export function normalizeAnalyticsAnonymousId(value) {
|
|
6
6
|
if (typeof value !== "string")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a visit says where the person came from. The browser uses this to
|
|
3
|
+
* decide which visits to keep as first and last touch, and the server uses the
|
|
4
|
+
* same share-link rules to derive `referral_source`, so the two can't drift.
|
|
5
|
+
*/
|
|
6
|
+
export interface AttributionTouch {
|
|
7
|
+
ref?: string;
|
|
8
|
+
via?: string;
|
|
9
|
+
utm_source?: string;
|
|
10
|
+
utm_medium?: string;
|
|
11
|
+
utm_campaign?: string;
|
|
12
|
+
utm_content?: string;
|
|
13
|
+
utm_term?: string;
|
|
14
|
+
gclid?: string;
|
|
15
|
+
msclkid?: string;
|
|
16
|
+
vector_source?: string;
|
|
17
|
+
landing_path?: string;
|
|
18
|
+
landing_referrer?: string;
|
|
19
|
+
site_referrer?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function shareLandingSource(path: string | undefined): "clip_share" | "plan_share" | undefined;
|
|
22
|
+
export declare function isFirstPartyHost(host: string | undefined): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether a referrer host says where someone came from. Moving between our own
|
|
25
|
+
* apps and the marketing site, a local dev server, or returning from Google
|
|
26
|
+
* sign-in is navigation, not a source. The browser and the server both use
|
|
27
|
+
* this, so capture and `referral_source` agree.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isSourceReferrerHost(host: string | undefined): boolean;
|
|
30
|
+
export declare function hasAttributionSource(touch: AttributionTouch | null | undefined): boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const TAG_FIELDS = [
|
|
2
|
+
"ref",
|
|
3
|
+
"via",
|
|
4
|
+
"utm_source",
|
|
5
|
+
"utm_medium",
|
|
6
|
+
"utm_campaign",
|
|
7
|
+
"utm_content",
|
|
8
|
+
"utm_term",
|
|
9
|
+
"gclid",
|
|
10
|
+
"msclkid",
|
|
11
|
+
"vector_source",
|
|
12
|
+
];
|
|
13
|
+
const PLAN_SHARE_PATH_SEGMENTS = [
|
|
14
|
+
"/p/",
|
|
15
|
+
"/plan/",
|
|
16
|
+
"/plans/",
|
|
17
|
+
"/recaps/",
|
|
18
|
+
"/share-plan/",
|
|
19
|
+
];
|
|
20
|
+
export function shareLandingSource(path) {
|
|
21
|
+
if (!path)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (path.startsWith("/share/"))
|
|
24
|
+
return "clip_share";
|
|
25
|
+
if (PLAN_SHARE_PATH_SEGMENTS.some((segment) => path.includes(segment))) {
|
|
26
|
+
return "plan_share";
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
export function isFirstPartyHost(host) {
|
|
31
|
+
const normalized = host?.trim().toLowerCase().replace(/\.$/, "") ?? "";
|
|
32
|
+
return (normalized === "agent-native.com" ||
|
|
33
|
+
normalized.endsWith(".agent-native.com"));
|
|
34
|
+
}
|
|
35
|
+
// Coming back from Google sign-in sends this referrer. It's a step in signing
|
|
36
|
+
// in, never where someone heard about us.
|
|
37
|
+
const SIGN_IN_HOSTS = new Set(["accounts.google.com"]);
|
|
38
|
+
// A developer's own machine, with or without a port.
|
|
39
|
+
const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "0.0.0.0"]);
|
|
40
|
+
/**
|
|
41
|
+
* Whether a referrer host says where someone came from. Moving between our own
|
|
42
|
+
* apps and the marketing site, a local dev server, or returning from Google
|
|
43
|
+
* sign-in is navigation, not a source. The browser and the server both use
|
|
44
|
+
* this, so capture and `referral_source` agree.
|
|
45
|
+
*/
|
|
46
|
+
export function isSourceReferrerHost(host) {
|
|
47
|
+
const normalized = host?.trim().toLowerCase().replace(/\.$/, "") ?? "";
|
|
48
|
+
if (!normalized)
|
|
49
|
+
return false;
|
|
50
|
+
const hostname = normalized.startsWith("[")
|
|
51
|
+
? normalized.slice(0, normalized.indexOf("]") + 1)
|
|
52
|
+
: normalized.split(":")[0];
|
|
53
|
+
return (!isFirstPartyHost(hostname) &&
|
|
54
|
+
!LOCAL_HOSTS.has(hostname) &&
|
|
55
|
+
!SIGN_IN_HOSTS.has(hostname));
|
|
56
|
+
}
|
|
57
|
+
export function hasAttributionSource(touch) {
|
|
58
|
+
if (!touch)
|
|
59
|
+
return false;
|
|
60
|
+
if (TAG_FIELDS.some((field) => !!touch[field]?.trim()))
|
|
61
|
+
return true;
|
|
62
|
+
if (shareLandingSource(touch.landing_path))
|
|
63
|
+
return true;
|
|
64
|
+
return [touch.landing_referrer, touch.site_referrer].some(isSourceReferrerHost);
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.201.
|
|
3
|
+
"version": "0.201.2-nightly-20261006010750",
|
|
4
4
|
"description": "The agentic application framework for building autonomous agents with intuitive UIs",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -671,7 +671,7 @@
|
|
|
671
671
|
"yaml": "^2.9.0",
|
|
672
672
|
"yjs": "^13.6.33",
|
|
673
673
|
"zod": "^4.3.6",
|
|
674
|
-
"@agent-native/agentkit": "^0.201.
|
|
674
|
+
"@agent-native/agentkit": "^0.201.2-nightly-20261006010750",
|
|
675
675
|
"@agent-native/recap-cli": "^0.5.62"
|
|
676
676
|
},
|
|
677
677
|
"devDependencies": {
|
|
@@ -742,7 +742,7 @@
|
|
|
742
742
|
"xlsx": "^0.18.5"
|
|
743
743
|
},
|
|
744
744
|
"peerDependencies": {
|
|
745
|
-
"@agent-native/core-corpus": "
|
|
745
|
+
"@agent-native/core-corpus": "0.201.2-nightly-20261006010750",
|
|
746
746
|
"@ai-sdk/anthropic": ">=3",
|
|
747
747
|
"@ai-sdk/cohere": ">=3",
|
|
748
748
|
"@ai-sdk/google": ">=3",
|