@agent-native/core 0.214.0 → 0.216.0
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/action.js +54 -1
- package/dist/agent/actions/disconnect-chatgpt-subscription.js +5 -0
- package/dist/agent/actions/get-agentkit-capabilities.js +3 -3
- package/dist/agent/actions/manage-builder-connection.js +5 -0
- package/dist/agent/actions/manage-provider-key-policy.js +5 -0
- package/dist/agent/actions/manage-provider-models.js +5 -0
- package/dist/agent/actions/manage-service-providers.js +5 -0
- package/dist/agent/actions/select-chatgpt-subscription-account.js +5 -0
- package/dist/agent/context-xray/actions/context-evict.js +5 -0
- package/dist/agent/context-xray/actions/context-pin.js +5 -0
- package/dist/agent/context-xray/actions/context-report.js +5 -0
- package/dist/agent/context-xray/actions/context-restore.js +5 -0
- package/dist/agent/execution-fence.d.ts +1 -0
- package/dist/agent/execution-fence.js +5 -0
- package/dist/agent/production-agent.d.ts +35 -2
- package/dist/agent/production-agent.js +629 -32
- package/dist/agent/run-manager.d.ts +2 -0
- package/dist/agent/run-manager.js +397 -115
- package/dist/agent/run-slot-lock.d.ts +2 -0
- package/dist/agent/run-slot-lock.js +6 -0
- package/dist/agent/run-store.d.ts +5 -0
- package/dist/agent/run-store.js +423 -156
- package/dist/agent/thread-data-builder.js +27 -5
- package/dist/agent/types.d.ts +6 -12
- package/dist/agentkit/capabilities.d.ts +1 -1
- package/dist/application-state/store.d.ts +5 -0
- package/dist/application-state/store.js +26 -0
- package/dist/chat-threads/schema-migrations.js +17 -0
- package/dist/chat-threads/store.d.ts +21 -1
- package/dist/chat-threads/store.js +223 -41
- package/dist/cli/skills-content/turn-into-app-skill.d.ts +1 -1
- package/dist/cli/skills-content/turn-into-app-skill.js +20 -18
- package/dist/cli/templates-meta.js +14 -0
- package/dist/client/analytics.js +167 -17
- package/dist/client/chat/agentkit-agent-native.js +79 -3
- package/dist/client/chat/agentkit-protocol.js +80 -42
- package/dist/client/chat/runtime.d.ts +5 -1
- package/dist/client/chat/runtime.js +46 -3
- package/dist/client/consent.d.ts +36 -0
- package/dist/client/consent.js +283 -0
- package/dist/client/review/use-review.d.ts +2 -0
- package/dist/client/review/use-review.js +28 -1
- package/dist/client/sharing/useResourceAccessGate.d.ts +3 -2
- package/dist/client/sharing/useResourceAccessGate.js +9 -1
- package/dist/client/use-chat-thread-list.d.ts +2 -0
- package/dist/client/use-chat-thread-list.js +12 -2
- package/dist/client/use-chat-threads.d.ts +1 -0
- package/dist/client/use-chat-threads.js +2 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +5 -5
- package/dist/connections/catalog.js +22 -3
- package/dist/data-widgets/index.d.ts +36 -2
- package/dist/data-widgets/index.js +94 -6
- package/dist/db/migrations.js +4 -1
- package/dist/db/schema-snapshot.generated.js +8 -1
- package/dist/feature-flags/actions/set-feature-flag.js +5 -0
- package/dist/feature-flags/index.d.ts +1 -1
- package/dist/feature-flags/index.js +1 -1
- package/dist/feature-flags/store.d.ts +12 -0
- package/dist/feature-flags/store.js +79 -7
- package/dist/file-upload/actions/manage-file-storage.js +5 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.js +5 -0
- package/dist/history/actions/create-resource-version.js +5 -0
- package/dist/history/actions/restore-resource-version.js +5 -0
- package/dist/ingestion/index.d.ts +1 -1
- package/dist/ingestion/index.js +1 -1
- package/dist/ingestion/pptx.d.ts +11 -0
- package/dist/ingestion/pptx.js +81 -1
- package/dist/integrations/actions/manage-messaging-channel.js +5 -0
- package/dist/jobs/scheduler.js +17 -1
- package/dist/labs/actions/set-lab.js +5 -0
- package/dist/localization/actions/set-localization-preference.js +5 -0
- package/dist/observability/actions/save-observability-instruction-update.js +5 -0
- package/dist/observability/actions/save-observability-review-summary.js +5 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +18 -18
- package/dist/provider-api/actions/provider-api.d.ts +14 -14
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/provider-api/index.js +9 -2
- package/dist/review/actions/consume-review-feedback.js +5 -0
- package/dist/review/actions/create-review-comment.js +5 -0
- package/dist/review/actions/delete-review-comment.js +5 -0
- package/dist/review/actions/react-to-review-comment.js +5 -0
- package/dist/review/actions/reply-review-comment.js +5 -0
- package/dist/review/actions/resolve-review-thread.js +5 -0
- package/dist/review/actions/send-review-thread-to-agent.js +5 -0
- package/dist/review/actions/set-review-status.js +5 -0
- package/dist/review/actions/set-review-thread-muted.js +5 -0
- package/dist/review/actions/set-review-thread-unread.js +5 -0
- package/dist/review/actions/set-review-threads-unread.js +5 -0
- package/dist/review/actions/update-review-comment-anchor.js +5 -0
- package/dist/review/actions/update-review-comment.js +5 -0
- package/dist/review/suggestions/actions.js +60 -21
- package/dist/review/suggestions/store.d.ts +2 -0
- package/dist/review/suggestions/store.js +28 -0
- package/dist/secrets/actions/delete-api-key.js +5 -0
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat/context-tools.js +63 -2
- package/dist/server/agent-chat-plugin.d.ts +2 -0
- package/dist/server/agent-chat-plugin.js +242 -54
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +2 -2
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/application-state-maintenance.d.ts +2 -0
- package/dist/server/application-state-maintenance.js +2 -0
- package/dist/server/attribution.d.ts +14 -0
- package/dist/server/attribution.js +62 -2
- package/dist/server/auth.js +7 -0
- package/dist/server/consent-route.d.ts +41 -0
- package/dist/server/consent-route.js +102 -0
- package/dist/server/first-touch-route.d.ts +39 -0
- package/dist/server/first-touch-route.js +115 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/queued-submission-inbox-drain.d.ts +9 -0
- package/dist/server/queued-submission-inbox-drain.js +12 -0
- package/dist/server/request-origin.d.ts +6 -0
- package/dist/server/request-origin.js +12 -0
- package/dist/settings/org-settings.d.ts +2 -1
- package/dist/settings/org-settings.js +4 -1
- package/dist/settings/store.d.ts +8 -0
- package/dist/settings/store.js +14 -5
- package/dist/shared/consent.d.ts +31 -0
- package/dist/shared/consent.js +145 -0
- package/dist/shared/first-touch-persistence.d.ts +31 -0
- package/dist/shared/first-touch-persistence.js +53 -0
- package/dist/sharing/actions/set-resource-visibility.js +5 -0
- package/dist/sharing/actions/share-resource.js +5 -0
- package/dist/sharing/actions/unshare-resource.js +5 -0
- package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
- package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
- package/dist/triggers/actions/manage-automation.d.ts +1 -1
- package/dist/usage/actions/get-usage-metrics.js +27 -4
- package/dist/usage/actions/manage-usage-alert.js +5 -0
- package/dist/vite/client.js +8 -6
- package/dist/workspace-connections/actions/bulk-update-workspace-user-groups.js +5 -0
- package/dist/workspace-connections/actions/delete-workspace-user-group.js +5 -0
- package/dist/workspace-connections/actions/upsert-workspace-user-group.js +5 -0
- package/docs/content/actions-access-control.mdx +8 -8
- package/docs/content/actions-defining.mdx +9 -8
- package/docs/content/template-design-developers.mdx +9 -9
- package/docs/content/template-design-features.mdx +1 -3
- package/docs/content/turn-into-app.mdx +4 -0
- package/package.json +4 -3
- package/src/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
- package/src/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* acquisition; last touch answers "what brought them to sign up?" and credits
|
|
14
14
|
* the post, video, or person that converted them.
|
|
15
15
|
*
|
|
16
|
+
* First touch also has a durable copy, `an_fts`, that our server sets for a
|
|
17
|
+
* year (see `first-touch-route.ts`). Signup reads both first-touch cookies and
|
|
18
|
+
* keeps the one `preferFirstTouch` picks.
|
|
19
|
+
*
|
|
16
20
|
* This module is intentionally pure and dependency-free so it is trivially
|
|
17
21
|
* unit-testable and can never throw into the signup path. The single hard rule:
|
|
18
22
|
* parsing untrusted cookie input must NEVER throw — every accessor is defensive.
|
|
@@ -80,11 +84,21 @@ export declare const LAST_TOUCH_COOKIE_NAME = "an_lt";
|
|
|
80
84
|
export declare function parseCookieHeader(cookieHeader: string | null | undefined): Record<string, string>;
|
|
81
85
|
export declare function decodeFirstTouchValue(value: string | null | undefined): FirstTouchAttribution | null;
|
|
82
86
|
export declare function decodeLastTouchValue(value: string | null | undefined): LastTouchAttribution | null;
|
|
87
|
+
/**
|
|
88
|
+
* Decode the year-long `an_fts` cookie. A value from another format version
|
|
89
|
+
* is unreadable here, so it holds no touch.
|
|
90
|
+
*/
|
|
91
|
+
export declare function decodeFirstTouchServerValue(value: string | null | undefined): FirstTouchAttribution | null;
|
|
83
92
|
/**
|
|
84
93
|
* Read the `an_ft` first-touch attribution out of a raw `Cookie:` header.
|
|
85
94
|
* Returns `null` when the cookie is absent or unparseable. Never throws.
|
|
86
95
|
*/
|
|
87
96
|
export declare function readFirstTouchAttribution(cookieHeader: string | null | undefined): FirstTouchAttribution | null;
|
|
97
|
+
/**
|
|
98
|
+
* The `an_fts` cookie value for a first touch, dropping the lowest-priority
|
|
99
|
+
* fields until `name=value` fits its budget.
|
|
100
|
+
*/
|
|
101
|
+
export declare function encodeFirstTouchServerCookieValue(touch: FirstTouchAttribution): string;
|
|
88
102
|
/**
|
|
89
103
|
* Read the `an_lt` last-touch attribution out of a raw `Cookie:` header.
|
|
90
104
|
* Returns `null` when the cookie is absent or unparseable. Never throws.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ANALYTICS_ANONYMOUS_ID_COOKIE_NAME, ANALYTICS_ANONYMOUS_ID_MAX_LENGTH, normalizeAnalyticsAnonymousId, } from "../shared/analytics-anonymous-id.js";
|
|
2
2
|
import { ANALYTICS_SESSION_ID_COOKIE_NAME, normalizeAnalyticsSessionId, } from "../shared/analytics-session-id.js";
|
|
3
3
|
import { isSourceReferrerHost, shareLandingSource, } from "../shared/attribution-source.js";
|
|
4
|
+
import { FIRST_TOUCH_SERVER_COOKIE_MAX_BYTES, FIRST_TOUCH_SERVER_COOKIE_NAME, FIRST_TOUCH_SERVER_COOKIE_VERSION, preferFirstTouch, } from "../shared/first-touch-persistence.js";
|
|
4
5
|
export const SIGNUP_ATTRIBUTION_HEADER_NAME = "x-agent-native-signup-attribution";
|
|
5
6
|
const SIGNUP_ATTRIBUTION_HEADER_MAX_LENGTH = 4096;
|
|
6
7
|
export const FIRST_TOUCH_COOKIE_NAME = "an_ft";
|
|
@@ -70,7 +71,14 @@ export function decodeFirstTouchValue(value) {
|
|
|
70
71
|
export function decodeLastTouchValue(value) {
|
|
71
72
|
return decodeAttributionCookieValue(value, LAST_TOUCH_STRING_FIELDS);
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Decode the year-long `an_fts` cookie. A value from another format version
|
|
76
|
+
* is unreadable here, so it holds no touch.
|
|
77
|
+
*/
|
|
78
|
+
export function decodeFirstTouchServerValue(value) {
|
|
79
|
+
return decodeAttributionCookieValue(value, STRING_FIELDS, FIRST_TOUCH_SERVER_COOKIE_VERSION);
|
|
80
|
+
}
|
|
81
|
+
function decodeAttributionCookieValue(value, fields, version) {
|
|
74
82
|
if (!value || typeof value !== "string")
|
|
75
83
|
return null;
|
|
76
84
|
let decoded = value;
|
|
@@ -91,6 +99,8 @@ function decodeAttributionCookieValue(value, fields) {
|
|
|
91
99
|
return null;
|
|
92
100
|
}
|
|
93
101
|
const source = parsed;
|
|
102
|
+
if (version !== undefined && source.v !== version)
|
|
103
|
+
return null;
|
|
94
104
|
const result = {};
|
|
95
105
|
let any = false;
|
|
96
106
|
for (const field of fields) {
|
|
@@ -109,12 +119,62 @@ function decodeAttributionCookieValue(value, fields) {
|
|
|
109
119
|
export function readFirstTouchAttribution(cookieHeader) {
|
|
110
120
|
try {
|
|
111
121
|
const cookies = parseCookieHeader(cookieHeader);
|
|
112
|
-
return decodeFirstTouchValue(cookies[FIRST_TOUCH_COOKIE_NAME]);
|
|
122
|
+
return preferFirstTouch(decodeFirstTouchServerValue(cookies[FIRST_TOUCH_SERVER_COOKIE_NAME]), decodeFirstTouchValue(cookies[FIRST_TOUCH_COOKIE_NAME]));
|
|
113
123
|
}
|
|
114
124
|
catch {
|
|
115
125
|
return null;
|
|
116
126
|
}
|
|
117
127
|
}
|
|
128
|
+
// `landed_at` sets the cookie's expiry and identifies the visit, so it is
|
|
129
|
+
// never the field dropped to fit.
|
|
130
|
+
const FIRST_TOUCH_SERVER_FIELD_PRIORITY = [
|
|
131
|
+
"landed_at",
|
|
132
|
+
"gclid",
|
|
133
|
+
"msclkid",
|
|
134
|
+
"vector_source",
|
|
135
|
+
"utm_source",
|
|
136
|
+
"utm_medium",
|
|
137
|
+
"utm_campaign",
|
|
138
|
+
"ref",
|
|
139
|
+
"via",
|
|
140
|
+
"utm_content",
|
|
141
|
+
"utm_term",
|
|
142
|
+
"landing_path",
|
|
143
|
+
"landing_referrer",
|
|
144
|
+
"site_referrer",
|
|
145
|
+
"site_landing_path",
|
|
146
|
+
];
|
|
147
|
+
/**
|
|
148
|
+
* The `an_fts` cookie value for a first touch, dropping the lowest-priority
|
|
149
|
+
* fields until `name=value` fits its budget.
|
|
150
|
+
*/
|
|
151
|
+
export function encodeFirstTouchServerCookieValue(touch) {
|
|
152
|
+
const compact = {
|
|
153
|
+
v: FIRST_TOUCH_SERVER_COOKIE_VERSION,
|
|
154
|
+
};
|
|
155
|
+
let truncated = touch.capture_truncated === "1";
|
|
156
|
+
const encode = (value) => encodeURIComponent(JSON.stringify(value));
|
|
157
|
+
const fits = (value) => FIRST_TOUCH_SERVER_COOKIE_NAME.length + 1 + encode(value).length <=
|
|
158
|
+
FIRST_TOUCH_SERVER_COOKIE_MAX_BYTES;
|
|
159
|
+
// Room for the marker, so adding it later can't push the value over.
|
|
160
|
+
const withMarker = (value) => ({
|
|
161
|
+
...value,
|
|
162
|
+
capture_truncated: "1",
|
|
163
|
+
});
|
|
164
|
+
for (const field of FIRST_TOUCH_SERVER_FIELD_PRIORITY) {
|
|
165
|
+
const value = touch[field];
|
|
166
|
+
if (!value)
|
|
167
|
+
continue;
|
|
168
|
+
const candidate = { ...compact, [field]: value };
|
|
169
|
+
if (fits(withMarker(candidate))) {
|
|
170
|
+
compact[field] = value;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
truncated = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return encode(truncated ? withMarker(compact) : compact);
|
|
177
|
+
}
|
|
118
178
|
/**
|
|
119
179
|
* Read the `an_lt` last-touch attribution out of a raw `Cookie:` header.
|
|
120
180
|
* Returns `null` when the cookie is absent or unparseable. Never throws.
|
package/dist/server/auth.js
CHANGED
|
@@ -5,14 +5,18 @@ import { acceptPendingInvitationsForEmail } from "../org/accept-pending.js";
|
|
|
5
5
|
import { newOrgSelection, ORG_SELECTION_COOKIE, } from "../org/request-org-cache.js";
|
|
6
6
|
import { isWorkspaceAppAccessUnavailable, workspaceAppAccessUnavailableBody, } from "../org/workspace-app-access-outcome.js";
|
|
7
7
|
import { checkWorkspaceAppAccessForRequest } from "../org/workspace-app-access-request.js";
|
|
8
|
+
import { CONSENT_PATH, CONSENT_REGION_PATH } from "../shared/consent.js";
|
|
8
9
|
import { EMBED_SESSION_COOKIE, EMBED_START_PATH, EMBED_TARGET_HEADER, isMcpDirectoryWidgetCapabilityScope, } from "../shared/embed-auth.js";
|
|
9
10
|
import { FIRST_RUN_ONBOARDING_COOKIE, FIRST_RUN_ONBOARDING_MAX_AGE, } from "../shared/first-run-onboarding.js";
|
|
11
|
+
import { FIRST_TOUCH_SERVER_PATH } from "../shared/first-touch-persistence.js";
|
|
10
12
|
import { isGoogleProfileImageUrl } from "../shared/google-profile-image.js";
|
|
11
13
|
import { EMBED_TRANSPLANT_HEADER, isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
|
|
14
|
+
import { createConsentHandler, createConsentRegionHandler, } from "./consent-route.js";
|
|
12
15
|
import { readDevActionDiscoveryFile } from "./dev-action-discovery.js";
|
|
13
16
|
import { devLoopbackAuthHint } from "./dev-origin-hint.js";
|
|
14
17
|
import { EMAIL_AUTH_LINK_LANDING_PATH, emailAuthLinkFields, emailAuthLinkLandingPage, emailAuthVerificationPath, emailAuthVerificationUrl, } from "./email-auth-links.js";
|
|
15
18
|
import { isEmbedCapabilityScope, revokeEmbedSessionsForOwners, requestHasEmbedAuthMarker, resolveEmbedSessionCookieOwners, resolveEmbedSessionFromRequest, verifyEmbedSessionToken, } from "./embed-session.js";
|
|
19
|
+
import { createFirstTouchHandler } from "./first-touch-route.js";
|
|
16
20
|
import { getPublicFrameworkPathname } from "./framework-request-context.js";
|
|
17
21
|
import { FRAMEWORK_INTERNAL_ROUTE_PREFIX, canonicalFrameworkPathname, getFrameworkRoutePrefix, publicFrameworkPath, } from "./framework-route-prefix.js";
|
|
18
22
|
import { isHttpsRequest } from "./https-request.js";
|
|
@@ -5547,6 +5551,9 @@ export async function autoMountAuth(app, options = {}) {
|
|
|
5547
5551
|
const workspaceAppAudience = resolveWorkspaceAppAudience(options);
|
|
5548
5552
|
const workspaceAppRouteAccess = resolveWorkspaceAppRouteAccess(options);
|
|
5549
5553
|
mountAuthCorsMiddleware(app);
|
|
5554
|
+
app.use(FIRST_TOUCH_SERVER_PATH, createFirstTouchHandler());
|
|
5555
|
+
app.use(CONSENT_REGION_PATH, createConsentRegionHandler());
|
|
5556
|
+
app.use(CONSENT_PATH, createConsentHandler());
|
|
5550
5557
|
if (options.getSession) {
|
|
5551
5558
|
customGetSession = options.getSession;
|
|
5552
5559
|
trustCustomEmailVerification =
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { H3Event } from "h3";
|
|
2
|
+
import { type ConsentCategory } from "../shared/consent.js";
|
|
3
|
+
/**
|
|
4
|
+
* The visitor's country from the host's geo header, if it sent one. The
|
|
5
|
+
* headers a CDN always overwrites come first, so a visitor's own `x-country`
|
|
6
|
+
* can't outrank them.
|
|
7
|
+
*/
|
|
8
|
+
export declare function requestCountry(event: H3Event): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* A stored choice applies everywhere; without one, the country decides
|
|
11
|
+
* whether storing `category` cookies requires an opt-in.
|
|
12
|
+
*/
|
|
13
|
+
export declare function requestAllowsConsentCategory(event: H3Event, category: ConsentCategory): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* `GET /_agent-native/auth/consent-region`: `consent` when the visitor's
|
|
16
|
+
* country gets the banner, `none` when it doesn't, `unknown` when the host
|
|
17
|
+
* sent no country. The SSR shell is cached for everyone, so the browser asks
|
|
18
|
+
* here instead of reading it from the page.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createConsentRegionHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, {
|
|
21
|
+
error: string;
|
|
22
|
+
region?: undefined;
|
|
23
|
+
} | {
|
|
24
|
+
error?: undefined;
|
|
25
|
+
region: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* `POST /_agent-native/auth/consent` with `{ choice: "1.p1.t0" }`: write the
|
|
29
|
+
* visitor's choice from the server. Safari keeps a cookie page JavaScript
|
|
30
|
+
* wrote for seven days at most, and builder.io asks again only after a year.
|
|
31
|
+
* Uses the same domain as the browser's copy, so it replaces that copy.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createConsentHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
34
|
+
error: string;
|
|
35
|
+
stored?: undefined;
|
|
36
|
+
choice?: undefined;
|
|
37
|
+
} | {
|
|
38
|
+
error?: undefined;
|
|
39
|
+
stored: boolean;
|
|
40
|
+
choice: any;
|
|
41
|
+
}>>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { appendResponseHeader, defineEventHandler, getHeader, getMethod, setResponseHeader, setResponseStatus, } from "h3";
|
|
2
|
+
import { CONSENT_COOKIE_LIFETIME_SECONDS, CONSENT_COOKIE_NAME, consentCookieDomain, isConsentRegionCountry, parseConsent, serializeConsent, } from "../shared/consent.js";
|
|
3
|
+
import { parseCookieHeader } from "./attribution.js";
|
|
4
|
+
import { readBodyWithSizeLimit } from "./h3-helpers.js";
|
|
5
|
+
import { getForwardedRequestOrigin, getForwardedRequestURL, isSameOriginPost, } from "./request-origin.js";
|
|
6
|
+
const MAX_CONSENT_BODY_BYTES = 256;
|
|
7
|
+
/**
|
|
8
|
+
* The visitor's country from the host's geo header, if it sent one. The
|
|
9
|
+
* headers a CDN always overwrites come first, so a visitor's own `x-country`
|
|
10
|
+
* can't outrank them.
|
|
11
|
+
*/
|
|
12
|
+
export function requestCountry(event) {
|
|
13
|
+
return (netlifyGeoCountry(event) ??
|
|
14
|
+
getHeader(event, "cf-ipcountry") ??
|
|
15
|
+
getHeader(event, "x-vercel-ip-country") ??
|
|
16
|
+
getHeader(event, "x-country"));
|
|
17
|
+
}
|
|
18
|
+
function netlifyGeoCountry(event) {
|
|
19
|
+
const geo = getHeader(event, "x-nf-geo");
|
|
20
|
+
if (!geo)
|
|
21
|
+
return undefined;
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(Buffer.from(geo, "base64").toString("utf8"));
|
|
24
|
+
const code = parsed?.country?.code;
|
|
25
|
+
return typeof code === "string" ? code : undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// coercion-ok: an unreadable geo header is an unknown country; the next
|
|
29
|
+
// header, or the browser's time zone, decides.
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A stored choice applies everywhere; without one, the country decides
|
|
35
|
+
* whether storing `category` cookies requires an opt-in.
|
|
36
|
+
*/
|
|
37
|
+
export function requestAllowsConsentCategory(event, category) {
|
|
38
|
+
const consent = parseConsent(parseCookieHeader(getHeader(event, "cookie"))[CONSENT_COOKIE_NAME]);
|
|
39
|
+
if (consent)
|
|
40
|
+
return consent[category];
|
|
41
|
+
return !isConsentRegionCountry(requestCountry(event));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* `GET /_agent-native/auth/consent-region`: `consent` when the visitor's
|
|
45
|
+
* country gets the banner, `none` when it doesn't, `unknown` when the host
|
|
46
|
+
* sent no country. The SSR shell is cached for everyone, so the browser asks
|
|
47
|
+
* here instead of reading it from the page.
|
|
48
|
+
*/
|
|
49
|
+
export function createConsentRegionHandler() {
|
|
50
|
+
return defineEventHandler((event) => {
|
|
51
|
+
setResponseHeader(event, "cache-control", "no-store");
|
|
52
|
+
if (getMethod(event) !== "GET") {
|
|
53
|
+
setResponseStatus(event, 405);
|
|
54
|
+
return { error: "Method not allowed" };
|
|
55
|
+
}
|
|
56
|
+
const country = requestCountry(event);
|
|
57
|
+
if (!country)
|
|
58
|
+
return { region: "unknown" };
|
|
59
|
+
return { region: isConsentRegionCountry(country) ? "consent" : "none" };
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* `POST /_agent-native/auth/consent` with `{ choice: "1.p1.t0" }`: write the
|
|
64
|
+
* visitor's choice from the server. Safari keeps a cookie page JavaScript
|
|
65
|
+
* wrote for seven days at most, and builder.io asks again only after a year.
|
|
66
|
+
* Uses the same domain as the browser's copy, so it replaces that copy.
|
|
67
|
+
*/
|
|
68
|
+
export function createConsentHandler() {
|
|
69
|
+
return defineEventHandler(async (event) => {
|
|
70
|
+
setResponseHeader(event, "cache-control", "no-store");
|
|
71
|
+
if (getMethod(event) !== "POST") {
|
|
72
|
+
setResponseStatus(event, 405);
|
|
73
|
+
return { error: "Method not allowed" };
|
|
74
|
+
}
|
|
75
|
+
if (!isSameOriginPost(event)) {
|
|
76
|
+
setResponseStatus(event, 403);
|
|
77
|
+
return { error: "Cross-origin request" };
|
|
78
|
+
}
|
|
79
|
+
let choice;
|
|
80
|
+
try {
|
|
81
|
+
const body = await readBodyWithSizeLimit(event, MAX_CONSENT_BODY_BYTES);
|
|
82
|
+
choice = parseConsent(body?.choice);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error.statusCode === 413) {
|
|
86
|
+
return { error: "Body too large" };
|
|
87
|
+
}
|
|
88
|
+
setResponseStatus(event, 400);
|
|
89
|
+
return { error: "Invalid JSON" };
|
|
90
|
+
}
|
|
91
|
+
if (!choice) {
|
|
92
|
+
setResponseStatus(event, 400);
|
|
93
|
+
return { error: "Missing choice" };
|
|
94
|
+
}
|
|
95
|
+
const domain = consentCookieDomain(getForwardedRequestURL(event).hostname);
|
|
96
|
+
const secure = getForwardedRequestOrigin(event).startsWith("https:");
|
|
97
|
+
appendResponseHeader(event, "set-cookie", `${CONSENT_COOKIE_NAME}=${serializeConsent(choice)}; ` +
|
|
98
|
+
`Max-Age=${CONSENT_COOKIE_LIFETIME_SECONDS}; Path=/; SameSite=Lax` +
|
|
99
|
+
`${domain ? `; Domain=${domain}` : ""}${secure ? "; Secure" : ""}`);
|
|
100
|
+
return { stored: true, choice };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type FirstTouchAttribution } from "./attribution.js";
|
|
2
|
+
export type FirstTouchSyncResult = {
|
|
3
|
+
stored: true;
|
|
4
|
+
firstTouch: FirstTouchAttribution;
|
|
5
|
+
} | {
|
|
6
|
+
stored: false;
|
|
7
|
+
reason: "consent_required" | "expired";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* `POST /_agent-native/auth/attribution` with `{ firstTouch }`: keep the
|
|
11
|
+
* browser's first touch in the year-long `an_fts` cookie. Anyone may call it,
|
|
12
|
+
* signed in or not, but only from this origin. The value already in the
|
|
13
|
+
* cookie wins unless `preferFirstTouch` picks the new one, so a slow request
|
|
14
|
+
* from another tab can't replace a better first touch it never saw. Where the
|
|
15
|
+
* banner applies and the visitor hasn't accepted targeting cookies, it stores
|
|
16
|
+
* nothing and clears every attribution cookie. Elsewhere it also writes the
|
|
17
|
+
* browser's `an_ft` and `an_lt` again, so Safari keeps them their 30 days.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createFirstTouchHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
20
|
+
reason?: undefined;
|
|
21
|
+
error: string;
|
|
22
|
+
stored?: undefined;
|
|
23
|
+
firstTouch?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
error?: undefined;
|
|
26
|
+
stored: false;
|
|
27
|
+
reason: "consent_required";
|
|
28
|
+
firstTouch?: undefined;
|
|
29
|
+
} | {
|
|
30
|
+
error?: undefined;
|
|
31
|
+
stored: false;
|
|
32
|
+
reason: "expired";
|
|
33
|
+
firstTouch?: undefined;
|
|
34
|
+
} | {
|
|
35
|
+
reason?: undefined;
|
|
36
|
+
error?: undefined;
|
|
37
|
+
stored: true;
|
|
38
|
+
firstTouch: FirstTouchAttribution;
|
|
39
|
+
}>>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { appendResponseHeader, defineEventHandler, getHeader, getMethod, setResponseHeader, setResponseStatus, } from "h3";
|
|
2
|
+
import { FIRST_TOUCH_SERVER_COOKIE_NAME, firstTouchServerExpiry, preferFirstTouch, } from "../shared/first-touch-persistence.js";
|
|
3
|
+
import { FIRST_TOUCH_COOKIE_NAME, LAST_TOUCH_COOKIE_NAME, decodeFirstTouchServerValue, decodeFirstTouchValue, decodeLastTouchValue, encodeFirstTouchServerCookieValue, parseCookieHeader, } from "./attribution.js";
|
|
4
|
+
import { requestAllowsConsentCategory } from "./consent-route.js";
|
|
5
|
+
import { readBodyWithSizeLimit } from "./h3-helpers.js";
|
|
6
|
+
import { getForwardedRequestOrigin, isSameOriginPost, } from "./request-origin.js";
|
|
7
|
+
const MAX_BODY_BYTES = 8 * 1024;
|
|
8
|
+
/** How long the browser's own `an_ft` and `an_lt` copies last. */
|
|
9
|
+
const BROWSER_COOKIE_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
|
|
10
|
+
const BROWSER_COOKIES = [
|
|
11
|
+
{ name: FIRST_TOUCH_COOKIE_NAME, decode: decodeFirstTouchValue },
|
|
12
|
+
{ name: LAST_TOUCH_COOKIE_NAME, decode: decodeLastTouchValue },
|
|
13
|
+
];
|
|
14
|
+
// What `encodeURIComponent` emits, so a value echoed into Set-Cookie can't
|
|
15
|
+
// carry an attribute or a header break.
|
|
16
|
+
const ENCODED_COOKIE_VALUE = /^[A-Za-z0-9\-_.!~*'()%]{1,1500}$/;
|
|
17
|
+
function cookieAttributes(event) {
|
|
18
|
+
const secure = getForwardedRequestOrigin(event).startsWith("https:");
|
|
19
|
+
return `Path=/; SameSite=Lax${secure ? "; Secure" : ""}`;
|
|
20
|
+
}
|
|
21
|
+
function clearAttributionCookies(event) {
|
|
22
|
+
for (const name of [
|
|
23
|
+
FIRST_TOUCH_SERVER_COOKIE_NAME,
|
|
24
|
+
FIRST_TOUCH_COOKIE_NAME,
|
|
25
|
+
LAST_TOUCH_COOKIE_NAME,
|
|
26
|
+
]) {
|
|
27
|
+
appendResponseHeader(event, "set-cookie", `${name}=; Max-Age=0; ${cookieAttributes(event)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Safari keeps a cookie page JavaScript wrote for seven days at most, so write
|
|
32
|
+
* the browser's own `an_ft` and `an_lt` again from here, for their full 30
|
|
33
|
+
* days. Only values that decode as touches are echoed back.
|
|
34
|
+
*/
|
|
35
|
+
function resetBrowserCookies(event) {
|
|
36
|
+
const cookies = parseCookieHeader(getHeader(event, "cookie"));
|
|
37
|
+
for (const { name, decode } of BROWSER_COOKIES) {
|
|
38
|
+
const value = cookies[name];
|
|
39
|
+
if (!value || !ENCODED_COOKIE_VALUE.test(value) || !decode(value)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
appendResponseHeader(event, "set-cookie", `${name}=${value}; Max-Age=${BROWSER_COOKIE_MAX_AGE_SECONDS}; ${cookieAttributes(event)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function readCandidate(body) {
|
|
46
|
+
const firstTouch = body?.firstTouch;
|
|
47
|
+
if (!firstTouch || typeof firstTouch !== "object")
|
|
48
|
+
return null;
|
|
49
|
+
return decodeFirstTouchValue(encodeURIComponent(JSON.stringify(firstTouch)));
|
|
50
|
+
}
|
|
51
|
+
/** A touch more than a year old can't be kept, or win against one that can. */
|
|
52
|
+
function unexpired(touch) {
|
|
53
|
+
return touch && firstTouchServerExpiry(touch) ? touch : null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `POST /_agent-native/auth/attribution` with `{ firstTouch }`: keep the
|
|
57
|
+
* browser's first touch in the year-long `an_fts` cookie. Anyone may call it,
|
|
58
|
+
* signed in or not, but only from this origin. The value already in the
|
|
59
|
+
* cookie wins unless `preferFirstTouch` picks the new one, so a slow request
|
|
60
|
+
* from another tab can't replace a better first touch it never saw. Where the
|
|
61
|
+
* banner applies and the visitor hasn't accepted targeting cookies, it stores
|
|
62
|
+
* nothing and clears every attribution cookie. Elsewhere it also writes the
|
|
63
|
+
* browser's `an_ft` and `an_lt` again, so Safari keeps them their 30 days.
|
|
64
|
+
*/
|
|
65
|
+
export function createFirstTouchHandler() {
|
|
66
|
+
return defineEventHandler(async (event) => {
|
|
67
|
+
setResponseHeader(event, "cache-control", "no-store");
|
|
68
|
+
if (getMethod(event) !== "POST") {
|
|
69
|
+
setResponseStatus(event, 405);
|
|
70
|
+
return { error: "Method not allowed" };
|
|
71
|
+
}
|
|
72
|
+
if (!isSameOriginPost(event)) {
|
|
73
|
+
setResponseStatus(event, 403);
|
|
74
|
+
return { error: "Cross-origin request" };
|
|
75
|
+
}
|
|
76
|
+
let body;
|
|
77
|
+
try {
|
|
78
|
+
body = await readBodyWithSizeLimit(event, MAX_BODY_BYTES);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error.statusCode === 413) {
|
|
82
|
+
return { error: "Body too large" };
|
|
83
|
+
}
|
|
84
|
+
setResponseStatus(event, 400);
|
|
85
|
+
return { error: "Invalid JSON" };
|
|
86
|
+
}
|
|
87
|
+
if (!requestAllowsConsentCategory(event, "targeting")) {
|
|
88
|
+
clearAttributionCookies(event);
|
|
89
|
+
return {
|
|
90
|
+
stored: false,
|
|
91
|
+
reason: "consent_required",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const candidate = readCandidate(body);
|
|
95
|
+
if (!candidate) {
|
|
96
|
+
setResponseStatus(event, 400);
|
|
97
|
+
return { error: "Missing firstTouch" };
|
|
98
|
+
}
|
|
99
|
+
resetBrowserCookies(event);
|
|
100
|
+
const existing = decodeFirstTouchServerValue(parseCookieHeader(getHeader(event, "cookie"))[FIRST_TOUCH_SERVER_COOKIE_NAME]);
|
|
101
|
+
const firstTouch = preferFirstTouch(unexpired(existing), unexpired(candidate));
|
|
102
|
+
const expires = firstTouch && firstTouchServerExpiry(firstTouch);
|
|
103
|
+
if (!firstTouch || !expires) {
|
|
104
|
+
return {
|
|
105
|
+
stored: false,
|
|
106
|
+
reason: "expired",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (firstTouch !== existing) {
|
|
110
|
+
appendResponseHeader(event, "set-cookie", `${FIRST_TOUCH_SERVER_COOKIE_NAME}=${encodeFirstTouchServerCookieValue(firstTouch)}; ` +
|
|
111
|
+
`Expires=${expires.toUTCString()}; ${cookieAttributes(event)}`);
|
|
112
|
+
}
|
|
113
|
+
return { stored: true, firstTouch };
|
|
114
|
+
});
|
|
115
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -127,3 +127,4 @@ export { AGENT_READABLE_RESOURCE_PAYLOAD_TYPE, AGENT_READABLE_RESOURCE_SCRIPT_TY
|
|
|
127
127
|
export { registerObservabilityProvider, type ObservabilityMeterProvider, type ObservabilityProvider, type ObservabilityTracerProvider, } from "../observability/otel-provider.js";
|
|
128
128
|
export type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
|
|
129
129
|
export declare function defineNitroPlugin(def: NitroPluginDef): NitroPluginDef;
|
|
130
|
+
export { appStateListKeysByKeySuffixPrefix } from "./application-state-maintenance.js";
|
package/dist/server/index.js
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function scheduleQueuedSubmissionInboxDrain(input: {
|
|
2
|
+
threadId: string;
|
|
3
|
+
turnContinues: boolean;
|
|
4
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
5
|
+
drain: (input: {
|
|
6
|
+
threadId: string;
|
|
7
|
+
}) => Promise<unknown>;
|
|
8
|
+
onError: (error: unknown) => void;
|
|
9
|
+
}): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { runWithoutExecutionFence } from "../agent/execution-fence.js";
|
|
2
|
+
export function scheduleQueuedSubmissionInboxDrain(input) {
|
|
3
|
+
if (input.turnContinues)
|
|
4
|
+
return;
|
|
5
|
+
const drain = Promise.resolve()
|
|
6
|
+
.then(() => runWithoutExecutionFence(() => input.drain({ threadId: input.threadId })))
|
|
7
|
+
.catch(input.onError);
|
|
8
|
+
if (input.waitUntil)
|
|
9
|
+
input.waitUntil(drain);
|
|
10
|
+
else
|
|
11
|
+
void drain;
|
|
12
|
+
}
|
|
@@ -4,4 +4,10 @@ export declare function getForwardedRequestOrigin(event: H3Event): string;
|
|
|
4
4
|
export declare function getForwardedRequestURL(event: H3Event): URL;
|
|
5
5
|
export declare function getForwardedRequestHostname(event: H3Event): string;
|
|
6
6
|
export declare function getForwardedRequestHostnameFromHeaders(headers: Headers | IncomingHttpHeaders, remoteAddress?: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Stricter than `isSameOriginRequest`, for a POST that writes cookies: a
|
|
9
|
+
* request with neither header, a navigation, or a desktop shell's origin
|
|
10
|
+
* can't prove it came from this site's pages.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isSameOriginPost(event: H3Event): boolean;
|
|
7
13
|
export declare function isSameOriginRequest(event: H3Event): boolean;
|
|
@@ -95,6 +95,18 @@ export function getForwardedRequestHostnameFromHeaders(headers, remoteAddress) {
|
|
|
95
95
|
function isLoopbackHost(host) {
|
|
96
96
|
return host.startsWith("localhost:") || host.startsWith("127.0.0.1:");
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Stricter than `isSameOriginRequest`, for a POST that writes cookies: a
|
|
100
|
+
* request with neither header, a navigation, or a desktop shell's origin
|
|
101
|
+
* can't prove it came from this site's pages.
|
|
102
|
+
*/
|
|
103
|
+
export function isSameOriginPost(event) {
|
|
104
|
+
const fetchSite = getRequestHeader(event, "sec-fetch-site");
|
|
105
|
+
if (fetchSite)
|
|
106
|
+
return fetchSite === "same-origin";
|
|
107
|
+
const origin = getRequestHeader(event, "origin");
|
|
108
|
+
return !!origin && origin === getForwardedRequestOrigin(event);
|
|
109
|
+
}
|
|
98
110
|
export function isSameOriginRequest(event) {
|
|
99
111
|
const fetchSite = getRequestHeader(event, "sec-fetch-site");
|
|
100
112
|
if (fetchSite)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type StoreWriteOptions, type StoreReadOptions } from "./store.js";
|
|
1
|
+
import { type SettingEntry, type StoreWriteOptions, type StoreReadOptions } from "./store.js";
|
|
2
2
|
export declare function getOrgSetting(orgId: string, key: string, options?: StoreReadOptions): Promise<Record<string, unknown> | null>;
|
|
3
|
+
export declare function getOrgSettingEntry(orgId: string, key: string, options?: StoreReadOptions): Promise<SettingEntry>;
|
|
3
4
|
export declare function putOrgSetting(orgId: string, key: string, value: Record<string, unknown>, options?: StoreWriteOptions): Promise<void>;
|
|
4
5
|
export declare function mutateOrgSetting(orgId: string, key: string, updater: (current: Record<string, unknown> | null) => Record<string, unknown> | Promise<Record<string, unknown>>, options?: StoreWriteOptions): Promise<Record<string, unknown>>;
|
|
5
6
|
export declare function deleteOrgSetting(orgId: string, key: string, options?: StoreWriteOptions): Promise<boolean>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getSetting, mutateSetting, putSetting, deleteSetting, deleteSettingsByPrefix, listSettingsByPrefix, } from "./store.js";
|
|
1
|
+
import { getSetting, getSettingEntry, mutateSetting, putSetting, deleteSetting, deleteSettingsByPrefix, listSettingsByPrefix, } from "./store.js";
|
|
2
2
|
function orgKey(orgId, key) {
|
|
3
3
|
return `o:${orgId}:${key}`;
|
|
4
4
|
}
|
|
@@ -6,6 +6,9 @@ const ORG_PREFIX_RE = /^o:([^:]+):(.+)$/;
|
|
|
6
6
|
export async function getOrgSetting(orgId, key, options) {
|
|
7
7
|
return getSetting(orgKey(orgId, key), options);
|
|
8
8
|
}
|
|
9
|
+
export async function getOrgSettingEntry(orgId, key, options) {
|
|
10
|
+
return getSettingEntry(orgKey(orgId, key), options);
|
|
11
|
+
}
|
|
9
12
|
export async function putOrgSetting(orgId, key, value, options) {
|
|
10
13
|
return putSetting(orgKey(orgId, key), value, options);
|
|
11
14
|
}
|
package/dist/settings/store.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ export interface StoreReadOptions {
|
|
|
9
9
|
transaction?: DbExec;
|
|
10
10
|
}
|
|
11
11
|
export declare function getSetting(key: string, options?: StoreReadOptions): Promise<Record<string, unknown> | null>;
|
|
12
|
+
export type SettingEntry = {
|
|
13
|
+
found: false;
|
|
14
|
+
} | {
|
|
15
|
+
found: true;
|
|
16
|
+
value: unknown;
|
|
17
|
+
};
|
|
18
|
+
/** Like `getSetting`, but a stored JSON `null` is found, not missing. */
|
|
19
|
+
export declare function getSettingEntry(key: string, options?: StoreReadOptions): Promise<SettingEntry>;
|
|
12
20
|
export declare function getSettings(keys: readonly string[], options?: StoreReadOptions): Promise<Map<string, Record<string, unknown> | null>>;
|
|
13
21
|
export interface StoreWriteOptions {
|
|
14
22
|
requestSource?: string;
|
package/dist/settings/store.js
CHANGED
|
@@ -63,12 +63,10 @@ export const settingsStore = defineStore({
|
|
|
63
63
|
export function ensureTable() {
|
|
64
64
|
return settingsStore.ready();
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
async function readSettingRaw(key, options) {
|
|
67
67
|
const cache = options?.transaction ? null : requestSettingsCache();
|
|
68
|
-
if (!options?.bypassCache && cache?.has(key))
|
|
69
|
-
|
|
70
|
-
return cached == null ? null : JSON.parse(cached);
|
|
71
|
-
}
|
|
68
|
+
if (!options?.bypassCache && cache?.has(key))
|
|
69
|
+
return cache.get(key) ?? null;
|
|
72
70
|
if (!options?.transaction)
|
|
73
71
|
await ensureTable();
|
|
74
72
|
const client = options?.transaction ?? getDbExec();
|
|
@@ -80,8 +78,19 @@ export async function getSetting(key, options) {
|
|
|
80
78
|
const raw = rows.length === 0 ? null : rows[0].value;
|
|
81
79
|
if (!options?.bypassCache)
|
|
82
80
|
cache?.set(key, raw);
|
|
81
|
+
return raw;
|
|
82
|
+
}
|
|
83
|
+
export async function getSetting(key, options) {
|
|
84
|
+
const raw = await readSettingRaw(key, options);
|
|
83
85
|
return raw == null ? null : JSON.parse(raw);
|
|
84
86
|
}
|
|
87
|
+
/** Like `getSetting`, but a stored JSON `null` is found, not missing. */
|
|
88
|
+
export async function getSettingEntry(key, options) {
|
|
89
|
+
const raw = await readSettingRaw(key, options);
|
|
90
|
+
return raw == null
|
|
91
|
+
? { found: false }
|
|
92
|
+
: { found: true, value: JSON.parse(raw) };
|
|
93
|
+
}
|
|
85
94
|
const SETTINGS_IN_LIST_CHUNK_SIZE = 500;
|
|
86
95
|
function parseSettingValue(key, raw) {
|
|
87
96
|
try {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie consent for the regions that need it. The regions and categories
|
|
3
|
+
* follow builder.io's consent banner: visitors in these countries opt in, and
|
|
4
|
+
* every other visitor is tracked as before, with no banner.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isConsentRegionCountry(code: string | null | undefined): boolean;
|
|
7
|
+
export declare function isConsentRegionTimeZone(timeZone: string | null | undefined): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* What the banner asks about. `performance` covers analytics (Google
|
|
10
|
+
* Analytics, Amplitude); `targeting` covers advertising and attribution (Tag
|
|
11
|
+
* Manager's ad tags, the referral attribution cookies). Strictly necessary
|
|
12
|
+
* cookies need no consent.
|
|
13
|
+
*/
|
|
14
|
+
export type ConsentCategory = "performance" | "targeting";
|
|
15
|
+
export type ConsentChoice = Record<ConsentCategory, boolean>;
|
|
16
|
+
/** `consent` shows the banner; `none` tracks as before. */
|
|
17
|
+
export type ConsentRegion = "consent" | "none";
|
|
18
|
+
export declare const CONSENT_COOKIE_NAME = "an_consent";
|
|
19
|
+
/** Asked again a year later, like builder.io. */
|
|
20
|
+
export declare const CONSENT_COOKIE_LIFETIME_SECONDS: number;
|
|
21
|
+
export declare const CONSENT_REGION_PATH = "/_agent-native/auth/consent-region";
|
|
22
|
+
export declare const CONSENT_PATH = "/_agent-native/auth/consent";
|
|
23
|
+
/**
|
|
24
|
+
* One choice covers the site and every app on agent-native.com. The browser
|
|
25
|
+
* and the server must agree, or the two copies become two cookies.
|
|
26
|
+
*/
|
|
27
|
+
export declare function consentCookieDomain(hostname: string): string | null;
|
|
28
|
+
/** `1.p1.t0`: version, then one flag per category. */
|
|
29
|
+
export declare function serializeConsent(choice: ConsentChoice): string;
|
|
30
|
+
/** The stored choice, or `null` when there is none this version can read. */
|
|
31
|
+
export declare function parseConsent(value: string | null | undefined): ConsentChoice | null;
|