@blotout/qinto-cli 0.1.24 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/index.js +2258 -2232
- package/dist/index.js.map +1 -1
- package/dist/worker-artifact/app-runtime/index.js +88 -47
- package/dist/worker-artifact/main/61afaba4cf160e30ef4b71a7ce30a398f71f13e8-q.js.txt +1 -0
- package/dist/worker-artifact/main/index.js +2007 -1583
- package/dist/worker-artifact/version.json +8 -8
- package/package.json +1 -1
- package/dist/worker-artifact/main/944c722e73fe144b1067da970f87716a835c697e-q.js.txt +0 -1
|
@@ -20604,7 +20604,7 @@ var MAX_APP_TOOL_NAME_LENGTH = 32;
|
|
|
20604
20604
|
var APP_TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
20605
20605
|
|
|
20606
20606
|
// ../../libs/app-manifest/src/tools.ts
|
|
20607
|
-
var MAX_APP_TOOLS =
|
|
20607
|
+
var MAX_APP_TOOLS = 20;
|
|
20608
20608
|
var MAX_APP_TOOL_TITLE_LENGTH = 60;
|
|
20609
20609
|
var MIN_APP_TOOL_DESCRIPTION = 20;
|
|
20610
20610
|
var MAX_APP_TOOL_DESCRIPTION = 16 * 1024;
|
|
@@ -20612,7 +20612,7 @@ var MAX_APP_TOOL_PROPERTY_DESCRIPTION = 200;
|
|
|
20612
20612
|
var MAX_APP_TOOL_PROPERTIES = 24;
|
|
20613
20613
|
var MAX_APP_TOOL_SCHEMA_DEPTH = 4;
|
|
20614
20614
|
var MAX_APP_TOOL_ENUM_VALUES = 32;
|
|
20615
|
-
var MAX_MANIFEST_TOOLS_BYTES =
|
|
20615
|
+
var MAX_MANIFEST_TOOLS_BYTES = 375 * 1024;
|
|
20616
20616
|
var TOOL_PROPERTY_NAME = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
20617
20617
|
var RESERVED_TOOL_ARGUMENTS = ["teamId", "siteId", "installId"];
|
|
20618
20618
|
var TOOL_STRING_FORMATS = ["uuid", "date", "date-time", "email", "uri"];
|
|
@@ -20818,6 +20818,7 @@ var APP_FLOWS = ["transform", "destination"];
|
|
|
20818
20818
|
var TRANSFORM_ACTION_TYPES = ["track", "identify"];
|
|
20819
20819
|
var EVENT_ACTION_TYPES = ["track", "identify", "consent"];
|
|
20820
20820
|
var BROWSER_RESULT_ACTION_TYPES = ["track", "identify"];
|
|
20821
|
+
var BROWSER_HOOKS = ["transform"];
|
|
20821
20822
|
var APP_LIFECYCLE_TYPES = ["installed", "configurationChanged", "enabled", "disabled", "versionChanged", "uninstalled"];
|
|
20822
20823
|
var API_CAPABILITIES = ["track", "identify", "alias", "consentWrite", "consentRead", "propertiesRead", "sessionRead"];
|
|
20823
20824
|
var DATA_CAPABILITIES = [
|
|
@@ -20828,7 +20829,8 @@ var DATA_CAPABILITIES = [
|
|
|
20828
20829
|
"eventContext",
|
|
20829
20830
|
"sharedContext",
|
|
20830
20831
|
"facts",
|
|
20831
|
-
"identity"
|
|
20832
|
+
"identity",
|
|
20833
|
+
"siteInstalls"
|
|
20832
20834
|
];
|
|
20833
20835
|
var MAX_APP_VARIABLES = 32;
|
|
20834
20836
|
var MAX_APP_SCOPE_VARIABLES = 16;
|
|
@@ -21106,7 +21108,8 @@ var baseSchema = external_exports.object({
|
|
|
21106
21108
|
browser: external_exports.object({
|
|
21107
21109
|
entry: external_exports.string().min(1).max(255).optional(),
|
|
21108
21110
|
capture: captureSchema.optional(),
|
|
21109
|
-
eventResults: external_exports.array(external_exports.enum(BROWSER_RESULT_ACTION_TYPES)).max(BROWSER_RESULT_ACTION_TYPES.length).optional()
|
|
21111
|
+
eventResults: external_exports.array(external_exports.enum(BROWSER_RESULT_ACTION_TYPES)).max(BROWSER_RESULT_ACTION_TYPES.length).optional(),
|
|
21112
|
+
hooks: external_exports.array(external_exports.enum(BROWSER_HOOKS)).max(BROWSER_HOOKS.length).optional()
|
|
21110
21113
|
}).strict().optional().meta({
|
|
21111
21114
|
description: "The app's browser surface: an optional entry for the bundle that ships inside the site's q.js, and the query params and cookies it declares for capture."
|
|
21112
21115
|
}),
|
|
@@ -21410,6 +21413,15 @@ var validateCapture = /* @__PURE__ */ __name((manifest, ctx) => {
|
|
|
21410
21413
|
}
|
|
21411
21414
|
}
|
|
21412
21415
|
}, "validateCapture");
|
|
21416
|
+
var validateBrowserHooks = /* @__PURE__ */ __name((manifest, ctx) => {
|
|
21417
|
+
const hooks = manifest.browser?.hooks ?? [];
|
|
21418
|
+
if (hooks.length > 0 && manifest.browser?.entry === void 0) {
|
|
21419
|
+
ctx.addIssue({ code: "custom", path: ["browser", "hooks"], message: "hooks needs a browser entry" });
|
|
21420
|
+
}
|
|
21421
|
+
if (hooks.includes("transform") && !manifest.flows.includes("transform")) {
|
|
21422
|
+
ctx.addIssue({ code: "custom", path: ["browser", "hooks"], message: "The browser transform hook needs the transform flow" });
|
|
21423
|
+
}
|
|
21424
|
+
}, "validateBrowserHooks");
|
|
21413
21425
|
var validateRpc = /* @__PURE__ */ __name((manifest, ctx) => {
|
|
21414
21426
|
const exposes = manifest.rpc?.exposes ?? [];
|
|
21415
21427
|
uniqueOrIssue(
|
|
@@ -21470,6 +21482,7 @@ var appManifestSchema = baseSchema.superRefine((manifest, ctx) => {
|
|
|
21470
21482
|
validatePipelines(manifest, ctx);
|
|
21471
21483
|
validateEndpoints(manifest, ctx);
|
|
21472
21484
|
validateCapture(manifest, ctx);
|
|
21485
|
+
validateBrowserHooks(manifest, ctx);
|
|
21473
21486
|
validateRpc(manifest, ctx);
|
|
21474
21487
|
validateEvents(manifest, ctx);
|
|
21475
21488
|
validateOauth(manifest, ctx);
|
|
@@ -21502,6 +21515,7 @@ var MAX_APPS_Q_BYTES = 4 * 1024 * 1024;
|
|
|
21502
21515
|
var ENVELOPE_VERSION = 3;
|
|
21503
21516
|
var ENVELOPE_ERROR_CODES = ["invalid_arguments"];
|
|
21504
21517
|
var MAX_INSTALLS_PER_APP = 15;
|
|
21518
|
+
var MAX_APPS_PER_DOMAIN = 50;
|
|
21505
21519
|
var MAX_EMITS_PER_INSTALL = 10;
|
|
21506
21520
|
var MAX_PUSHES_PER_INSTALL = 10;
|
|
21507
21521
|
var MAX_CAPTURE_WRITES_PER_INSTALL = 10;
|
|
@@ -21517,6 +21531,10 @@ var MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
|
21517
21531
|
var MAX_RPC_BYTES = 128 * 1024;
|
|
21518
21532
|
var installSchema = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict();
|
|
21519
21533
|
var emitSchema = external_exports.object({ type: external_exports.literal("track"), occurredAt: external_exports.number().int().nonnegative().safe(), payload: external_exports.record(external_exports.string(), external_exports.unknown()) }).strict();
|
|
21534
|
+
var installRuleSchema = external_exports.object({
|
|
21535
|
+
mode: external_exports.enum(["except", "only"]),
|
|
21536
|
+
installIds: external_exports.array(external_exports.string().min(1).max(64)).max(MAX_APPS_PER_DOMAIN * MAX_INSTALLS_PER_APP)
|
|
21537
|
+
}).strict();
|
|
21520
21538
|
var pushSchema = external_exports.object({ topic: external_exports.string().min(1).max(64), data: external_exports.unknown(), broadcast: external_exports.enum(PUSH_BROADCASTS).optional() }).strict().refine((push) => {
|
|
21521
21539
|
return new TextEncoder().encode(JSON.stringify(push.data ?? null)).length <= MAX_PUSH_BYTES;
|
|
21522
21540
|
}, "Push data exceeds the per-message size cap");
|
|
@@ -21603,7 +21621,11 @@ var envelopeRequestSchema = external_exports.object({
|
|
|
21603
21621
|
var envelopeResponseSchema = external_exports.object({
|
|
21604
21622
|
v: external_exports.literal(ENVELOPE_VERSION),
|
|
21605
21623
|
// Transform invocations only: pass the action through, drop it, or replace its payload (§5.4).
|
|
21606
|
-
result: external_exports.union([
|
|
21624
|
+
result: external_exports.union([
|
|
21625
|
+
external_exports.literal("pass"),
|
|
21626
|
+
external_exports.literal("drop"),
|
|
21627
|
+
external_exports.object({ payload: external_exports.record(external_exports.string(), external_exports.unknown()).optional(), installs: installRuleSchema.optional() }).strict()
|
|
21628
|
+
]).optional(),
|
|
21607
21629
|
effects: external_exports.array(effectGroupSchema).optional(),
|
|
21608
21630
|
rpc: external_exports.object({ result: external_exports.unknown() }).strict().refine((rpc) => {
|
|
21609
21631
|
return new TextEncoder().encode(JSON.stringify(rpc.result ?? null)).length <= MAX_RPC_BYTES;
|
|
@@ -21901,6 +21923,16 @@ var aggregateEndpointOutcomes = /* @__PURE__ */ __name((outcomes) => {
|
|
|
21901
21923
|
return { status: failed ? 502 : 200 };
|
|
21902
21924
|
}, "aggregateEndpointOutcomes");
|
|
21903
21925
|
|
|
21926
|
+
// ../../libs/app-envelope/src/install-rule.ts
|
|
21927
|
+
var intersectInstallRules = /* @__PURE__ */ __name((a, b) => {
|
|
21928
|
+
if (a.mode === "except" && b.mode === "except") {
|
|
21929
|
+
return { mode: "except", installIds: [.../* @__PURE__ */ new Set([...a.installIds, ...b.installIds])] };
|
|
21930
|
+
}
|
|
21931
|
+
const [only, other] = a.mode === "only" ? [a, b] : [b, a];
|
|
21932
|
+
const listed = new Set(other.installIds);
|
|
21933
|
+
return { mode: "only", installIds: only.installIds.filter((id) => listed.has(id) === (other.mode === "only")) };
|
|
21934
|
+
}, "intersectInstallRules");
|
|
21935
|
+
|
|
21904
21936
|
// ../../libs/app-envelope/src/observability.ts
|
|
21905
21937
|
var APP_OBSERVABILITY_HEADER = "x-qinto-app-observability";
|
|
21906
21938
|
var APP_OBSERVABILITY_VERSION = 1;
|
|
@@ -21970,6 +22002,12 @@ var parseAppObservability = /* @__PURE__ */ __name((header) => {
|
|
|
21970
22002
|
var UI_CONTEXT_HEADER = "x-qinto-ui-context";
|
|
21971
22003
|
var MAX_UI_BODY_BYTES = 1024 * 1024;
|
|
21972
22004
|
var uiInstallSchema = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100), enabled: external_exports.boolean() }).strict();
|
|
22005
|
+
var siteInstallSchema = external_exports.object({
|
|
22006
|
+
id: external_exports.string().min(1).max(64),
|
|
22007
|
+
appSlug: external_exports.string().min(1).max(30),
|
|
22008
|
+
appName: external_exports.string().min(1).max(100),
|
|
22009
|
+
label: external_exports.string().max(100)
|
|
22010
|
+
}).strict();
|
|
21973
22011
|
var uiContextSchema = external_exports.object({
|
|
21974
22012
|
version: external_exports.string().min(1).max(32),
|
|
21975
22013
|
viewer: external_exports.object({
|
|
@@ -21980,8 +22018,35 @@ var uiContextSchema = external_exports.object({
|
|
|
21980
22018
|
}).strict(),
|
|
21981
22019
|
install: uiInstallSchema,
|
|
21982
22020
|
installs: external_exports.array(uiInstallSchema).min(1).max(MAX_INSTALLS_PER_APP),
|
|
22021
|
+
siteInstalls: external_exports.array(siteInstallSchema).max(MAX_APPS_PER_DOMAIN * MAX_INSTALLS_PER_APP).optional(),
|
|
21983
22022
|
writeTicket: external_exports.string().min(1).max(2048).optional()
|
|
21984
22023
|
}).strict();
|
|
22024
|
+
var MAX_UI_SITE_INSTALLS_BYTES = 64 * 1024;
|
|
22025
|
+
var asciiJson = /* @__PURE__ */ __name((value) => {
|
|
22026
|
+
return JSON.stringify(value).replace(/[\u007f-\uffff]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
22027
|
+
}, "asciiJson");
|
|
22028
|
+
var encodeUiContext = /* @__PURE__ */ __name((context) => {
|
|
22029
|
+
return asciiJson(context);
|
|
22030
|
+
}, "encodeUiContext");
|
|
22031
|
+
var bySlugThenOrd = /* @__PURE__ */ __name((a, b) => {
|
|
22032
|
+
if (a.appSlug !== b.appSlug) {
|
|
22033
|
+
return a.appSlug < b.appSlug ? -1 : 1;
|
|
22034
|
+
}
|
|
22035
|
+
return a.ord - b.ord;
|
|
22036
|
+
}, "bySlugThenOrd");
|
|
22037
|
+
var boundSiteInstalls = /* @__PURE__ */ __name((installs) => {
|
|
22038
|
+
const listed = [];
|
|
22039
|
+
let bytes = 2;
|
|
22040
|
+
for (const { id, appSlug, appName, label } of [...installs].sort(bySlugThenOrd)) {
|
|
22041
|
+
const install = { id, appSlug, appName, label };
|
|
22042
|
+
bytes += asciiJson(install).length + 1;
|
|
22043
|
+
if (bytes > MAX_UI_SITE_INSTALLS_BYTES) {
|
|
22044
|
+
break;
|
|
22045
|
+
}
|
|
22046
|
+
listed.push(install);
|
|
22047
|
+
}
|
|
22048
|
+
return listed;
|
|
22049
|
+
}, "boundSiteInstalls");
|
|
21985
22050
|
|
|
21986
22051
|
// ../../node_modules/.pnpm/hono@4.13.5/node_modules/hono/dist/compose.js
|
|
21987
22052
|
var compose = /* @__PURE__ */ __name((middleware, onError, onNotFound) => {
|
|
@@ -26322,6 +26387,10 @@ var phoneIdentifier = /* @__PURE__ */ __name((raw2) => {
|
|
|
26322
26387
|
const identifier = { type: "phone", value: normalizePhone(raw2) };
|
|
26323
26388
|
return isValidIdentifier(identifier) ? identifier : void 0;
|
|
26324
26389
|
}, "phoneIdentifier");
|
|
26390
|
+
var customIdentifier = /* @__PURE__ */ __name((raw2) => {
|
|
26391
|
+
const identifier = { type: "custom", value: raw2.trim() };
|
|
26392
|
+
return isValidIdentifier(identifier) ? identifier : void 0;
|
|
26393
|
+
}, "customIdentifier");
|
|
26325
26394
|
var classifyIdentifier = /* @__PURE__ */ __name((raw2) => {
|
|
26326
26395
|
const identifier = emailIdentifier(raw2) ?? (raw2.trim().startsWith("+") ? phoneIdentifier(raw2) : void 0);
|
|
26327
26396
|
return identifier ?? { type: "custom", value: raw2.trim() };
|
|
@@ -26481,7 +26550,9 @@ var installSchema4 = external_exports.object({
|
|
|
26481
26550
|
active: external_exports.boolean(),
|
|
26482
26551
|
// Absent from a registry deployed before the ui data plane moved off `active`; falling back to it
|
|
26483
26552
|
// keeps that site's panels reachable exactly as they were until it redeploys.
|
|
26484
|
-
enabled: external_exports.boolean().optional()
|
|
26553
|
+
enabled: external_exports.boolean().optional(),
|
|
26554
|
+
// Optional by design: present only on an install that has a geo filter.
|
|
26555
|
+
geoFilter: external_exports.object({ include: external_exports.array(external_exports.string()), exclude: external_exports.array(external_exports.string()) }).optional()
|
|
26485
26556
|
});
|
|
26486
26557
|
var appSchema = external_exports.object({
|
|
26487
26558
|
binding: external_exports.string().min(1),
|
|
@@ -27373,22 +27444,80 @@ var handleCurrencyRates = /* @__PURE__ */ __name(async (ctx) => {
|
|
|
27373
27444
|
return Response.json(current);
|
|
27374
27445
|
}, "handleCurrencyRates");
|
|
27375
27446
|
|
|
27376
|
-
// src/
|
|
27377
|
-
var
|
|
27378
|
-
|
|
27379
|
-
|
|
27380
|
-
|
|
27381
|
-
|
|
27382
|
-
|
|
27383
|
-
|
|
27384
|
-
|
|
27447
|
+
// src/id-graph/upsert.ts
|
|
27448
|
+
var identifierValue = external_exports.string().min(1).max(256).optional();
|
|
27449
|
+
var traitValue = external_exports.string().max(256).optional();
|
|
27450
|
+
var personUpsertSchema = external_exports.object({
|
|
27451
|
+
email: identifierValue,
|
|
27452
|
+
phone: identifierValue,
|
|
27453
|
+
customId: identifierValue,
|
|
27454
|
+
firstName: traitValue,
|
|
27455
|
+
lastName: traitValue,
|
|
27456
|
+
city: traitValue,
|
|
27457
|
+
state: traitValue,
|
|
27458
|
+
zip: traitValue,
|
|
27459
|
+
country: traitValue,
|
|
27460
|
+
observedAt: external_exports.number().int().nonnegative().safe().optional()
|
|
27461
|
+
}).strict().refine((person2) => person2.email !== void 0 || person2.phone !== void 0 || person2.customId !== void 0);
|
|
27462
|
+
var IDENTIFIER_PARSERS = { email: emailIdentifier, phone: phoneIdentifier, customId: customIdentifier };
|
|
27463
|
+
var TRAIT_KEYS = {
|
|
27464
|
+
email: "email",
|
|
27465
|
+
phone: "phone",
|
|
27466
|
+
firstName: "first_name",
|
|
27467
|
+
lastName: "last_name",
|
|
27468
|
+
city: "city",
|
|
27469
|
+
state: "state",
|
|
27470
|
+
zip: "postal_code",
|
|
27471
|
+
country: "country"
|
|
27472
|
+
};
|
|
27473
|
+
var upsertIdentifiers = /* @__PURE__ */ __name((upsert) => {
|
|
27474
|
+
return Object.entries(IDENTIFIER_PARSERS).flatMap(([field, parse4]) => {
|
|
27475
|
+
const raw2 = upsert[field];
|
|
27476
|
+
if (raw2 === void 0) {
|
|
27477
|
+
return [];
|
|
27478
|
+
}
|
|
27479
|
+
const identifier = parse4(raw2);
|
|
27480
|
+
if (identifier === void 0) {
|
|
27481
|
+
addSpanEvent("invalid identifier ignored", { level: "warn", attrs: { field } });
|
|
27482
|
+
return [];
|
|
27483
|
+
}
|
|
27484
|
+
return [identifier];
|
|
27485
|
+
});
|
|
27486
|
+
}, "upsertIdentifiers");
|
|
27487
|
+
var upsertTraits = /* @__PURE__ */ __name((upsert) => {
|
|
27488
|
+
const traits = {};
|
|
27489
|
+
for (const [field, key] of Object.entries(TRAIT_KEYS)) {
|
|
27490
|
+
const value = upsert[field];
|
|
27491
|
+
if (value !== void 0) {
|
|
27492
|
+
traits[key] = value;
|
|
27493
|
+
}
|
|
27385
27494
|
}
|
|
27386
|
-
|
|
27387
|
-
|
|
27388
|
-
|
|
27495
|
+
return traits;
|
|
27496
|
+
}, "upsertTraits");
|
|
27497
|
+
var upsertPerson = /* @__PURE__ */ __name(async (env, upsert) => {
|
|
27498
|
+
const [routing, ...others] = upsertIdentifiers(upsert);
|
|
27499
|
+
if (routing === void 0) {
|
|
27500
|
+
return null;
|
|
27389
27501
|
}
|
|
27390
|
-
|
|
27391
|
-
},
|
|
27502
|
+
const candidate = generateUserId();
|
|
27503
|
+
const { anonId } = await claimAnon(env, routing, candidate);
|
|
27504
|
+
const receivedAt = Date.now();
|
|
27505
|
+
const observedAt = upsert.observedAt ?? receivedAt;
|
|
27506
|
+
const action = buildAction({
|
|
27507
|
+
type: "identify",
|
|
27508
|
+
userId: anonId,
|
|
27509
|
+
source: "app",
|
|
27510
|
+
identifier: routing,
|
|
27511
|
+
identifiers: others,
|
|
27512
|
+
graphOnly: true,
|
|
27513
|
+
observedAt,
|
|
27514
|
+
receivedAt,
|
|
27515
|
+
payload: { traits: upsertTraits(upsert) },
|
|
27516
|
+
context: { source: "app", occurredAt: observedAt, offline: false, replay: false }
|
|
27517
|
+
});
|
|
27518
|
+
await ingest(env, action);
|
|
27519
|
+
return { anonId, created: anonId === candidate };
|
|
27520
|
+
}, "upsertPerson");
|
|
27392
27521
|
|
|
27393
27522
|
// src/apps/rate-limit.ts
|
|
27394
27523
|
var limiterAbsenceLogged = false;
|
|
@@ -27405,6 +27534,49 @@ var rateLimited = /* @__PURE__ */ __name(async (env, key, binding = "BROKER_RATE
|
|
|
27405
27534
|
return !outcome.success;
|
|
27406
27535
|
}, "rateLimited");
|
|
27407
27536
|
|
|
27537
|
+
// src/apps/broker-identity.ts
|
|
27538
|
+
var identityUpsertBody = external_exports.object({
|
|
27539
|
+
...identityFields,
|
|
27540
|
+
kind: external_exports.literal("identityUpsert"),
|
|
27541
|
+
person: personUpsertSchema
|
|
27542
|
+
}).strict();
|
|
27543
|
+
var identityGranted = /* @__PURE__ */ __name((slug) => {
|
|
27544
|
+
return siteRegistry().registry.apps[slug]?.data.includes(IDENTITY_CAPABILITY) === true;
|
|
27545
|
+
}, "identityGranted");
|
|
27546
|
+
var identityPermissionDenied = /* @__PURE__ */ __name(() => {
|
|
27547
|
+
return Response.json({ error: "identity_permission_denied" }, { status: 403 });
|
|
27548
|
+
}, "identityPermissionDenied");
|
|
27549
|
+
var handleIdentityUpsert = /* @__PURE__ */ __name(async (env, body) => {
|
|
27550
|
+
if (!identityGranted(body.slug)) {
|
|
27551
|
+
return identityPermissionDenied();
|
|
27552
|
+
}
|
|
27553
|
+
if (await rateLimited(env, `${body.slug}::identity-upsert`)) {
|
|
27554
|
+
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
27555
|
+
}
|
|
27556
|
+
const upserted = await upsertPerson(env, body.person);
|
|
27557
|
+
if (upserted === null) {
|
|
27558
|
+
return Response.json({ error: "invalid_identifier" }, { status: 400 });
|
|
27559
|
+
}
|
|
27560
|
+
return Response.json(upserted);
|
|
27561
|
+
}, "handleIdentityUpsert");
|
|
27562
|
+
|
|
27563
|
+
// src/apps/emit-payload.ts
|
|
27564
|
+
var emitPayloadSchema = external_exports.object({
|
|
27565
|
+
event: external_exports.string().min(1),
|
|
27566
|
+
eventId: external_exports.string().min(1).optional(),
|
|
27567
|
+
properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
27568
|
+
}).strict();
|
|
27569
|
+
var emittedEventId = /* @__PURE__ */ __name((eventId, properties2) => {
|
|
27570
|
+
if (eventId !== void 0) {
|
|
27571
|
+
return eventId;
|
|
27572
|
+
}
|
|
27573
|
+
const orderId = properties2?.["orderId"];
|
|
27574
|
+
if (typeof orderId === "string" && orderId !== "") {
|
|
27575
|
+
return orderId;
|
|
27576
|
+
}
|
|
27577
|
+
return `evt_${ulid3()}`;
|
|
27578
|
+
}, "emittedEventId");
|
|
27579
|
+
|
|
27408
27580
|
// src/apps/broker-track.ts
|
|
27409
27581
|
var trackBody = external_exports.object({
|
|
27410
27582
|
...identityFields,
|
|
@@ -27412,6 +27584,7 @@ var trackBody = external_exports.object({
|
|
|
27412
27584
|
source: external_exports.enum(APP_HANDLER_SOURCES),
|
|
27413
27585
|
userId: external_exports.string().min(1).max(64),
|
|
27414
27586
|
installId: external_exports.string().min(1).max(64),
|
|
27587
|
+
installs: installRuleSchema.optional(),
|
|
27415
27588
|
event: emitSchema
|
|
27416
27589
|
}).strict();
|
|
27417
27590
|
var handleTrack = /* @__PURE__ */ __name(async (env, body) => {
|
|
@@ -27442,7 +27615,8 @@ var handleTrack = /* @__PURE__ */ __name(async (env, body) => {
|
|
|
27442
27615
|
userId: body.userId,
|
|
27443
27616
|
source: body.source,
|
|
27444
27617
|
payload: { event, eventId: emittedEventId(eventId, properties2), properties: properties2 ?? {} },
|
|
27445
|
-
context: { source: "app", occurredAt: body.event.occurredAt, offline: false, replay: false }
|
|
27618
|
+
context: { source: "app", occurredAt: body.event.occurredAt, offline: false, replay: false },
|
|
27619
|
+
...body.installs === void 0 ? {} : { installs: body.installs }
|
|
27446
27620
|
});
|
|
27447
27621
|
await ingest(env, action, site);
|
|
27448
27622
|
return Response.json({ accepted: true }, { status: 202 });
|
|
@@ -27661,1434 +27835,6 @@ var readableContext = /* @__PURE__ */ __name((own2, shared) => {
|
|
|
27661
27835
|
return { private: own2 === void 0 ? null : wireEntry(own2), shared: sharedContextWire(shared) };
|
|
27662
27836
|
}, "readableContext");
|
|
27663
27837
|
|
|
27664
|
-
// src/apps/observability.ts
|
|
27665
|
-
var APP_ERROR_DATA_POINT_RESERVE = 50;
|
|
27666
|
-
var observabilityApp = /* @__PURE__ */ __name((slug, app2) => {
|
|
27667
|
-
return { slug, name: app2.name, version: app2.version };
|
|
27668
|
-
}, "observabilityApp");
|
|
27669
|
-
var createDataPointBudget = /* @__PURE__ */ __name(() => {
|
|
27670
|
-
let events = 0;
|
|
27671
|
-
let total = 0;
|
|
27672
|
-
return {
|
|
27673
|
-
take: /* @__PURE__ */ __name((kind2) => {
|
|
27674
|
-
if (total >= MAX_APP_DATA_POINTS_PER_INVOCATION) {
|
|
27675
|
-
return false;
|
|
27676
|
-
}
|
|
27677
|
-
if (kind2 === "event") {
|
|
27678
|
-
if (events >= MAX_APP_DATA_POINTS_PER_INVOCATION - APP_ERROR_DATA_POINT_RESERVE) {
|
|
27679
|
-
return false;
|
|
27680
|
-
}
|
|
27681
|
-
events += 1;
|
|
27682
|
-
}
|
|
27683
|
-
total += 1;
|
|
27684
|
-
return true;
|
|
27685
|
-
}, "take")
|
|
27686
|
-
};
|
|
27687
|
-
}, "createDataPointBudget");
|
|
27688
|
-
var totalLength = /* @__PURE__ */ __name((blobs) => {
|
|
27689
|
-
return blobs.reduce((sum, blob) => {
|
|
27690
|
-
return sum + utf8Length(blob);
|
|
27691
|
-
}, 0);
|
|
27692
|
-
}, "totalLength");
|
|
27693
|
-
var errorBlobs = /* @__PURE__ */ __name((app2, install, record2, rowId) => {
|
|
27694
|
-
const fixed = [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, rowId];
|
|
27695
|
-
let remaining = MAX_APP_DATA_POINT_BLOB_BYTES - totalLength(fixed);
|
|
27696
|
-
const description = truncateUtf8(record2.description, Math.max(0, remaining));
|
|
27697
|
-
remaining -= utf8Length(description);
|
|
27698
|
-
const payload = truncateUtf8(record2.payload ?? "", Math.max(0, remaining));
|
|
27699
|
-
remaining -= utf8Length(payload);
|
|
27700
|
-
const error61 = truncateUtf8(record2.error ?? "", Math.max(0, remaining));
|
|
27701
|
-
return [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, description, payload, error61, rowId];
|
|
27702
|
-
}, "errorBlobs");
|
|
27703
|
-
var eventBlobs = /* @__PURE__ */ __name((app2, install, record2) => {
|
|
27704
|
-
return [app2.slug, app2.name, install.id, install.label, app2.version, record2.name, record2.status];
|
|
27705
|
-
}, "eventBlobs");
|
|
27706
|
-
var writeEvents = /* @__PURE__ */ __name((env, target, byOrd, events) => {
|
|
27707
|
-
for (const [index, event] of events.entries()) {
|
|
27708
|
-
const install = byOrd.get(event.ord);
|
|
27709
|
-
if (install === void 0) {
|
|
27710
|
-
addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
|
|
27711
|
-
continue;
|
|
27712
|
-
}
|
|
27713
|
-
if (!target.budget.take("event")) {
|
|
27714
|
-
addSpanEvent("app observability dropped events at the data-point cap", {
|
|
27715
|
-
level: "warn",
|
|
27716
|
-
attrs: { app: target.app.slug, skipped: events.length - index }
|
|
27717
|
-
});
|
|
27718
|
-
break;
|
|
27719
|
-
}
|
|
27720
|
-
env.APP_EVENTS.writeDataPoint({ indexes: [`${install.id}:${event.status}`], blobs: eventBlobs(target.app, install, event) });
|
|
27721
|
-
}
|
|
27722
|
-
}, "writeEvents");
|
|
27723
|
-
var writeErrors = /* @__PURE__ */ __name((env, target, byOrd, errors) => {
|
|
27724
|
-
for (const [index, saved] of errors.entries()) {
|
|
27725
|
-
const install = byOrd.get(saved.ord);
|
|
27726
|
-
if (install === void 0) {
|
|
27727
|
-
addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
|
|
27728
|
-
continue;
|
|
27729
|
-
}
|
|
27730
|
-
if (!target.budget.take("error")) {
|
|
27731
|
-
addSpanEvent("app observability dropped saved errors at the data-point cap", {
|
|
27732
|
-
level: "warn",
|
|
27733
|
-
attrs: { app: target.app.slug, skipped: errors.length - index }
|
|
27734
|
-
});
|
|
27735
|
-
break;
|
|
27736
|
-
}
|
|
27737
|
-
env.APP_ERRORS.writeDataPoint({
|
|
27738
|
-
indexes: [`${install.id}:${saved.category}`],
|
|
27739
|
-
blobs: errorBlobs(target.app, install, saved, ulid3())
|
|
27740
|
-
});
|
|
27741
|
-
}
|
|
27742
|
-
}, "writeErrors");
|
|
27743
|
-
var guarded = /* @__PURE__ */ __name((target, write) => {
|
|
27744
|
-
try {
|
|
27745
|
-
write();
|
|
27746
|
-
return true;
|
|
27747
|
-
} catch (error61) {
|
|
27748
|
-
addSpanEvent("app observability write failed", { level: "warn", attrs: { app: target.app.slug, error: String(error61) } });
|
|
27749
|
-
return false;
|
|
27750
|
-
}
|
|
27751
|
-
}, "guarded");
|
|
27752
|
-
var writeAppObservabilityRecord = /* @__PURE__ */ __name((env, record2, target) => {
|
|
27753
|
-
const byOrd = new Map(
|
|
27754
|
-
target.installs.map((install) => {
|
|
27755
|
-
return [install.ord, install];
|
|
27756
|
-
})
|
|
27757
|
-
);
|
|
27758
|
-
const eventsWritten = guarded(target, () => {
|
|
27759
|
-
writeEvents(env, target, byOrd, record2.events);
|
|
27760
|
-
});
|
|
27761
|
-
const errorsWritten = guarded(target, () => {
|
|
27762
|
-
writeErrors(env, target, byOrd, record2.errors);
|
|
27763
|
-
});
|
|
27764
|
-
if (!eventsWritten || !errorsWritten) {
|
|
27765
|
-
return;
|
|
27766
|
-
}
|
|
27767
|
-
if (record2.truncated === true) {
|
|
27768
|
-
addSpanEvent("the app recorded more observability than one invocation carries", { level: "warn", attrs: { app: target.app.slug } });
|
|
27769
|
-
}
|
|
27770
|
-
}, "writeAppObservabilityRecord");
|
|
27771
|
-
var writeAppObservability = /* @__PURE__ */ __name((env, response, target) => {
|
|
27772
|
-
const record2 = parseAppObservability(response.headers.get(APP_OBSERVABILITY_HEADER));
|
|
27773
|
-
if (record2 !== null) {
|
|
27774
|
-
writeAppObservabilityRecord(env, record2, target);
|
|
27775
|
-
}
|
|
27776
|
-
}, "writeAppObservability");
|
|
27777
|
-
|
|
27778
|
-
// src/apps/read-bounded.ts
|
|
27779
|
-
var readBoundedBody = /* @__PURE__ */ __name(async (message, maxBytes) => {
|
|
27780
|
-
const reader = message.body?.getReader();
|
|
27781
|
-
if (reader === void 0) {
|
|
27782
|
-
return new Uint8Array(0);
|
|
27783
|
-
}
|
|
27784
|
-
const chunks = [];
|
|
27785
|
-
let received = 0;
|
|
27786
|
-
let result = await reader.read();
|
|
27787
|
-
while (!result.done) {
|
|
27788
|
-
received += result.value.byteLength;
|
|
27789
|
-
if (received > maxBytes) {
|
|
27790
|
-
await reader.cancel();
|
|
27791
|
-
return null;
|
|
27792
|
-
}
|
|
27793
|
-
chunks.push(result.value);
|
|
27794
|
-
result = await reader.read();
|
|
27795
|
-
}
|
|
27796
|
-
const bytes = new Uint8Array(received);
|
|
27797
|
-
let offset = 0;
|
|
27798
|
-
for (const chunk of chunks) {
|
|
27799
|
-
bytes.set(chunk, offset);
|
|
27800
|
-
offset += chunk.byteLength;
|
|
27801
|
-
}
|
|
27802
|
-
return bytes;
|
|
27803
|
-
}, "readBoundedBody");
|
|
27804
|
-
|
|
27805
|
-
// src/apps/spans.ts
|
|
27806
|
-
var adoptAppSpans = /* @__PURE__ */ __name((response) => {
|
|
27807
|
-
const header = response.headers.get(APP_SPANS_HEADER);
|
|
27808
|
-
if (header === null || header.length > MAX_APP_SPANS_BYTES) {
|
|
27809
|
-
return;
|
|
27810
|
-
}
|
|
27811
|
-
const trace = currentTraceContext();
|
|
27812
|
-
if (trace?.parent === void 0) {
|
|
27813
|
-
return;
|
|
27814
|
-
}
|
|
27815
|
-
const record2 = parseAppSpans(header);
|
|
27816
|
-
if (record2 === void 0) {
|
|
27817
|
-
return;
|
|
27818
|
-
}
|
|
27819
|
-
for (const span of adoptSpans(record2.spans, { traceId: trace.traceId, parent: trace.parent })) {
|
|
27820
|
-
appendSpan(span);
|
|
27821
|
-
}
|
|
27822
|
-
if (record2.truncated !== void 0 && record2.truncated > 0) {
|
|
27823
|
-
setSpanAttributes({ "app.spans_truncated": record2.truncated });
|
|
27824
|
-
}
|
|
27825
|
-
}, "adoptAppSpans");
|
|
27826
|
-
|
|
27827
|
-
// src/apps/invoke.ts
|
|
27828
|
-
var APP_INVOCATION_TIMEOUT_MS = 1e4;
|
|
27829
|
-
var APP_HOOK_TIMEOUT_MS = 25e3;
|
|
27830
|
-
var MAX_EMIT_DEPTH = 2;
|
|
27831
|
-
var HOOK_URL = "https://app/__qinto/hook";
|
|
27832
|
-
var invocationContext = /* @__PURE__ */ __name((env) => {
|
|
27833
|
-
const { APPS_KEY_MASTER: master, QINTO_DOMAIN_ID: domainId } = env;
|
|
27834
|
-
if (master === void 0 || domainId === void 0) {
|
|
27835
|
-
return void 0;
|
|
27836
|
-
}
|
|
27837
|
-
return { master, domainId, consentEnforced: consentEnforced(env) };
|
|
27838
|
-
}, "invocationContext");
|
|
27839
|
-
var postHookEnvelope = /* @__PURE__ */ __name(async (env, { binding, key, envelope, timeoutMs }) => {
|
|
27840
|
-
const service = env[binding];
|
|
27841
|
-
if (typeof service?.fetch !== "function") {
|
|
27842
|
-
return void 0;
|
|
27843
|
-
}
|
|
27844
|
-
const headers = { "content-type": "application/json", "x-qinto-app-key": key };
|
|
27845
|
-
const trace = currentTraceContext();
|
|
27846
|
-
if (trace !== void 0) {
|
|
27847
|
-
headers[TRACE_HEADER] = serializeTraceContext(trace);
|
|
27848
|
-
}
|
|
27849
|
-
const response = await service.fetch(HOOK_URL, {
|
|
27850
|
-
method: "POST",
|
|
27851
|
-
headers,
|
|
27852
|
-
body: JSON.stringify(envelope),
|
|
27853
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
27854
|
-
});
|
|
27855
|
-
adoptAppSpans(response);
|
|
27856
|
-
return response;
|
|
27857
|
-
}, "postHookEnvelope");
|
|
27858
|
-
var triggerDepth = /* @__PURE__ */ __name((trigger) => {
|
|
27859
|
-
return trigger.kind === "event" ? trigger.action.origin?.depth ?? 0 : 0;
|
|
27860
|
-
}, "triggerDepth");
|
|
27861
|
-
var stillDeclared = /* @__PURE__ */ __name((target, key, source) => {
|
|
27862
|
-
if (source === "app") {
|
|
27863
|
-
return true;
|
|
27864
|
-
}
|
|
27865
|
-
const { queryParams, cookies } = target.app.capture;
|
|
27866
|
-
return source === "query" ? queryParams.some((entry) => entry.name === key) : cookies.some((entry) => entry.name === key);
|
|
27867
|
-
}, "stillDeclared");
|
|
27868
|
-
var deliverableCaptures = /* @__PURE__ */ __name((target, rows) => {
|
|
27869
|
-
if (rows === void 0) {
|
|
27870
|
-
return void 0;
|
|
27871
|
-
}
|
|
27872
|
-
const deliverable = {};
|
|
27873
|
-
for (const [key, row] of Object.entries(rows)) {
|
|
27874
|
-
if (stillDeclared(target, key, row.source)) {
|
|
27875
|
-
deliverable[key] = { value: row.value, createdAt: row.createdAt, updatedAt: row.updatedAt, type: row.type };
|
|
27876
|
-
}
|
|
27877
|
-
}
|
|
27878
|
-
return Object.keys(deliverable).length === 0 ? void 0 : deliverable;
|
|
27879
|
-
}, "deliverableCaptures");
|
|
27880
|
-
var eventAction = /* @__PURE__ */ __name((target, trigger, snapshot) => {
|
|
27881
|
-
const buckets = target.app.data;
|
|
27882
|
-
return {
|
|
27883
|
-
actionId: trigger.action.actionId,
|
|
27884
|
-
type: trigger.action.type,
|
|
27885
|
-
userId: snapshot.anonId,
|
|
27886
|
-
payload: trigger.action.payload,
|
|
27887
|
-
receivedAt: trigger.action.receivedAt,
|
|
27888
|
-
source: trigger.action.source,
|
|
27889
|
-
...buckets.includes("eventContext") && trigger.action.context !== void 0 ? { context: trigger.action.context } : {},
|
|
27890
|
-
...trigger.action.browserResults?.[target.slug] === void 0 ? {} : { browser: trigger.action.browserResults[target.slug] },
|
|
27891
|
-
...trigger.action.origin !== void 0 ? { origin: trigger.action.origin } : {}
|
|
27892
|
-
};
|
|
27893
|
-
}, "eventAction");
|
|
27894
|
-
var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context) => {
|
|
27895
|
-
const buckets = target.app.data;
|
|
27896
|
-
const geoAllowed = buckets.includes("geo");
|
|
27897
|
-
const currentGeo = trigger.kind === "event" ? trigger.action.geo : trigger.geo;
|
|
27898
|
-
const consented = consentAllows(snapshot.consent, target.slug, target.app.consent.category, context.consentEnforced);
|
|
27899
|
-
const captures = consented ? deliverableCaptures(target, snapshot.captures[target.slug]) : void 0;
|
|
27900
|
-
const visitorContext = deliverableContext({ target, consented, ...snapshot.context });
|
|
27901
|
-
return {
|
|
27902
|
-
v: ENVELOPE_VERSION,
|
|
27903
|
-
kind: trigger.kind,
|
|
27904
|
-
app: { slug: target.slug, version: target.app.version },
|
|
27905
|
-
installs: target.installs,
|
|
27906
|
-
user: {
|
|
27907
|
-
anonId: snapshot.anonId,
|
|
27908
|
-
globalId: snapshot.globalId,
|
|
27909
|
-
...snapshot.createdAt === null ? {} : { createdAt: snapshot.createdAt },
|
|
27910
|
-
// Custom properties intentionally win on key collisions with PII, matching the profile read path.
|
|
27911
|
-
properties: {
|
|
27912
|
-
...buckets.includes("pii") ? snapshot.pii : {},
|
|
27913
|
-
...buckets.includes("customProperties") ? snapshot.custom : {}
|
|
27914
|
-
},
|
|
27915
|
-
...geoAllowed ? { geo: snapshot.userGeo } : {}
|
|
27916
|
-
},
|
|
27917
|
-
...buckets.includes("consentState") ? { consent: { enabled: context.consentEnforced, categories: snapshot.consent.categories, apps: snapshot.consent.apps } } : {},
|
|
27918
|
-
...geoAllowed && currentGeo !== void 0 ? { geo: currentGeo } : {},
|
|
27919
|
-
...trigger.kind === "connect" && trigger.page !== void 0 && buckets.includes("eventContext") ? { page: trigger.page } : {},
|
|
27920
|
-
// Not gated on a capabilities.data bucket: an app only ever sees what it captured or wrote.
|
|
27921
|
-
...captures === void 0 ? {} : { captures },
|
|
27922
|
-
...visitorContext === void 0 ? {} : { context: visitorContext },
|
|
27923
|
-
depth: triggerDepth(trigger),
|
|
27924
|
-
...trigger.kind === "event" ? { action: eventAction(target, trigger, snapshot) } : {}
|
|
27925
|
-
};
|
|
27926
|
-
}, "buildEnvelope");
|
|
27927
|
-
var ingestEmit = /* @__PURE__ */ __name(async (ingest2, { slug, ord, depth, emit, anonId, geo, host, context }) => {
|
|
27928
|
-
const parsed = emitPayloadSchema.safeParse(emit.payload);
|
|
27929
|
-
if (!parsed.success) {
|
|
27930
|
-
addSpanEvent("emit payload rejected", { level: "warn", attrs: { app: slug, ord } });
|
|
27931
|
-
return;
|
|
27932
|
-
}
|
|
27933
|
-
const { event, eventId, properties: properties2 } = parsed.data;
|
|
27934
|
-
const inherited = {
|
|
27935
|
-
...context ?? {},
|
|
27936
|
-
source: "app",
|
|
27937
|
-
occurredAt: emit.occurredAt,
|
|
27938
|
-
...context !== void 0 || host === void 0 ? {} : {
|
|
27939
|
-
network: {
|
|
27940
|
-
...host.ip === void 0 ? {} : { ip: host.ip },
|
|
27941
|
-
...host.userAgent === void 0 ? {} : { userAgent: host.userAgent },
|
|
27942
|
-
...host.locale === void 0 ? {} : { locale: host.locale },
|
|
27943
|
-
provenance: "edge"
|
|
27944
|
-
}
|
|
27945
|
-
},
|
|
27946
|
-
offline: context === void 0 || context.source === "browser" ? false : context.offline,
|
|
27947
|
-
replay: context === void 0 || context.source === "browser" ? false : context.replay
|
|
27948
|
-
};
|
|
27949
|
-
const action = buildAction({
|
|
27950
|
-
type: "track",
|
|
27951
|
-
userId: anonId,
|
|
27952
|
-
source: "app",
|
|
27953
|
-
payload: { event, eventId: emittedEventId(eventId, properties2), properties: properties2 ?? {} },
|
|
27954
|
-
context: inherited,
|
|
27955
|
-
origin: { app: slug, install: ord, depth: depth + 1 },
|
|
27956
|
-
geo,
|
|
27957
|
-
host
|
|
27958
|
-
});
|
|
27959
|
-
await ingest2(action);
|
|
27960
|
-
}, "ingestEmit");
|
|
27961
|
-
var deliverGroupPushes = /* @__PURE__ */ __name(async ({ slug, group, installs, deliverPush }) => {
|
|
27962
|
-
const install = installs.find((entry) => entry.ord === group.ord);
|
|
27963
|
-
for (const push of group.pushes) {
|
|
27964
|
-
const { topic, data } = push;
|
|
27965
|
-
switch (push.broadcast ?? "install") {
|
|
27966
|
-
case "install": {
|
|
27967
|
-
if (install === void 0) {
|
|
27968
|
-
addSpanEvent("push rejected: its group matches no invoked install", { level: "warn", attrs: { app: slug, ord: group.ord } });
|
|
27969
|
-
break;
|
|
27970
|
-
}
|
|
27971
|
-
await deliverPush({ slug, installId: install.id, topic, data, scope: "app" });
|
|
27972
|
-
break;
|
|
27973
|
-
}
|
|
27974
|
-
case "app": {
|
|
27975
|
-
await deliverPush({ slug, installId: null, topic, data, scope: "app" });
|
|
27976
|
-
break;
|
|
27977
|
-
}
|
|
27978
|
-
// Whether this app may broadcast site-wide is settled in `deliverAppPush`, the one path every
|
|
27979
|
-
// delivery funnels through; re-deciding it here would be a second authority to keep in sync.
|
|
27980
|
-
case "site": {
|
|
27981
|
-
await deliverPush({ slug, installId: null, topic, data, scope: "site" });
|
|
27982
|
-
break;
|
|
27983
|
-
}
|
|
27984
|
-
}
|
|
27985
|
-
}
|
|
27986
|
-
}, "deliverGroupPushes");
|
|
27987
|
-
var applyEffects = /* @__PURE__ */ __name(async ({
|
|
27988
|
-
slug,
|
|
27989
|
-
groups,
|
|
27990
|
-
depth,
|
|
27991
|
-
anonId,
|
|
27992
|
-
installs,
|
|
27993
|
-
deliverPush,
|
|
27994
|
-
writeCaptures: writeCaptures2,
|
|
27995
|
-
writeContext: writeContext2,
|
|
27996
|
-
ingest: ingest2,
|
|
27997
|
-
geo,
|
|
27998
|
-
host,
|
|
27999
|
-
context
|
|
28000
|
-
}) => {
|
|
28001
|
-
const captures = groups.flatMap((group) => group.captures);
|
|
28002
|
-
if (captures.length > 0) {
|
|
28003
|
-
await writeCaptures2(slug, captures);
|
|
28004
|
-
}
|
|
28005
|
-
const contextWrites = groups.flatMap((group) => group.context);
|
|
28006
|
-
if (contextWrites.length > 0) {
|
|
28007
|
-
await writeContext2(slug, contextWrites);
|
|
28008
|
-
}
|
|
28009
|
-
for (const group of groups) {
|
|
28010
|
-
await deliverGroupPushes({ slug, group, installs, deliverPush });
|
|
28011
|
-
if (group.emits.length === 0) {
|
|
28012
|
-
continue;
|
|
28013
|
-
}
|
|
28014
|
-
if (depth >= MAX_EMIT_DEPTH) {
|
|
28015
|
-
addSpanEvent("emit rejected at the depth cap", { level: "warn", attrs: { app: slug, ord: group.ord, depth } });
|
|
28016
|
-
continue;
|
|
28017
|
-
}
|
|
28018
|
-
for (const emit of group.emits) {
|
|
28019
|
-
await ingestEmit(ingest2, { slug, ord: group.ord, depth, emit, anonId, geo, host, context });
|
|
28020
|
-
}
|
|
28021
|
-
}
|
|
28022
|
-
}, "applyEffects");
|
|
28023
|
-
var hookResponseBody = /* @__PURE__ */ __name(async (response, slug) => {
|
|
28024
|
-
if (!response.ok) {
|
|
28025
|
-
addSpanEvent("app hook failed", { level: "warn", attrs: { app: slug, status: response.status } });
|
|
28026
|
-
return void 0;
|
|
28027
|
-
}
|
|
28028
|
-
const bytes = await readBoundedBody(response, MAX_RESPONSE_BYTES);
|
|
28029
|
-
if (bytes === null) {
|
|
28030
|
-
addSpanEvent("app hook response rejected", { level: "warn", attrs: { app: slug, reason: "The response exceeds the size cap." } });
|
|
28031
|
-
return void 0;
|
|
28032
|
-
}
|
|
28033
|
-
return new TextDecoder().decode(bytes);
|
|
28034
|
-
}, "hookResponseBody");
|
|
28035
|
-
var runInvokeApp = /* @__PURE__ */ __name(async (env, { target, trigger, snapshot, context, deliverEffects, budget }) => {
|
|
28036
|
-
try {
|
|
28037
|
-
const key = await deriveAppInternalKey(context.master, context.domainId, target.slug);
|
|
28038
|
-
const response = await postHookEnvelope(env, {
|
|
28039
|
-
binding: target.app.binding,
|
|
28040
|
-
key,
|
|
28041
|
-
envelope: buildEnvelope(target, trigger, snapshot, context),
|
|
28042
|
-
timeoutMs: APP_HOOK_TIMEOUT_MS
|
|
28043
|
-
});
|
|
28044
|
-
if (response === void 0) {
|
|
28045
|
-
addSpanEvent("app service binding is missing", { level: "warn", attrs: { app: target.slug, binding: target.app.binding } });
|
|
28046
|
-
return;
|
|
28047
|
-
}
|
|
28048
|
-
writeAppObservability(env, response, {
|
|
28049
|
-
app: observabilityApp(target.slug, target.app),
|
|
28050
|
-
installs: target.installs,
|
|
28051
|
-
budget
|
|
28052
|
-
});
|
|
28053
|
-
setSpanAttributes({ "app.status": response.status });
|
|
28054
|
-
const body = await hookResponseBody(response, target.slug);
|
|
28055
|
-
if (body === void 0) {
|
|
28056
|
-
return;
|
|
28057
|
-
}
|
|
28058
|
-
const verdict = validateEnvelopeResponse(JSON.parse(body), target.installs.length);
|
|
28059
|
-
if (!verdict.ok) {
|
|
28060
|
-
addSpanEvent("app hook response rejected", { level: "warn", attrs: { app: target.slug, reason: verdict.reason } });
|
|
28061
|
-
return;
|
|
28062
|
-
}
|
|
28063
|
-
if (verdict.error !== null) {
|
|
28064
|
-
addSpanEvent("app hook reported an error", { level: "warn", attrs: { app: target.slug, message: redactUrlParams(verdict.error) } });
|
|
28065
|
-
}
|
|
28066
|
-
for (const ord of verdict.effects.rejectedOrds) {
|
|
28067
|
-
addSpanEvent("app effects rejected over the per-install caps", { level: "warn", attrs: { app: target.slug, ord } });
|
|
28068
|
-
}
|
|
28069
|
-
if (verdict.effects.accepted.length === 0) {
|
|
28070
|
-
return;
|
|
28071
|
-
}
|
|
28072
|
-
await deliverEffects({
|
|
28073
|
-
anonId: snapshot.anonId,
|
|
28074
|
-
slug: target.slug,
|
|
28075
|
-
groups: verdict.effects.accepted,
|
|
28076
|
-
depth: triggerDepth(trigger),
|
|
28077
|
-
installs: target.installs,
|
|
28078
|
-
geo: trigger.kind === "event" ? trigger.action.geo : trigger.geo,
|
|
28079
|
-
host: trigger.kind === "event" ? trigger.action.host : trigger.host,
|
|
28080
|
-
context: trigger.kind === "event" ? trigger.action.context : void 0
|
|
28081
|
-
});
|
|
28082
|
-
} catch (error61) {
|
|
28083
|
-
addSpanEvent("app invocation failed", { level: "warn", attrs: { app: target.slug, error: String(error61) } });
|
|
28084
|
-
}
|
|
28085
|
-
}, "runInvokeApp");
|
|
28086
|
-
var invokeApp = /* @__PURE__ */ __name(async (env, input2) => {
|
|
28087
|
-
const { target, trigger } = input2;
|
|
28088
|
-
return traced(
|
|
28089
|
-
`app.${trigger.kind} ${target.slug}`,
|
|
28090
|
-
{
|
|
28091
|
-
attrs: {
|
|
28092
|
-
"app.slug": target.slug,
|
|
28093
|
-
"app.version": target.app.version,
|
|
28094
|
-
"app.binding": target.app.binding,
|
|
28095
|
-
"app.hook": trigger.kind === "event" ? `onEvent.${trigger.action.type}` : "onConnect",
|
|
28096
|
-
"app.installs": target.installs.length
|
|
28097
|
-
}
|
|
28098
|
-
},
|
|
28099
|
-
() => {
|
|
28100
|
-
return runInvokeApp(env, input2);
|
|
28101
|
-
}
|
|
28102
|
-
);
|
|
28103
|
-
}, "invokeApp");
|
|
28104
|
-
|
|
28105
|
-
// src/apps/broker.ts
|
|
28106
|
-
var MAX_RPC_DEPTH = 3;
|
|
28107
|
-
var BROKER_PATHS = ["/__qinto/push", "/__qinto/rpc"];
|
|
28108
|
-
var pushBody = external_exports.object({
|
|
28109
|
-
...identityFields,
|
|
28110
|
-
kind: external_exports.literal("push"),
|
|
28111
|
-
userId: external_exports.string().min(1).max(64),
|
|
28112
|
-
installId: external_exports.string().min(1).max(64).optional(),
|
|
28113
|
-
broadcast: external_exports.enum(PUSH_BROADCASTS).optional(),
|
|
28114
|
-
topic: external_exports.string().min(1).max(64),
|
|
28115
|
-
data: external_exports.unknown().optional()
|
|
28116
|
-
}).strict();
|
|
28117
|
-
var rpcBody = external_exports.object({
|
|
28118
|
-
...identityFields,
|
|
28119
|
-
kind: external_exports.literal("rpc"),
|
|
28120
|
-
// Absent when the caller is an endpoint, a schedule or a ui handler; the callee then sees no user.
|
|
28121
|
-
userId: external_exports.string().min(1).max(64).optional(),
|
|
28122
|
-
callee: external_exports.string().min(1).max(30),
|
|
28123
|
-
fn: external_exports.string().min(1).max(64),
|
|
28124
|
-
args: external_exports.unknown().optional(),
|
|
28125
|
-
installId: external_exports.string().min(1).max(64).optional(),
|
|
28126
|
-
depth: external_exports.number().int().min(0)
|
|
28127
|
-
}).strict();
|
|
28128
|
-
var installsBody = external_exports.object({
|
|
28129
|
-
...identityFields,
|
|
28130
|
-
kind: external_exports.literal("installs"),
|
|
28131
|
-
callee: external_exports.string().min(1).max(30)
|
|
28132
|
-
}).strict();
|
|
28133
|
-
var activeBody = external_exports.object({
|
|
28134
|
-
...identityFields,
|
|
28135
|
-
kind: external_exports.literal("active"),
|
|
28136
|
-
ord: external_exports.number().int().min(1),
|
|
28137
|
-
userId: external_exports.string().min(1).max(64).optional()
|
|
28138
|
-
}).strict();
|
|
28139
|
-
var contextReadBody = external_exports.object({
|
|
28140
|
-
...identityFields,
|
|
28141
|
-
kind: external_exports.literal("contextRead"),
|
|
28142
|
-
userId: external_exports.string().min(1).max(64)
|
|
28143
|
-
}).strict();
|
|
28144
|
-
var contextWriteBody = external_exports.object({
|
|
28145
|
-
...identityFields,
|
|
28146
|
-
kind: external_exports.literal("contextWrite"),
|
|
28147
|
-
userId: external_exports.string().min(1).max(64),
|
|
28148
|
-
writes: external_exports.array(contextWriteSchema).min(1).max(MAX_CONTEXT_WRITES_PER_INSTALL)
|
|
28149
|
-
}).strict();
|
|
28150
|
-
var factsQuery = external_exports.object({
|
|
28151
|
-
kinds: external_exports.array(external_exports.enum(APP_FACT_KINDS)).min(1).max(APP_FACT_KINDS.length).optional(),
|
|
28152
|
-
observedAtGte: external_exports.number().int().nonnegative().safe().optional(),
|
|
28153
|
-
observedAtLt: external_exports.number().int().nonnegative().safe().optional(),
|
|
28154
|
-
limit: external_exports.number().int().min(1).max(MAX_APP_FACTS_PER_PAGE).optional()
|
|
28155
|
-
}).strict().refine((query) => query.observedAtGte === void 0 || query.observedAtLt === void 0 || query.observedAtGte < query.observedAtLt);
|
|
28156
|
-
var factsReadBody = external_exports.object({
|
|
28157
|
-
...identityFields,
|
|
28158
|
-
kind: external_exports.literal("factsRead"),
|
|
28159
|
-
userId: external_exports.string().min(1).max(64),
|
|
28160
|
-
query: factsQuery.optional(),
|
|
28161
|
-
cursor: external_exports.string().min(1).max(4096).optional()
|
|
28162
|
-
}).strict().refine((body) => body.query === void 0 !== (body.cursor === void 0));
|
|
28163
|
-
var identityReadBody = external_exports.object({
|
|
28164
|
-
...identityFields,
|
|
28165
|
-
kind: external_exports.literal("identityRead"),
|
|
28166
|
-
identifier: identifierField2,
|
|
28167
|
-
cursor: external_exports.string().regex(/^\d+$/).optional()
|
|
28168
|
-
}).strict();
|
|
28169
|
-
var brokerBody = external_exports.discriminatedUnion("kind", [
|
|
28170
|
-
pushBody,
|
|
28171
|
-
rpcBody,
|
|
28172
|
-
installsBody,
|
|
28173
|
-
activeBody,
|
|
28174
|
-
contextReadBody,
|
|
28175
|
-
contextWriteBody,
|
|
28176
|
-
factsReadBody,
|
|
28177
|
-
identityReadBody,
|
|
28178
|
-
currencyRatesBody,
|
|
28179
|
-
trackBody
|
|
28180
|
-
]);
|
|
28181
|
-
var BODY_ENVELOPE_ALLOWANCE_BYTES = 2 * 1024;
|
|
28182
|
-
var MAX_BODY_BYTES = MAX_RPC_BYTES + BODY_ENVELOPE_ALLOWANCE_BYTES;
|
|
28183
|
-
var parseJson = /* @__PURE__ */ __name((raw2) => {
|
|
28184
|
-
try {
|
|
28185
|
-
return JSON.parse(raw2);
|
|
28186
|
-
} catch {
|
|
28187
|
-
return null;
|
|
28188
|
-
}
|
|
28189
|
-
}, "parseJson");
|
|
28190
|
-
var factsCursor = external_exports.object({
|
|
28191
|
-
userId: external_exports.string().min(1).max(64),
|
|
28192
|
-
kinds: external_exports.array(external_exports.enum(APP_FACT_KINDS)).min(1).max(APP_FACT_KINDS.length),
|
|
28193
|
-
observedAtGte: external_exports.number().int().nonnegative().safe().nullable(),
|
|
28194
|
-
observedAtLt: external_exports.number().int().nonnegative().safe().nullable(),
|
|
28195
|
-
limit: external_exports.number().int().min(1).max(MAX_APP_FACTS_PER_PAGE),
|
|
28196
|
-
afterRowId: external_exports.number().int().nonnegative().safe(),
|
|
28197
|
-
maxRowId: external_exports.number().int().positive().safe().nullable()
|
|
28198
|
-
}).strict();
|
|
28199
|
-
var cursorKey = /* @__PURE__ */ __name(async (hex3) => {
|
|
28200
|
-
const bytes = Uint8Array.from(hex3.matchAll(/.{2}/g), ([pair]) => Number.parseInt(pair, 16));
|
|
28201
|
-
return crypto.subtle.importKey("raw", bytes, "AES-GCM", false, ["encrypt", "decrypt"]);
|
|
28202
|
-
}, "cursorKey");
|
|
28203
|
-
var encodeBase64Url = /* @__PURE__ */ __name((bytes) => {
|
|
28204
|
-
let binary = "";
|
|
28205
|
-
for (const byte of bytes) {
|
|
28206
|
-
binary += String.fromCharCode(byte);
|
|
28207
|
-
}
|
|
28208
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
28209
|
-
}, "encodeBase64Url");
|
|
28210
|
-
var decodeBase64Url = /* @__PURE__ */ __name((value) => {
|
|
28211
|
-
const base643 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
28212
|
-
const padded = base643.padEnd(Math.ceil(base643.length / 4) * 4, "=");
|
|
28213
|
-
const binary = atob(padded);
|
|
28214
|
-
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
28215
|
-
}, "decodeBase64Url");
|
|
28216
|
-
var encryptFactsCursor = /* @__PURE__ */ __name(async (key, cursor) => {
|
|
28217
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
28218
|
-
const plaintext = new TextEncoder().encode(JSON.stringify(cursor));
|
|
28219
|
-
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, await cursorKey(key), plaintext));
|
|
28220
|
-
const packed = new Uint8Array(iv.byteLength + ciphertext.byteLength);
|
|
28221
|
-
packed.set(iv);
|
|
28222
|
-
packed.set(ciphertext, iv.byteLength);
|
|
28223
|
-
return encodeBase64Url(packed);
|
|
28224
|
-
}, "encryptFactsCursor");
|
|
28225
|
-
var decryptFactsCursor = /* @__PURE__ */ __name(async (key, encoded) => {
|
|
28226
|
-
try {
|
|
28227
|
-
const packed = decodeBase64Url(encoded);
|
|
28228
|
-
if (packed.byteLength <= 12) {
|
|
28229
|
-
return null;
|
|
28230
|
-
}
|
|
28231
|
-
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: packed.slice(0, 12) }, await cursorKey(key), packed.slice(12));
|
|
28232
|
-
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
28233
|
-
} catch {
|
|
28234
|
-
return null;
|
|
28235
|
-
}
|
|
28236
|
-
}, "decryptFactsCursor");
|
|
28237
|
-
var invalidBody = /* @__PURE__ */ __name(() => {
|
|
28238
|
-
return Response.json({ error: "invalid_body" }, { status: 400 });
|
|
28239
|
-
}, "invalidBody");
|
|
28240
|
-
var forbidden = /* @__PURE__ */ __name(() => {
|
|
28241
|
-
return Response.json({ error: "forbidden" }, { status: 403 });
|
|
28242
|
-
}, "forbidden");
|
|
28243
|
-
var factsPermissionDenied = /* @__PURE__ */ __name(() => {
|
|
28244
|
-
return Response.json({ error: "facts_permission_denied" }, { status: 403 });
|
|
28245
|
-
}, "factsPermissionDenied");
|
|
28246
|
-
var identityPermissionDenied = /* @__PURE__ */ __name(() => {
|
|
28247
|
-
return Response.json({ error: "identity_permission_denied" }, { status: 403 });
|
|
28248
|
-
}, "identityPermissionDenied");
|
|
28249
|
-
var handlePush = /* @__PURE__ */ __name(async (env, body) => {
|
|
28250
|
-
if (new TextEncoder().encode(JSON.stringify(body.data ?? null)).length > MAX_PUSH_BYTES) {
|
|
28251
|
-
return invalidBody();
|
|
28252
|
-
}
|
|
28253
|
-
const broadcast = body.broadcast ?? "install";
|
|
28254
|
-
if (broadcast === "install" && body.installId === void 0) {
|
|
28255
|
-
return invalidBody();
|
|
28256
|
-
}
|
|
28257
|
-
const installId = broadcast === "install" ? body.installId : null;
|
|
28258
|
-
if (await rateLimited(env, `${body.slug}:${installId ?? ""}:push`)) {
|
|
28259
|
-
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
28260
|
-
}
|
|
28261
|
-
const app2 = siteRegistry().registry.apps[body.slug];
|
|
28262
|
-
if (broadcast === "site" && (app2 === void 0 || !app2.events.publishes.includes(body.topic) || !app2.data.includes(SHARED_CONTEXT_CAPABILITY))) {
|
|
28263
|
-
return Response.json({ error: "topic_not_public" }, { status: 400 });
|
|
28264
|
-
}
|
|
28265
|
-
await anonStub(env, body.userId).appPush(
|
|
28266
|
-
{ slug: body.slug, installId, topic: body.topic, data: body.data, scope: broadcast === "site" ? "site" : "app" },
|
|
28267
|
-
ownSite(env),
|
|
28268
|
-
currentTraceContext()
|
|
28269
|
-
);
|
|
28270
|
-
return Response.json({ accepted: true }, { status: 202 });
|
|
28271
|
-
}, "handlePush");
|
|
28272
|
-
var resolveInstall = /* @__PURE__ */ __name((callee, installId) => {
|
|
28273
|
-
const active = callee.installs.filter((install) => install.active);
|
|
28274
|
-
if (installId !== void 0) {
|
|
28275
|
-
const install = active.find((entry) => entry.id === installId);
|
|
28276
|
-
return install === void 0 ? "invalid_install" : { id: install.id, ord: install.ord, label: install.label };
|
|
28277
|
-
}
|
|
28278
|
-
const [only, second] = active;
|
|
28279
|
-
return only === void 0 || second !== void 0 ? "install_required" : { id: only.id, ord: only.ord, label: only.label };
|
|
28280
|
-
}, "resolveInstall");
|
|
28281
|
-
var calleeFailed = /* @__PURE__ */ __name((callee, reason) => {
|
|
28282
|
-
addSpanEvent("rpc callee failed", { level: "warn", attrs: { app: callee, reason } });
|
|
28283
|
-
return Response.json({ error: "callee_failed" }, { status: 502 });
|
|
28284
|
-
}, "calleeFailed");
|
|
28285
|
-
var deliverRpcEffects = /* @__PURE__ */ __name(async (env, { body, userId, effects, install }) => {
|
|
28286
|
-
for (const ord of effects.rejectedOrds) {
|
|
28287
|
-
addSpanEvent("app effects rejected over the per-install caps", { level: "warn", attrs: { app: body.callee, ord } });
|
|
28288
|
-
}
|
|
28289
|
-
const site = ownSite(env);
|
|
28290
|
-
await applyEffects({
|
|
28291
|
-
slug: body.callee,
|
|
28292
|
-
groups: effects.accepted,
|
|
28293
|
-
depth: body.depth,
|
|
28294
|
-
anonId: userId,
|
|
28295
|
-
installs: [install],
|
|
28296
|
-
deliverPush: /* @__PURE__ */ __name(async (push) => {
|
|
28297
|
-
await anonStub(env, userId).appPush(push, site, currentTraceContext());
|
|
28298
|
-
}, "deliverPush"),
|
|
28299
|
-
writeCaptures: /* @__PURE__ */ __name(async (slug, writes) => {
|
|
28300
|
-
await anonStub(env, userId).appCaptures({ slug, writes }, site, currentTraceContext());
|
|
28301
|
-
}, "writeCaptures"),
|
|
28302
|
-
writeContext: /* @__PURE__ */ __name(async (slug, writes) => {
|
|
28303
|
-
await anonStub(env, userId).appContext({ slug, writes }, site, currentTraceContext());
|
|
28304
|
-
}, "writeContext"),
|
|
28305
|
-
ingest: /* @__PURE__ */ __name((action) => {
|
|
28306
|
-
return ingest(env, action, site);
|
|
28307
|
-
}, "ingest")
|
|
28308
|
-
});
|
|
28309
|
-
}, "deliverRpcEffects");
|
|
28310
|
-
var runRelayRpc = /* @__PURE__ */ __name(async (env, { context, body, callee, install }) => {
|
|
28311
|
-
let raw2;
|
|
28312
|
-
try {
|
|
28313
|
-
const key = await deriveAppInternalKey(context.master, context.domainId, body.callee);
|
|
28314
|
-
const response = await postHookEnvelope(env, {
|
|
28315
|
-
binding: callee.binding,
|
|
28316
|
-
key,
|
|
28317
|
-
envelope: {
|
|
28318
|
-
v: ENVELOPE_VERSION,
|
|
28319
|
-
kind: "rpc",
|
|
28320
|
-
app: { slug: body.callee, version: callee.version },
|
|
28321
|
-
installs: [install],
|
|
28322
|
-
// No bucket projection for rpc: the callee sees only the subject's id, never its profile.
|
|
28323
|
-
// Omitted entirely when the caller had no visitor, so the callee's ctx.user is null.
|
|
28324
|
-
...body.userId === void 0 ? {} : { user: { anonId: body.userId, globalId: null, properties: {} } },
|
|
28325
|
-
depth: body.depth,
|
|
28326
|
-
// JSON.stringify drops an undefined key, and the envelope requires `args` to be present.
|
|
28327
|
-
rpc: { caller: body.slug, fn: body.fn, args: body.args ?? null }
|
|
28328
|
-
},
|
|
28329
|
-
timeoutMs: APP_INVOCATION_TIMEOUT_MS
|
|
28330
|
-
});
|
|
28331
|
-
if (response === void 0) {
|
|
28332
|
-
return calleeFailed(body.callee, "The callee service binding is missing.");
|
|
28333
|
-
}
|
|
28334
|
-
writeAppObservability(env, response, {
|
|
28335
|
-
app: observabilityApp(body.callee, callee),
|
|
28336
|
-
installs: [install],
|
|
28337
|
-
budget: createDataPointBudget()
|
|
28338
|
-
});
|
|
28339
|
-
setSpanAttributes({ "app.status": response.status });
|
|
28340
|
-
if (!response.ok) {
|
|
28341
|
-
return calleeFailed(body.callee, `The callee answered status ${response.status}.`);
|
|
28342
|
-
}
|
|
28343
|
-
const bytes = await readBoundedBody(response, MAX_RESPONSE_BYTES);
|
|
28344
|
-
if (bytes === null) {
|
|
28345
|
-
return calleeFailed(body.callee, "The response exceeds the size cap.");
|
|
28346
|
-
}
|
|
28347
|
-
raw2 = new TextDecoder().decode(bytes);
|
|
28348
|
-
} catch (error61) {
|
|
28349
|
-
return calleeFailed(body.callee, String(error61));
|
|
28350
|
-
}
|
|
28351
|
-
const verdict = validateEnvelopeResponse(parseJson(raw2), 1);
|
|
28352
|
-
if (!verdict.ok) {
|
|
28353
|
-
return calleeFailed(body.callee, verdict.reason);
|
|
28354
|
-
}
|
|
28355
|
-
if (verdict.error !== null) {
|
|
28356
|
-
const reason = redactUrlParams(verdict.error);
|
|
28357
|
-
if (verdict.errorCode === "invalid_arguments") {
|
|
28358
|
-
return Response.json({ error: "invalid_arguments", message: reason }, { status: 400 });
|
|
28359
|
-
}
|
|
28360
|
-
return calleeFailed(body.callee, reason);
|
|
28361
|
-
}
|
|
28362
|
-
if (verdict.rpc === null) {
|
|
28363
|
-
return calleeFailed(body.callee, "The response carries no rpc result.");
|
|
28364
|
-
}
|
|
28365
|
-
if (body.userId === void 0) {
|
|
28366
|
-
if (verdict.effects.accepted.length > 0) {
|
|
28367
|
-
addSpanEvent("rpc effects dropped: the call carries no subject", { level: "warn", attrs: { app: body.callee } });
|
|
28368
|
-
}
|
|
28369
|
-
} else {
|
|
28370
|
-
await deliverRpcEffects(env, { body, userId: body.userId, effects: verdict.effects, install });
|
|
28371
|
-
}
|
|
28372
|
-
return Response.json({ result: verdict.rpc.result });
|
|
28373
|
-
}, "runRelayRpc");
|
|
28374
|
-
var relayRpc = /* @__PURE__ */ __name(async (env, input2) => {
|
|
28375
|
-
const { body, callee, install } = input2;
|
|
28376
|
-
return traced(
|
|
28377
|
-
`app.rpc ${body.callee}`,
|
|
28378
|
-
{
|
|
28379
|
-
attrs: {
|
|
28380
|
-
"app.slug": body.callee,
|
|
28381
|
-
"app.version": callee.version,
|
|
28382
|
-
"app.binding": callee.binding,
|
|
28383
|
-
"app.hook": `rpc.${body.fn}`,
|
|
28384
|
-
"app.install_id": install.id,
|
|
28385
|
-
"app.install_ord": install.ord,
|
|
28386
|
-
"app.install_label": install.label,
|
|
28387
|
-
"app.rpc_caller": body.slug,
|
|
28388
|
-
"app.rpc_depth": body.depth
|
|
28389
|
-
}
|
|
28390
|
-
},
|
|
28391
|
-
() => {
|
|
28392
|
-
return runRelayRpc(env, input2);
|
|
28393
|
-
}
|
|
28394
|
-
);
|
|
28395
|
-
}, "relayRpc");
|
|
28396
|
-
var handleRpc = /* @__PURE__ */ __name(async (env, context, body) => {
|
|
28397
|
-
if (body.depth > MAX_RPC_DEPTH) {
|
|
28398
|
-
return Response.json({ error: "depth_exceeded" }, { status: 400 });
|
|
28399
|
-
}
|
|
28400
|
-
const callee = siteRegistry().registry.apps[body.callee];
|
|
28401
|
-
if (callee === void 0) {
|
|
28402
|
-
return forbidden();
|
|
28403
|
-
}
|
|
28404
|
-
const visibility = callee.rpc.exposes[body.fn];
|
|
28405
|
-
if (visibility !== "public" && !(visibility === "private" && (callee.rpc.grants[body.slug] ?? []).includes(body.fn))) {
|
|
28406
|
-
return forbidden();
|
|
28407
|
-
}
|
|
28408
|
-
const install = resolveInstall(callee, body.installId);
|
|
28409
|
-
if (install === "invalid_install" || install === "install_required") {
|
|
28410
|
-
return Response.json({ error: install }, { status: 400 });
|
|
28411
|
-
}
|
|
28412
|
-
if (new TextEncoder().encode(JSON.stringify(body.args ?? null)).length > MAX_RPC_BYTES) {
|
|
28413
|
-
return Response.json({ error: "too_large" }, { status: 413 });
|
|
28414
|
-
}
|
|
28415
|
-
if (await rateLimited(env, `${body.slug}:${install.id}:rpc`)) {
|
|
28416
|
-
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
28417
|
-
}
|
|
28418
|
-
return relayRpc(env, { context, body, callee, install });
|
|
28419
|
-
}, "handleRpc");
|
|
28420
|
-
var handleInstalls = /* @__PURE__ */ __name(async (env, body) => {
|
|
28421
|
-
const callee = siteRegistry().registry.apps[body.callee];
|
|
28422
|
-
if (callee === void 0 || !(Object.values(callee.rpc.exposes).includes("public") || (callee.rpc.grants[body.slug] ?? []).length > 0)) {
|
|
28423
|
-
return forbidden();
|
|
28424
|
-
}
|
|
28425
|
-
if (await rateLimited(env, `${body.slug}::installs`)) {
|
|
28426
|
-
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
28427
|
-
}
|
|
28428
|
-
const installs = callee.installs.filter((install) => install.active).map(({ id, label }) => ({ id, label }));
|
|
28429
|
-
return Response.json({ installs });
|
|
28430
|
-
}, "handleInstalls");
|
|
28431
|
-
var handleContextRead = /* @__PURE__ */ __name(async (env, body) => {
|
|
28432
|
-
if (await rateLimited(env, `${body.slug}::context`)) {
|
|
28433
|
-
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
28434
|
-
}
|
|
28435
|
-
return Response.json(await anonStub(env, body.userId).getAppContext(body.slug, ownSite(env), currentTraceContext()));
|
|
28436
|
-
}, "handleContextRead");
|
|
28437
|
-
var handleContextWrite = /* @__PURE__ */ __name(async (env, body) => {
|
|
28438
|
-
if (await rateLimited(env, `${body.slug}::context`)) {
|
|
28439
|
-
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
28440
|
-
}
|
|
28441
|
-
const stored = await anonStub(env, body.userId).appContext({ slug: body.slug, writes: body.writes }, ownSite(env), currentTraceContext());
|
|
28442
|
-
return Response.json({ stored }, { status: 202 });
|
|
28443
|
-
}, "handleContextWrite");
|
|
28444
|
-
var initialFactsCursor = /* @__PURE__ */ __name((body) => {
|
|
28445
|
-
const query = body.query;
|
|
28446
|
-
return {
|
|
28447
|
-
userId: body.userId,
|
|
28448
|
-
kinds: query.kinds ?? [...APP_FACT_KINDS],
|
|
28449
|
-
observedAtGte: query.observedAtGte ?? null,
|
|
28450
|
-
observedAtLt: query.observedAtLt ?? null,
|
|
28451
|
-
limit: query.limit ?? MAX_APP_FACTS_PER_PAGE,
|
|
28452
|
-
afterRowId: 0,
|
|
28453
|
-
maxRowId: null
|
|
28454
|
-
};
|
|
28455
|
-
}, "initialFactsCursor");
|
|
28456
|
-
var handleFactsRead = /* @__PURE__ */ __name(async (env, body) => {
|
|
28457
|
-
const app2 = siteRegistry().registry.apps[body.slug];
|
|
28458
|
-
if (app2 === void 0 || !app2.data.includes("facts")) {
|
|
28459
|
-
return factsPermissionDenied();
|
|
28460
|
-
}
|
|
28461
|
-
if (await rateLimited(env, `${body.slug}::facts`)) {
|
|
28462
|
-
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
28463
|
-
}
|
|
28464
|
-
const decoded = body.cursor === void 0 ? initialFactsCursor(body) : await decryptFactsCursor(body.key, body.cursor);
|
|
28465
|
-
if (decoded === null || decoded.userId !== body.userId || body.cursor !== void 0 && decoded.maxRowId === null) {
|
|
28466
|
-
return Response.json({ error: "invalid_cursor" }, { status: 400 });
|
|
28467
|
-
}
|
|
28468
|
-
const query = {
|
|
28469
|
-
kinds: decoded.kinds,
|
|
28470
|
-
observedAtGte: decoded.observedAtGte,
|
|
28471
|
-
observedAtLt: decoded.observedAtLt,
|
|
28472
|
-
limit: decoded.limit,
|
|
28473
|
-
afterRowId: decoded.afterRowId,
|
|
28474
|
-
maxRowId: decoded.maxRowId
|
|
28475
|
-
};
|
|
28476
|
-
const page = await anonStub(env, body.userId).getAppFacts(body.slug, query, ownSite(env), currentTraceContext());
|
|
28477
|
-
if (page === null) {
|
|
28478
|
-
return factsPermissionDenied();
|
|
28479
|
-
}
|
|
28480
|
-
const nextCursor = page.next === null ? null : await encryptFactsCursor(body.key, {
|
|
28481
|
-
...decoded,
|
|
28482
|
-
afterRowId: page.next.afterRowId,
|
|
28483
|
-
maxRowId: page.next.maxRowId
|
|
28484
|
-
});
|
|
28485
|
-
return Response.json({ items: page.items, nextCursor });
|
|
28486
|
-
}, "handleFactsRead");
|
|
28487
|
-
var handleIdentityRead = /* @__PURE__ */ __name(async (env, body) => {
|
|
28488
|
-
const app2 = siteRegistry().registry.apps[body.slug];
|
|
28489
|
-
if (app2 === void 0 || !app2.data.includes(IDENTITY_CAPABILITY)) {
|
|
28490
|
-
return identityPermissionDenied();
|
|
28491
|
-
}
|
|
28492
|
-
if (await rateLimited(env, `${body.slug}::identity`)) {
|
|
28493
|
-
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
28494
|
-
}
|
|
28495
|
-
const person2 = await lookupPersonForApp(env, body.slug, body.identifier, body.cursor === void 0 ? 0 : Number(body.cursor));
|
|
28496
|
-
if (person2 === null) {
|
|
28497
|
-
return identityPermissionDenied();
|
|
28498
|
-
}
|
|
28499
|
-
return Response.json(person2);
|
|
28500
|
-
}, "handleIdentityRead");
|
|
28501
|
-
var handleActive = /* @__PURE__ */ __name(async (env, body) => {
|
|
28502
|
-
const app2 = siteRegistry().registry.apps[body.slug];
|
|
28503
|
-
if (app2 === void 0) {
|
|
28504
|
-
return forbidden();
|
|
28505
|
-
}
|
|
28506
|
-
if (!app2.installs.some((install) => install.active && install.ord === body.ord)) {
|
|
28507
|
-
return forbidden();
|
|
28508
|
-
}
|
|
28509
|
-
if (body.userId !== void 0 && !await anonStub(env, body.userId).appAllowed(body.slug, ownSite(env), currentTraceContext())) {
|
|
28510
|
-
return forbidden();
|
|
28511
|
-
}
|
|
28512
|
-
return Response.json({ active: true });
|
|
28513
|
-
}, "handleActive");
|
|
28514
|
-
var handleBroker = /* @__PURE__ */ __name(async (request, env, ctx) => {
|
|
28515
|
-
const bytes = await readBoundedBody(request, MAX_BODY_BYTES);
|
|
28516
|
-
if (bytes === null) {
|
|
28517
|
-
return Response.json({ error: "too_large" }, { status: 413 });
|
|
28518
|
-
}
|
|
28519
|
-
const candidate = parseJson(new TextDecoder().decode(bytes));
|
|
28520
|
-
const parsed = brokerBody.safeParse(candidate);
|
|
28521
|
-
if (!parsed.success) {
|
|
28522
|
-
if (typeof candidate === "object" && candidate !== null && "kind" in candidate && candidate.kind === "factsRead") {
|
|
28523
|
-
const malformedFacts = candidate;
|
|
28524
|
-
const error61 = malformedFacts.cursor !== void 0 && malformedFacts.query === void 0 ? "invalid_cursor" : "invalid_query";
|
|
28525
|
-
return Response.json({ error: error61 }, { status: 400 });
|
|
28526
|
-
}
|
|
28527
|
-
return invalidBody();
|
|
28528
|
-
}
|
|
28529
|
-
const body = parsed.data;
|
|
28530
|
-
if (siteRegistry().registry.apps[body.slug] === void 0) {
|
|
28531
|
-
return forbidden();
|
|
28532
|
-
}
|
|
28533
|
-
const context = invocationContext(env);
|
|
28534
|
-
if (context === void 0) {
|
|
28535
|
-
return forbidden();
|
|
28536
|
-
}
|
|
28537
|
-
const expected = await deriveAppInternalKey(context.master, context.domainId, body.slug);
|
|
28538
|
-
if (!constantTimeEqual(expected, body.key)) {
|
|
28539
|
-
return forbidden();
|
|
28540
|
-
}
|
|
28541
|
-
switch (body.kind) {
|
|
28542
|
-
case "push": {
|
|
28543
|
-
return handlePush(env, body);
|
|
28544
|
-
}
|
|
28545
|
-
case "rpc": {
|
|
28546
|
-
return handleRpc(env, context, body);
|
|
28547
|
-
}
|
|
28548
|
-
case "installs": {
|
|
28549
|
-
return handleInstalls(env, body);
|
|
28550
|
-
}
|
|
28551
|
-
case "active": {
|
|
28552
|
-
return handleActive(env, body);
|
|
28553
|
-
}
|
|
28554
|
-
case "contextRead": {
|
|
28555
|
-
return handleContextRead(env, body);
|
|
28556
|
-
}
|
|
28557
|
-
case "contextWrite": {
|
|
28558
|
-
return handleContextWrite(env, body);
|
|
28559
|
-
}
|
|
28560
|
-
case "factsRead": {
|
|
28561
|
-
return handleFactsRead(env, body);
|
|
28562
|
-
}
|
|
28563
|
-
case "identityRead": {
|
|
28564
|
-
return handleIdentityRead(env, body);
|
|
28565
|
-
}
|
|
28566
|
-
case "currencyRates": {
|
|
28567
|
-
return handleCurrencyRates(ctx);
|
|
28568
|
-
}
|
|
28569
|
-
case "track": {
|
|
28570
|
-
return handleTrack(env, body);
|
|
28571
|
-
}
|
|
28572
|
-
}
|
|
28573
|
-
}, "handleBroker");
|
|
28574
|
-
var brokerRoutes = new Hono2();
|
|
28575
|
-
var dispatchGated = /* @__PURE__ */ __name((c) => {
|
|
28576
|
-
if (c.env.DEPLOY_TARGET !== "dispatch") {
|
|
28577
|
-
return c.notFound();
|
|
28578
|
-
}
|
|
28579
|
-
return handleBroker(c.req.raw, c.env, c.executionCtx);
|
|
28580
|
-
}, "dispatchGated");
|
|
28581
|
-
for (const path of BROKER_PATHS) {
|
|
28582
|
-
brokerRoutes.post(path, dispatchGated);
|
|
28583
|
-
}
|
|
28584
|
-
var Broker = class extends WorkerEntrypoint {
|
|
28585
|
-
static {
|
|
28586
|
-
__name(this, "Broker");
|
|
28587
|
-
}
|
|
28588
|
-
fetch(request) {
|
|
28589
|
-
const trace = parseTraceContext(request.headers.get(TRACE_HEADER));
|
|
28590
|
-
return runInvocation({ name: "broker", trace }, () => {
|
|
28591
|
-
return handleBroker(request, this.env, this.ctx);
|
|
28592
|
-
});
|
|
28593
|
-
}
|
|
28594
|
-
};
|
|
28595
|
-
|
|
28596
|
-
// src/apps-control.ts
|
|
28597
|
-
import controlText from "./apps-control.json";
|
|
28598
|
-
var apps_control_default = controlText;
|
|
28599
|
-
|
|
28600
|
-
// src/apps/control-targets.ts
|
|
28601
|
-
var controlTargetsSchema = external_exports.object({
|
|
28602
|
-
v: external_exports.literal(1),
|
|
28603
|
-
apps: external_exports.record(
|
|
28604
|
-
external_exports.string(),
|
|
28605
|
-
external_exports.object({
|
|
28606
|
-
binding: external_exports.string().min(1),
|
|
28607
|
-
versions: external_exports.array(external_exports.object({ version: external_exports.string().min(1), versionId: external_exports.string().min(1) }).strict()).min(1)
|
|
28608
|
-
}).strict()
|
|
28609
|
-
)
|
|
28610
|
-
}).strict();
|
|
28611
|
-
var EMPTY_TARGETS = Object.freeze({ v: 1, apps: Object.freeze({}) });
|
|
28612
|
-
var parseControlTargets = /* @__PURE__ */ __name((raw2) => {
|
|
28613
|
-
try {
|
|
28614
|
-
const parsed = controlTargetsSchema.safeParse(JSON.parse(raw2));
|
|
28615
|
-
return parsed.success ? parsed.data : EMPTY_TARGETS;
|
|
28616
|
-
} catch {
|
|
28617
|
-
return EMPTY_TARGETS;
|
|
28618
|
-
}
|
|
28619
|
-
}, "parseControlTargets");
|
|
28620
|
-
var cachedRaw3;
|
|
28621
|
-
var cachedTargets = EMPTY_TARGETS;
|
|
28622
|
-
var controlTargets = /* @__PURE__ */ __name(() => {
|
|
28623
|
-
if (apps_control_default !== cachedRaw3) {
|
|
28624
|
-
cachedTargets = parseControlTargets(apps_control_default);
|
|
28625
|
-
cachedRaw3 = apps_control_default;
|
|
28626
|
-
}
|
|
28627
|
-
return cachedTargets;
|
|
28628
|
-
}, "controlTargets");
|
|
28629
|
-
|
|
28630
|
-
// src/apps/control.ts
|
|
28631
|
-
var CONTROL_PATH = "/__qinto/control";
|
|
28632
|
-
var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
|
|
28633
|
-
var observabilityTarget = /* @__PURE__ */ __name((envelope) => {
|
|
28634
|
-
const installs = envelope.schedule?.installs ?? (envelope.install === void 0 ? [] : [envelope.install]);
|
|
28635
|
-
const app2 = siteRegistry().registry.apps[envelope.app.slug];
|
|
28636
|
-
if (installs.length === 0 || app2 === void 0) {
|
|
28637
|
-
return void 0;
|
|
28638
|
-
}
|
|
28639
|
-
return {
|
|
28640
|
-
app: observabilityApp(envelope.app.slug, { name: app2.name, version: envelope.app.version }),
|
|
28641
|
-
installs,
|
|
28642
|
-
budget: createDataPointBudget()
|
|
28643
|
-
};
|
|
28644
|
-
}, "observabilityTarget");
|
|
28645
|
-
var writeControlObservability = /* @__PURE__ */ __name((env, upstream, envelope) => {
|
|
28646
|
-
const target = observabilityTarget(envelope);
|
|
28647
|
-
if (target !== void 0) {
|
|
28648
|
-
writeAppObservability(env, upstream, target);
|
|
28649
|
-
}
|
|
28650
|
-
}, "writeControlObservability");
|
|
28651
|
-
var timedOutAction = /* @__PURE__ */ __name((envelope) => {
|
|
28652
|
-
if (envelope.schedule !== void 0) {
|
|
28653
|
-
return `schedules.${envelope.schedule.id}`;
|
|
28654
|
-
}
|
|
28655
|
-
if (envelope.tool !== void 0) {
|
|
28656
|
-
return `tools.${envelope.tool.name}`;
|
|
28657
|
-
}
|
|
28658
|
-
return envelope.lifecycle === void 0 ? envelope.kind : `lifecycle.${envelope.lifecycle.type}`;
|
|
28659
|
-
}, "timedOutAction");
|
|
28660
|
-
var writeControlTimeout = /* @__PURE__ */ __name((env, envelope, deadlineMs) => {
|
|
28661
|
-
const target = observabilityTarget(envelope);
|
|
28662
|
-
if (target === void 0) {
|
|
28663
|
-
return;
|
|
28664
|
-
}
|
|
28665
|
-
const action = timedOutAction(envelope);
|
|
28666
|
-
const description = `${action} did not answer within ${deadlineMs / 1e3} s, so the site stopped waiting.`;
|
|
28667
|
-
writeAppObservabilityRecord(
|
|
28668
|
-
env,
|
|
28669
|
-
{
|
|
28670
|
-
v: APP_OBSERVABILITY_VERSION,
|
|
28671
|
-
events: [],
|
|
28672
|
-
errors: target.installs.map((install) => {
|
|
28673
|
-
return { ord: install.ord, category: "internal", action, description };
|
|
28674
|
-
})
|
|
28675
|
-
},
|
|
28676
|
-
target
|
|
28677
|
-
);
|
|
28678
|
-
}, "writeControlTimeout");
|
|
28679
|
-
var forwardControl = /* @__PURE__ */ __name(async (env, service, envelope, init) => {
|
|
28680
|
-
const deadlineMs = CONTROL_DEADLINES_MS[envelope.kind].site;
|
|
28681
|
-
try {
|
|
28682
|
-
return await service.fetch("https://app/__qinto/control", { method: "POST", ...init, signal: AbortSignal.timeout(deadlineMs) });
|
|
28683
|
-
} catch (error61) {
|
|
28684
|
-
if (!(error61 instanceof Error && error61.name === "TimeoutError")) {
|
|
28685
|
-
throw error61;
|
|
28686
|
-
}
|
|
28687
|
-
writeControlTimeout(env, envelope, deadlineMs);
|
|
28688
|
-
return void 0;
|
|
28689
|
-
}
|
|
28690
|
-
}, "forwardControl");
|
|
28691
|
-
var textBody = /* @__PURE__ */ __name(async (request) => {
|
|
28692
|
-
const bytes = await readBoundedBody(request, MAX_CONTROL_BYTES);
|
|
28693
|
-
return bytes === null ? null : new TextDecoder().decode(bytes);
|
|
28694
|
-
}, "textBody");
|
|
28695
|
-
var parseJson2 = /* @__PURE__ */ __name((value) => {
|
|
28696
|
-
try {
|
|
28697
|
-
return JSON.parse(value);
|
|
28698
|
-
} catch {
|
|
28699
|
-
return void 0;
|
|
28700
|
-
}
|
|
28701
|
-
}, "parseJson");
|
|
28702
|
-
var authenticateControl = /* @__PURE__ */ __name(async (request, env, body) => {
|
|
28703
|
-
const timestamp = request.headers.get(CONTROL_TIMESTAMP_HEADER);
|
|
28704
|
-
const signature = request.headers.get(CONTROL_SIGNATURE_HEADER);
|
|
28705
|
-
const { APPS_KEY_MASTER: master, QINTO_CONTROL_KEY: controlKey, QINTO_DOMAIN_ID: domainId } = env;
|
|
28706
|
-
if (timestamp === null || signature === null || master === void 0 || controlKey === void 0 || domainId === void 0) {
|
|
28707
|
-
return null;
|
|
28708
|
-
}
|
|
28709
|
-
const timestampMs = Number(timestamp);
|
|
28710
|
-
if (!Number.isSafeInteger(timestampMs) || Math.abs(Date.now() - timestampMs) > MAX_CLOCK_SKEW_MS) {
|
|
28711
|
-
return null;
|
|
28712
|
-
}
|
|
28713
|
-
return await verifyControlRequestSignature(controlKey, timestamp, body, signature) ? { master, domainId } : null;
|
|
28714
|
-
}, "authenticateControl");
|
|
28715
|
-
var handleControl = /* @__PURE__ */ __name(async (request, env) => {
|
|
28716
|
-
const body = await textBody(request);
|
|
28717
|
-
if (body === null) {
|
|
28718
|
-
return Response.json({ error: "too_large" }, { status: 413 });
|
|
28719
|
-
}
|
|
28720
|
-
const authentication = await authenticateControl(request, env, body);
|
|
28721
|
-
if (authentication === null) {
|
|
28722
|
-
return Response.json({ error: "unauthorized" }, { status: 401 });
|
|
28723
|
-
}
|
|
28724
|
-
const raw2 = parseJson2(body);
|
|
28725
|
-
if (raw2 === void 0) {
|
|
28726
|
-
return Response.json({ error: "invalid_body" }, { status: 400 });
|
|
28727
|
-
}
|
|
28728
|
-
const parsed = controlEnvelopeSchema.safeParse(raw2);
|
|
28729
|
-
if (!parsed.success) {
|
|
28730
|
-
return Response.json({ error: "invalid_body" }, { status: 400 });
|
|
28731
|
-
}
|
|
28732
|
-
const envelope = parsed.data;
|
|
28733
|
-
const target = controlTargets().apps[envelope.app.slug];
|
|
28734
|
-
if (target === void 0) {
|
|
28735
|
-
return Response.json({ error: "target_missing" }, { status: 404 });
|
|
28736
|
-
}
|
|
28737
|
-
if (!target.versions.some((version2) => version2.version === envelope.app.version)) {
|
|
28738
|
-
return Response.json({ error: "target_version_mismatch" }, { status: 409 });
|
|
28739
|
-
}
|
|
28740
|
-
const service = env[target.binding];
|
|
28741
|
-
if (typeof service?.fetch !== "function") {
|
|
28742
|
-
return Response.json({ error: "binding_missing" }, { status: 503 });
|
|
28743
|
-
}
|
|
28744
|
-
const headers = {
|
|
28745
|
-
"content-type": "application/json",
|
|
28746
|
-
"x-qinto-app-key": await deriveAppInternalKey(authentication.master, authentication.domainId, envelope.app.slug)
|
|
28747
|
-
};
|
|
28748
|
-
const trace = currentTraceContext();
|
|
28749
|
-
if (trace !== void 0) {
|
|
28750
|
-
headers[TRACE_HEADER] = serializeTraceContext(trace);
|
|
28751
|
-
}
|
|
28752
|
-
const upstream = await forwardControl(env, service, envelope, { headers, body });
|
|
28753
|
-
if (upstream === void 0) {
|
|
28754
|
-
return Response.json({ error: "upstream_timeout" }, { status: 504 });
|
|
28755
|
-
}
|
|
28756
|
-
adoptAppSpans(upstream);
|
|
28757
|
-
writeControlObservability(env, upstream, envelope);
|
|
28758
|
-
const responseBytes = await readBoundedBody(upstream, MAX_CONTROL_BYTES);
|
|
28759
|
-
if (responseBytes === null) {
|
|
28760
|
-
return Response.json({ error: "invalid_response" }, { status: 502 });
|
|
28761
|
-
}
|
|
28762
|
-
const responseBody = parseJson2(new TextDecoder().decode(responseBytes));
|
|
28763
|
-
if (responseBody === void 0) {
|
|
28764
|
-
return Response.json({ error: "invalid_response" }, { status: 502 });
|
|
28765
|
-
}
|
|
28766
|
-
const response = controlResponseSchema.safeParse(responseBody);
|
|
28767
|
-
if (!upstream.ok || !response.success) {
|
|
28768
|
-
return Response.json({ error: "invalid_response" }, { status: 502 });
|
|
28769
|
-
}
|
|
28770
|
-
return Response.json(response.data);
|
|
28771
|
-
}, "handleControl");
|
|
28772
|
-
var appControlRoutes = new Hono2().post(CONTROL_PATH, (c) => {
|
|
28773
|
-
return handleControl(c.req.raw, c.env);
|
|
28774
|
-
});
|
|
28775
|
-
|
|
28776
|
-
// ../../libs/worker-access-token/src/endpoints.ts
|
|
28777
|
-
var APP_ENDPOINT_PREFIX = "/app/";
|
|
28778
|
-
var MAX_CUSTOM_ENDPOINT_LENGTH = 128;
|
|
28779
|
-
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
28780
|
-
var HTTP_METHOD_BIT = { GET: 1, POST: 2, PUT: 4, PATCH: 8, DELETE: 16 };
|
|
28781
|
-
var ALL_METHODS_MASK = HTTP_METHODS.reduce((mask, method) => mask | HTTP_METHOD_BIT[method], 0);
|
|
28782
|
-
var ALLOWED_CHARS = /^[A-Za-z0-9._~/-]+$/;
|
|
28783
|
-
var matchesAppEndpoint = /* @__PURE__ */ __name((endpoints, path, method) => {
|
|
28784
|
-
const bit = HTTP_METHODS.includes(method) ? HTTP_METHOD_BIT[method] : 0;
|
|
28785
|
-
return endpoints.some((endpoint) => {
|
|
28786
|
-
if ((endpoint.methods & bit) === 0) {
|
|
28787
|
-
return false;
|
|
28788
|
-
}
|
|
28789
|
-
return endpoint.path.endsWith("/*") ? path.startsWith(endpoint.path.slice(0, -1)) : path === endpoint.path;
|
|
28790
|
-
});
|
|
28791
|
-
}, "matchesAppEndpoint");
|
|
28792
|
-
var validateAppEndpoint = /* @__PURE__ */ __name((raw2) => {
|
|
28793
|
-
if (!Number.isInteger(raw2.methods) || raw2.methods <= 0 || raw2.methods > ALL_METHODS_MASK) {
|
|
28794
|
-
return { ok: false, reason: "Choose at least one HTTP method." };
|
|
28795
|
-
}
|
|
28796
|
-
const path = raw2.path.trim();
|
|
28797
|
-
if (!path.startsWith(APP_ENDPOINT_PREFIX)) {
|
|
28798
|
-
return { ok: false, reason: "Custom endpoints must start with /app/." };
|
|
28799
|
-
}
|
|
28800
|
-
if (path.length > MAX_CUSTOM_ENDPOINT_LENGTH) {
|
|
28801
|
-
return { ok: false, reason: `Custom endpoint must be ${MAX_CUSTOM_ENDPOINT_LENGTH} characters or fewer.` };
|
|
28802
|
-
}
|
|
28803
|
-
const isWildcard = path.endsWith("/*");
|
|
28804
|
-
if (path.includes("*") && (!isWildcard || path.indexOf("*") !== path.length - 1)) {
|
|
28805
|
-
return { ok: false, reason: "A wildcard is only allowed as a trailing /* (e.g. /app/reports/*)." };
|
|
28806
|
-
}
|
|
28807
|
-
if (!isWildcard && path.endsWith("/")) {
|
|
28808
|
-
return { ok: false, reason: "Custom endpoint must not end with a slash." };
|
|
28809
|
-
}
|
|
28810
|
-
const body = isWildcard ? path.slice(0, -1) : path;
|
|
28811
|
-
if (!ALLOWED_CHARS.test(body)) {
|
|
28812
|
-
return { ok: false, reason: "Custom endpoint contains invalid characters." };
|
|
28813
|
-
}
|
|
28814
|
-
if (body.includes("..")) {
|
|
28815
|
-
return { ok: false, reason: 'Custom endpoint must not contain "..".' };
|
|
28816
|
-
}
|
|
28817
|
-
return { ok: true, value: { methods: raw2.methods, path } };
|
|
28818
|
-
}, "validateAppEndpoint");
|
|
28819
|
-
|
|
28820
|
-
// ../../libs/worker-access-token/src/scopes.ts
|
|
28821
|
-
var SCOPE = {
|
|
28822
|
-
track: 1,
|
|
28823
|
-
identify: 2,
|
|
28824
|
-
alias: 4,
|
|
28825
|
-
consentWrite: 8,
|
|
28826
|
-
propertiesRead: 16,
|
|
28827
|
-
consentRead: 32,
|
|
28828
|
-
sessionRead: 64,
|
|
28829
|
-
idGraphInspect: 128,
|
|
28830
|
-
capture: 256
|
|
28831
|
-
};
|
|
28832
|
-
var ALL_SCOPE_MASK = Object.values(SCOPE).reduce((mask, bit) => mask | bit, 0);
|
|
28833
|
-
var WORKER_ACCESS_TOKEN_SCOPES = [
|
|
28834
|
-
{ key: "track", bit: SCOPE.track, label: "Track events", description: "POST /v1/track \u2014 record events a user performed." },
|
|
28835
|
-
{ key: "identify", bit: SCOPE.identify, label: "Identify users", description: "POST /v1/identify \u2014 attach identity traits to a user." },
|
|
28836
|
-
{ key: "alias", bit: SCOPE.alias, label: "Link identifiers", description: "POST /v1/alias \u2014 link an identifier of your own to a user." },
|
|
28837
|
-
{
|
|
28838
|
-
key: "consentWrite",
|
|
28839
|
-
bit: SCOPE.consentWrite,
|
|
28840
|
-
label: "Save consent",
|
|
28841
|
-
description: "POST /v1/consent \u2014 record a user\u2019s consent choices."
|
|
28842
|
-
},
|
|
28843
|
-
{
|
|
28844
|
-
key: "propertiesRead",
|
|
28845
|
-
bit: SCOPE.propertiesRead,
|
|
28846
|
-
label: "Read properties",
|
|
28847
|
-
description: "GET /v1/properties \u2014 read the properties stored for a user."
|
|
28848
|
-
},
|
|
28849
|
-
{
|
|
28850
|
-
key: "consentRead",
|
|
28851
|
-
bit: SCOPE.consentRead,
|
|
28852
|
-
label: "Read consent",
|
|
28853
|
-
description: "GET /v1/consent \u2014 read the consent stored for a user."
|
|
28854
|
-
},
|
|
28855
|
-
{
|
|
28856
|
-
key: "sessionRead",
|
|
28857
|
-
bit: SCOPE.sessionRead,
|
|
28858
|
-
label: "Read session ids",
|
|
28859
|
-
description: "GET /v1/session, GET /v1/person \u2014 read the anonymous and person ids for a user, and the sessions linked to a person."
|
|
28860
|
-
},
|
|
28861
|
-
{
|
|
28862
|
-
key: "capture",
|
|
28863
|
-
bit: SCOPE.capture,
|
|
28864
|
-
label: "Write captures",
|
|
28865
|
-
description: "POST /v1/capture \u2014 record click ids and vendor ids an installed app declares."
|
|
28866
|
-
}
|
|
28867
|
-
];
|
|
28868
|
-
|
|
28869
|
-
// ../../libs/worker-access-token/src/env.ts
|
|
28870
|
-
var validMask = /* @__PURE__ */ __name((mask) => {
|
|
28871
|
-
return typeof mask === "number" && Number.isSafeInteger(mask) && mask >= 0 && mask <= ALL_SCOPE_MASK;
|
|
28872
|
-
}, "validMask");
|
|
28873
|
-
var validApp = /* @__PURE__ */ __name((app2) => {
|
|
28874
|
-
return app2 === void 0 || typeof app2 === "string" && app2.length > 0 && app2.length <= 30;
|
|
28875
|
-
}, "validApp");
|
|
28876
|
-
var decodeEndpoints = /* @__PURE__ */ __name((value) => {
|
|
28877
|
-
if (value === void 0) {
|
|
28878
|
-
return { endpoints: [], dropped: 0 };
|
|
28879
|
-
}
|
|
28880
|
-
if (!Array.isArray(value)) {
|
|
28881
|
-
return { endpoints: [], dropped: 1 };
|
|
28882
|
-
}
|
|
28883
|
-
const endpoints = [];
|
|
28884
|
-
let dropped = 0;
|
|
28885
|
-
for (const encoded of value) {
|
|
28886
|
-
if (!Array.isArray(encoded) || encoded.length !== 2 || typeof encoded[0] !== "number" || typeof encoded[1] !== "string") {
|
|
28887
|
-
dropped += 1;
|
|
28888
|
-
continue;
|
|
28889
|
-
}
|
|
28890
|
-
const result = validateAppEndpoint({ methods: encoded[0], path: encoded[1] });
|
|
28891
|
-
if (!result.ok) {
|
|
28892
|
-
dropped += 1;
|
|
28893
|
-
continue;
|
|
28894
|
-
}
|
|
28895
|
-
endpoints.push(Object.freeze(result.value));
|
|
28896
|
-
}
|
|
28897
|
-
return { endpoints: Object.freeze(endpoints), dropped };
|
|
28898
|
-
}, "decodeEndpoints");
|
|
28899
|
-
var parseTokenEntry = /* @__PURE__ */ __name((value) => {
|
|
28900
|
-
if (typeof value === "number") {
|
|
28901
|
-
return { entry: validMask(value) && value > 0 ? Object.freeze({ mask: value, app: null, endpoints: null }) : null, dropped: 0 };
|
|
28902
|
-
}
|
|
28903
|
-
if (typeof value !== "object" || value === null) {
|
|
28904
|
-
return { entry: null, dropped: 0 };
|
|
28905
|
-
}
|
|
28906
|
-
const entry = value;
|
|
28907
|
-
if (!validMask(entry.m) || !validApp(entry.a)) {
|
|
28908
|
-
return { entry: null, dropped: 0 };
|
|
28909
|
-
}
|
|
28910
|
-
const decoded = decodeEndpoints(entry.e);
|
|
28911
|
-
if (entry.m === 0 && decoded.endpoints.length === 0) {
|
|
28912
|
-
return { entry: null, dropped: decoded.dropped };
|
|
28913
|
-
}
|
|
28914
|
-
return {
|
|
28915
|
-
entry: Object.freeze({
|
|
28916
|
-
mask: entry.m,
|
|
28917
|
-
app: typeof entry.a === "string" ? entry.a : null,
|
|
28918
|
-
endpoints: decoded.endpoints.length === 0 ? null : decoded.endpoints
|
|
28919
|
-
}),
|
|
28920
|
-
dropped: decoded.dropped
|
|
28921
|
-
};
|
|
28922
|
-
}, "parseTokenEntry");
|
|
28923
|
-
var parseWorkerAccessTokensEnv = /* @__PURE__ */ __name((raw2) => {
|
|
28924
|
-
if (raw2.trim() === "") {
|
|
28925
|
-
return { tokens: /* @__PURE__ */ new Map() };
|
|
28926
|
-
}
|
|
28927
|
-
let parsed;
|
|
28928
|
-
try {
|
|
28929
|
-
parsed = JSON.parse(raw2);
|
|
28930
|
-
} catch {
|
|
28931
|
-
return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS is not valid JSON" };
|
|
28932
|
-
}
|
|
28933
|
-
if (typeof parsed !== "object" || parsed === null || parsed.v !== 2) {
|
|
28934
|
-
return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS has an unsupported version" };
|
|
28935
|
-
}
|
|
28936
|
-
const table = parsed.t;
|
|
28937
|
-
if (typeof table !== "object" || table === null) {
|
|
28938
|
-
return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS is missing its token table" };
|
|
28939
|
-
}
|
|
28940
|
-
const tokens = /* @__PURE__ */ new Map();
|
|
28941
|
-
let rejected = 0;
|
|
28942
|
-
for (const [hash2, value] of Object.entries(table)) {
|
|
28943
|
-
const parsedEntry = parseTokenEntry(value);
|
|
28944
|
-
rejected += parsedEntry.dropped;
|
|
28945
|
-
if (parsedEntry.entry === null) {
|
|
28946
|
-
rejected += 1;
|
|
28947
|
-
continue;
|
|
28948
|
-
}
|
|
28949
|
-
tokens.set(hash2, parsedEntry.entry);
|
|
28950
|
-
}
|
|
28951
|
-
if (rejected > 0) {
|
|
28952
|
-
return { tokens, error: `WORKER_ACCESS_TOKENS dropped ${rejected} invalid entr${rejected === 1 ? "y" : "ies"}` };
|
|
28953
|
-
}
|
|
28954
|
-
return { tokens };
|
|
28955
|
-
}, "parseWorkerAccessTokensEnv");
|
|
28956
|
-
|
|
28957
|
-
// ../../libs/worker-access-token/src/hash.ts
|
|
28958
|
-
var hashWorkerAccessToken = /* @__PURE__ */ __name(async (raw2) => {
|
|
28959
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(raw2));
|
|
28960
|
-
let binary = "";
|
|
28961
|
-
for (const byte of new Uint8Array(digest)) {
|
|
28962
|
-
binary += String.fromCharCode(byte);
|
|
28963
|
-
}
|
|
28964
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
28965
|
-
}, "hashWorkerAccessToken");
|
|
28966
|
-
|
|
28967
|
-
// src/common/auth.ts
|
|
28968
|
-
var cachedRaw4;
|
|
28969
|
-
var cachedTokens = /* @__PURE__ */ new Map();
|
|
28970
|
-
var tokensFor = /* @__PURE__ */ __name((raw2) => {
|
|
28971
|
-
if (raw2 === cachedRaw4) {
|
|
28972
|
-
return cachedTokens;
|
|
28973
|
-
}
|
|
28974
|
-
const parsed = parseWorkerAccessTokensEnv(raw2);
|
|
28975
|
-
if (parsed.error !== void 0) {
|
|
28976
|
-
addSpanEvent("WORKER_ACCESS_TOKENS is misconfigured; rejecting affected tokens until fixed", {
|
|
28977
|
-
level: "error",
|
|
28978
|
-
attrs: { error: String(parsed.error) }
|
|
28979
|
-
});
|
|
28980
|
-
}
|
|
28981
|
-
cachedRaw4 = raw2;
|
|
28982
|
-
cachedTokens = parsed.tokens;
|
|
28983
|
-
return cachedTokens;
|
|
28984
|
-
}, "tokensFor");
|
|
28985
|
-
var readBearerToken = /* @__PURE__ */ __name((request) => {
|
|
28986
|
-
const header = request.headers.get("Authorization");
|
|
28987
|
-
if (header === null) {
|
|
28988
|
-
return null;
|
|
28989
|
-
}
|
|
28990
|
-
const prefix = "Bearer ";
|
|
28991
|
-
if (!header.startsWith(prefix)) {
|
|
28992
|
-
return null;
|
|
28993
|
-
}
|
|
28994
|
-
const token = header.slice(prefix.length).trim();
|
|
28995
|
-
return token.length > 0 ? token : null;
|
|
28996
|
-
}, "readBearerToken");
|
|
28997
|
-
var workerAccessTokenEntry = /* @__PURE__ */ __name(async (env, token) => {
|
|
28998
|
-
const raw2 = env.WORKER_ACCESS_TOKENS ?? "";
|
|
28999
|
-
return tokensFor(raw2).get(await hashWorkerAccessToken(token));
|
|
29000
|
-
}, "workerAccessTokenEntry");
|
|
29001
|
-
var requireScope = /* @__PURE__ */ __name((bit) => {
|
|
29002
|
-
return async (c, next) => {
|
|
29003
|
-
const token = readBearerToken(c.req.raw);
|
|
29004
|
-
if (token === null) {
|
|
29005
|
-
return c.json({ error: "unauthenticated" }, 401);
|
|
29006
|
-
}
|
|
29007
|
-
const entry = await workerAccessTokenEntry(c.env, token);
|
|
29008
|
-
if (entry === void 0) {
|
|
29009
|
-
return c.json({ error: "unauthenticated" }, 401);
|
|
29010
|
-
}
|
|
29011
|
-
if ((entry.mask & bit) === 0) {
|
|
29012
|
-
return c.json({ error: "forbidden" }, 403);
|
|
29013
|
-
}
|
|
29014
|
-
c.set("actionSource", entry.app === null ? "api" : "app");
|
|
29015
|
-
return next();
|
|
29016
|
-
};
|
|
29017
|
-
}, "requireScope");
|
|
29018
|
-
|
|
29019
|
-
// src/helpers/header-json.ts
|
|
29020
|
-
var headerJson = /* @__PURE__ */ __name((value) => {
|
|
29021
|
-
return JSON.stringify(value).replace(/[\u0080-\uffff]/g, (char) => {
|
|
29022
|
-
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
|
29023
|
-
});
|
|
29024
|
-
}, "headerJson");
|
|
29025
|
-
|
|
29026
|
-
// src/apps/stream-deadlines.ts
|
|
29027
|
-
var streamWithDeadlines = /* @__PURE__ */ __name((source, { idleMs, totalMs }) => {
|
|
29028
|
-
if (source === null) {
|
|
29029
|
-
return null;
|
|
29030
|
-
}
|
|
29031
|
-
const reader = source.getReader();
|
|
29032
|
-
let target;
|
|
29033
|
-
let totalTimer;
|
|
29034
|
-
let idleTimer = null;
|
|
29035
|
-
let stopped = false;
|
|
29036
|
-
const clearIdleTimer = /* @__PURE__ */ __name(() => {
|
|
29037
|
-
if (idleTimer !== null) {
|
|
29038
|
-
clearTimeout(idleTimer);
|
|
29039
|
-
idleTimer = null;
|
|
29040
|
-
}
|
|
29041
|
-
}, "clearIdleTimer");
|
|
29042
|
-
const clearTimers = /* @__PURE__ */ __name(() => {
|
|
29043
|
-
clearTimeout(totalTimer);
|
|
29044
|
-
clearIdleTimer();
|
|
29045
|
-
}, "clearTimers");
|
|
29046
|
-
const stop = /* @__PURE__ */ __name((message) => {
|
|
29047
|
-
stopped = true;
|
|
29048
|
-
clearTimers();
|
|
29049
|
-
const error61 = new DOMException(message, "TimeoutError");
|
|
29050
|
-
target.error(error61);
|
|
29051
|
-
void reader.cancel(error61).catch(() => void 0);
|
|
29052
|
-
}, "stop");
|
|
29053
|
-
return new ReadableStream({
|
|
29054
|
-
start: /* @__PURE__ */ __name((controller) => {
|
|
29055
|
-
target = controller;
|
|
29056
|
-
totalTimer = setTimeout(() => {
|
|
29057
|
-
stop("The app endpoint stream exceeded its maximum duration.");
|
|
29058
|
-
}, totalMs);
|
|
29059
|
-
}, "start"),
|
|
29060
|
-
pull: /* @__PURE__ */ __name(async (controller) => {
|
|
29061
|
-
idleTimer = setTimeout(() => {
|
|
29062
|
-
stop("The app endpoint stream stopped producing data.");
|
|
29063
|
-
}, idleMs);
|
|
29064
|
-
let result;
|
|
29065
|
-
try {
|
|
29066
|
-
result = await reader.read();
|
|
29067
|
-
} catch (error61) {
|
|
29068
|
-
stopped = true;
|
|
29069
|
-
clearTimers();
|
|
29070
|
-
throw error61;
|
|
29071
|
-
}
|
|
29072
|
-
clearIdleTimer();
|
|
29073
|
-
if (stopped) {
|
|
29074
|
-
return;
|
|
29075
|
-
}
|
|
29076
|
-
if (result.done) {
|
|
29077
|
-
stopped = true;
|
|
29078
|
-
clearTimers();
|
|
29079
|
-
controller.close();
|
|
29080
|
-
return;
|
|
29081
|
-
}
|
|
29082
|
-
controller.enqueue(result.value);
|
|
29083
|
-
}, "pull"),
|
|
29084
|
-
cancel: /* @__PURE__ */ __name(async (reason) => {
|
|
29085
|
-
stopped = true;
|
|
29086
|
-
clearTimers();
|
|
29087
|
-
await reader.cancel(reason);
|
|
29088
|
-
}, "cancel")
|
|
29089
|
-
});
|
|
29090
|
-
}, "streamWithDeadlines");
|
|
29091
|
-
|
|
29092
27838
|
// ../../libs/bot-protection/src/countries.ts
|
|
29093
27839
|
var EU_TOKEN = "EU";
|
|
29094
27840
|
var COUNTRY_CODES = [
|
|
@@ -29342,90 +28088,1656 @@ var COUNTRY_CODES = [
|
|
|
29342
28088
|
"ZM",
|
|
29343
28089
|
"ZW"
|
|
29344
28090
|
];
|
|
29345
|
-
var SELECTABLE_TOKENS = /* @__PURE__ */ new Set([EU_TOKEN, ...COUNTRY_CODES]);
|
|
29346
|
-
var isSelectableCountryToken = /* @__PURE__ */ __name((token) => {
|
|
29347
|
-
return SELECTABLE_TOKENS.has(token);
|
|
29348
|
-
}, "isSelectableCountryToken");
|
|
29349
|
-
var matchesRule = /* @__PURE__ */ __name((country, isEU, list) => {
|
|
29350
|
-
if (country !== void 0 && list.includes(country)) {
|
|
29351
|
-
return true;
|
|
28091
|
+
var SELECTABLE_TOKENS = /* @__PURE__ */ new Set([EU_TOKEN, ...COUNTRY_CODES]);
|
|
28092
|
+
var isSelectableCountryToken = /* @__PURE__ */ __name((token) => {
|
|
28093
|
+
return SELECTABLE_TOKENS.has(token);
|
|
28094
|
+
}, "isSelectableCountryToken");
|
|
28095
|
+
var matchesRule = /* @__PURE__ */ __name((country, isEU, list) => {
|
|
28096
|
+
if (country !== void 0 && list.includes(country)) {
|
|
28097
|
+
return true;
|
|
28098
|
+
}
|
|
28099
|
+
return isEU && list.includes(EU_TOKEN);
|
|
28100
|
+
}, "matchesRule");
|
|
28101
|
+
var isCountryBlocked = /* @__PURE__ */ __name((country, isEU, rule) => {
|
|
28102
|
+
if (rule === null || rule.list.length === 0) {
|
|
28103
|
+
return false;
|
|
28104
|
+
}
|
|
28105
|
+
const matched = matchesRule(country, isEU, rule.list);
|
|
28106
|
+
return rule.mode === "block" ? matched : !matched;
|
|
28107
|
+
}, "isCountryBlocked");
|
|
28108
|
+
|
|
28109
|
+
// ../../libs/bot-protection/src/score.ts
|
|
28110
|
+
var BOT_SCORE_MIN = 1;
|
|
28111
|
+
var BOT_SCORE_MAX = 99;
|
|
28112
|
+
|
|
28113
|
+
// ../../libs/bot-protection/src/config.ts
|
|
28114
|
+
var NO_PROTECTION = { minBotScore: null, countries: null };
|
|
28115
|
+
var normalizeScore = /* @__PURE__ */ __name((value, problems) => {
|
|
28116
|
+
if (value === void 0 || value === null) {
|
|
28117
|
+
return null;
|
|
28118
|
+
}
|
|
28119
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= BOT_SCORE_MIN && value <= BOT_SCORE_MAX) {
|
|
28120
|
+
return value;
|
|
28121
|
+
}
|
|
28122
|
+
problems.push("has an out-of-range minBotScore");
|
|
28123
|
+
return null;
|
|
28124
|
+
}, "normalizeScore");
|
|
28125
|
+
var normalizeCountries = /* @__PURE__ */ __name((value, problems) => {
|
|
28126
|
+
if (value === void 0 || value === null) {
|
|
28127
|
+
return null;
|
|
28128
|
+
}
|
|
28129
|
+
const record2 = value;
|
|
28130
|
+
if (record2.mode !== "allow" && record2.mode !== "block" || !Array.isArray(record2.list)) {
|
|
28131
|
+
problems.push("has an invalid countries rule");
|
|
28132
|
+
return null;
|
|
28133
|
+
}
|
|
28134
|
+
const list = [];
|
|
28135
|
+
let dropped = 0;
|
|
28136
|
+
for (const token of record2.list) {
|
|
28137
|
+
if (typeof token === "string" && isSelectableCountryToken(token)) {
|
|
28138
|
+
list.push(token);
|
|
28139
|
+
} else {
|
|
28140
|
+
dropped += 1;
|
|
28141
|
+
}
|
|
28142
|
+
}
|
|
28143
|
+
if (dropped > 0) {
|
|
28144
|
+
problems.push(`dropped ${dropped} invalid country token(s)`);
|
|
28145
|
+
}
|
|
28146
|
+
return list.length === 0 ? null : { mode: record2.mode, list };
|
|
28147
|
+
}, "normalizeCountries");
|
|
28148
|
+
var parseBotProtection = /* @__PURE__ */ __name((raw2) => {
|
|
28149
|
+
if (raw2.trim() === "") {
|
|
28150
|
+
return { config: NO_PROTECTION };
|
|
28151
|
+
}
|
|
28152
|
+
let value;
|
|
28153
|
+
try {
|
|
28154
|
+
value = JSON.parse(raw2);
|
|
28155
|
+
} catch {
|
|
28156
|
+
return { config: NO_PROTECTION, error: "BOT_PROTECTION is not valid JSON" };
|
|
28157
|
+
}
|
|
28158
|
+
if (typeof value !== "object" || value === null) {
|
|
28159
|
+
return { config: NO_PROTECTION, error: "BOT_PROTECTION is not an object" };
|
|
28160
|
+
}
|
|
28161
|
+
const record2 = value;
|
|
28162
|
+
const problems = [];
|
|
28163
|
+
const config2 = {
|
|
28164
|
+
minBotScore: normalizeScore(record2.minBotScore, problems),
|
|
28165
|
+
countries: normalizeCountries(record2.countries, problems)
|
|
28166
|
+
};
|
|
28167
|
+
return problems.length > 0 ? { config: config2, error: `BOT_PROTECTION ${problems.join("; ")}` } : { config: config2 };
|
|
28168
|
+
}, "parseBotProtection");
|
|
28169
|
+
var isRequestBlocked = /* @__PURE__ */ __name((config2, signal) => {
|
|
28170
|
+
if (config2.minBotScore !== null && signal.botScore !== void 0 && signal.botScore < config2.minBotScore) {
|
|
28171
|
+
return true;
|
|
28172
|
+
}
|
|
28173
|
+
return isCountryBlocked(signal.country, signal.isEU ?? false, config2.countries);
|
|
28174
|
+
}, "isRequestBlocked");
|
|
28175
|
+
|
|
28176
|
+
// ../../libs/geo-filter/src/us-states.ts
|
|
28177
|
+
var US_STATE_NAMES = {
|
|
28178
|
+
"US-AK": "Alaska",
|
|
28179
|
+
"US-AL": "Alabama",
|
|
28180
|
+
"US-AR": "Arkansas",
|
|
28181
|
+
"US-AZ": "Arizona",
|
|
28182
|
+
"US-CA": "California",
|
|
28183
|
+
"US-CO": "Colorado",
|
|
28184
|
+
"US-CT": "Connecticut",
|
|
28185
|
+
"US-DC": "District of Columbia",
|
|
28186
|
+
"US-DE": "Delaware",
|
|
28187
|
+
"US-FL": "Florida",
|
|
28188
|
+
"US-GA": "Georgia",
|
|
28189
|
+
"US-HI": "Hawaii",
|
|
28190
|
+
"US-IA": "Iowa",
|
|
28191
|
+
"US-ID": "Idaho",
|
|
28192
|
+
"US-IL": "Illinois",
|
|
28193
|
+
"US-IN": "Indiana",
|
|
28194
|
+
"US-KS": "Kansas",
|
|
28195
|
+
"US-KY": "Kentucky",
|
|
28196
|
+
"US-LA": "Louisiana",
|
|
28197
|
+
"US-MA": "Massachusetts",
|
|
28198
|
+
"US-MD": "Maryland",
|
|
28199
|
+
"US-ME": "Maine",
|
|
28200
|
+
"US-MI": "Michigan",
|
|
28201
|
+
"US-MN": "Minnesota",
|
|
28202
|
+
"US-MO": "Missouri",
|
|
28203
|
+
"US-MS": "Mississippi",
|
|
28204
|
+
"US-MT": "Montana",
|
|
28205
|
+
"US-NC": "North Carolina",
|
|
28206
|
+
"US-ND": "North Dakota",
|
|
28207
|
+
"US-NE": "Nebraska",
|
|
28208
|
+
"US-NH": "New Hampshire",
|
|
28209
|
+
"US-NJ": "New Jersey",
|
|
28210
|
+
"US-NM": "New Mexico",
|
|
28211
|
+
"US-NV": "Nevada",
|
|
28212
|
+
"US-NY": "New York",
|
|
28213
|
+
"US-OH": "Ohio",
|
|
28214
|
+
"US-OK": "Oklahoma",
|
|
28215
|
+
"US-OR": "Oregon",
|
|
28216
|
+
"US-PA": "Pennsylvania",
|
|
28217
|
+
"US-RI": "Rhode Island",
|
|
28218
|
+
"US-SC": "South Carolina",
|
|
28219
|
+
"US-SD": "South Dakota",
|
|
28220
|
+
"US-TN": "Tennessee",
|
|
28221
|
+
"US-TX": "Texas",
|
|
28222
|
+
"US-UT": "Utah",
|
|
28223
|
+
"US-VA": "Virginia",
|
|
28224
|
+
"US-VT": "Vermont",
|
|
28225
|
+
"US-WA": "Washington",
|
|
28226
|
+
"US-WI": "Wisconsin",
|
|
28227
|
+
"US-WV": "West Virginia",
|
|
28228
|
+
"US-WY": "Wyoming"
|
|
28229
|
+
};
|
|
28230
|
+
var US_STATE_TOKENS = Object.keys(US_STATE_NAMES);
|
|
28231
|
+
var STATE_TOKENS = new Set(US_STATE_TOKENS);
|
|
28232
|
+
|
|
28233
|
+
// ../../libs/geo-filter/src/geo-filter.ts
|
|
28234
|
+
var UNKNOWN_COUNTRIES = /* @__PURE__ */ new Set(["XX", "T1"]);
|
|
28235
|
+
var tokenMatches = /* @__PURE__ */ __name((token, location) => {
|
|
28236
|
+
if (token === EU_TOKEN) {
|
|
28237
|
+
return location.isEU === true;
|
|
28238
|
+
}
|
|
28239
|
+
if (STATE_TOKENS.has(token)) {
|
|
28240
|
+
return location.country === "US" && `US-${location.regionCode}` === token;
|
|
28241
|
+
}
|
|
28242
|
+
return token === location.country;
|
|
28243
|
+
}, "tokenMatches");
|
|
28244
|
+
var locationKnown = /* @__PURE__ */ __name((filter, location) => {
|
|
28245
|
+
if (location.country === void 0 || UNKNOWN_COUNTRIES.has(location.country)) {
|
|
28246
|
+
return false;
|
|
28247
|
+
}
|
|
28248
|
+
const tokens = [...filter.include, ...filter.exclude];
|
|
28249
|
+
if (location.country === "US" && !location.regionCode && tokens.some((token) => STATE_TOKENS.has(token))) {
|
|
28250
|
+
return false;
|
|
28251
|
+
}
|
|
28252
|
+
return location.isEU !== void 0 || !tokens.includes(EU_TOKEN);
|
|
28253
|
+
}, "locationKnown");
|
|
28254
|
+
var geoFilterAllows = /* @__PURE__ */ __name((filter, location) => {
|
|
28255
|
+
if (!locationKnown(filter, location)) {
|
|
28256
|
+
return false;
|
|
28257
|
+
}
|
|
28258
|
+
const included = filter.include.length === 0 || filter.include.some((token) => {
|
|
28259
|
+
return tokenMatches(token, location);
|
|
28260
|
+
});
|
|
28261
|
+
return included && !filter.exclude.some((token) => {
|
|
28262
|
+
return tokenMatches(token, location);
|
|
28263
|
+
});
|
|
28264
|
+
}, "geoFilterAllows");
|
|
28265
|
+
|
|
28266
|
+
// ../../libs/geo-filter/src/validate.ts
|
|
28267
|
+
var KNOWN_TOKENS = /* @__PURE__ */ new Set([EU_TOKEN, ...COUNTRY_CODES, ...US_STATE_TOKENS]);
|
|
28268
|
+
|
|
28269
|
+
// src/apps/geo-filter.ts
|
|
28270
|
+
var startedOnSocket = /* @__PURE__ */ __name((trigger) => {
|
|
28271
|
+
return trigger.kind === "connect" || trigger.action.source === "website" || trigger.action.fromSocket === true;
|
|
28272
|
+
}, "startedOnSocket");
|
|
28273
|
+
var geoLocation = /* @__PURE__ */ __name((geo) => {
|
|
28274
|
+
return {
|
|
28275
|
+
country: geo.country,
|
|
28276
|
+
regionCode: geo.regionCode,
|
|
28277
|
+
isEU: geo.isEUCountry === "unknown" ? void 0 : geo.isEUCountry === "yes"
|
|
28278
|
+
};
|
|
28279
|
+
}, "geoLocation");
|
|
28280
|
+
var socketLocation = /* @__PURE__ */ __name((cf) => {
|
|
28281
|
+
return geoLocation(geoFromCf(cf));
|
|
28282
|
+
}, "socketLocation");
|
|
28283
|
+
var visitorLocation = /* @__PURE__ */ __name((trigger) => {
|
|
28284
|
+
if (!startedOnSocket(trigger)) {
|
|
28285
|
+
return null;
|
|
28286
|
+
}
|
|
28287
|
+
return socketLocation(trigger.kind === "connect" ? trigger.geo : trigger.action.geo);
|
|
28288
|
+
}, "visitorLocation");
|
|
28289
|
+
var geoRunnableInstalls = /* @__PURE__ */ __name((slug, app2, location) => {
|
|
28290
|
+
const runs = [];
|
|
28291
|
+
const skipped = [];
|
|
28292
|
+
for (const install of app2.installs) {
|
|
28293
|
+
if (!install.active) {
|
|
28294
|
+
continue;
|
|
28295
|
+
}
|
|
28296
|
+
if (install.geoFilter === void 0 || location === null || geoFilterAllows(install.geoFilter, location)) {
|
|
28297
|
+
runs.push(install);
|
|
28298
|
+
} else {
|
|
28299
|
+
skipped.push(`${slug}:${install.id}`);
|
|
28300
|
+
}
|
|
28301
|
+
}
|
|
28302
|
+
return { runs, skipped };
|
|
28303
|
+
}, "geoRunnableInstalls");
|
|
28304
|
+
var geoAllowedInstalls = /* @__PURE__ */ __name((registry3, cf) => {
|
|
28305
|
+
const location = socketLocation(cf);
|
|
28306
|
+
return Object.entries(registry3.apps).flatMap(([slug, app2]) => {
|
|
28307
|
+
return geoRunnableInstalls(slug, app2, location).runs.filter((install) => install.geoFilter !== void 0).map((install) => install.id);
|
|
28308
|
+
});
|
|
28309
|
+
}, "geoAllowedInstalls");
|
|
28310
|
+
|
|
28311
|
+
// src/apps/observability.ts
|
|
28312
|
+
var APP_ERROR_DATA_POINT_RESERVE = 50;
|
|
28313
|
+
var observabilityApp = /* @__PURE__ */ __name((slug, app2) => {
|
|
28314
|
+
return { slug, name: app2.name, version: app2.version };
|
|
28315
|
+
}, "observabilityApp");
|
|
28316
|
+
var createDataPointBudget = /* @__PURE__ */ __name(() => {
|
|
28317
|
+
let events = 0;
|
|
28318
|
+
let total = 0;
|
|
28319
|
+
return {
|
|
28320
|
+
take: /* @__PURE__ */ __name((kind2) => {
|
|
28321
|
+
if (total >= MAX_APP_DATA_POINTS_PER_INVOCATION) {
|
|
28322
|
+
return false;
|
|
28323
|
+
}
|
|
28324
|
+
if (kind2 === "event") {
|
|
28325
|
+
if (events >= MAX_APP_DATA_POINTS_PER_INVOCATION - APP_ERROR_DATA_POINT_RESERVE) {
|
|
28326
|
+
return false;
|
|
28327
|
+
}
|
|
28328
|
+
events += 1;
|
|
28329
|
+
}
|
|
28330
|
+
total += 1;
|
|
28331
|
+
return true;
|
|
28332
|
+
}, "take")
|
|
28333
|
+
};
|
|
28334
|
+
}, "createDataPointBudget");
|
|
28335
|
+
var totalLength = /* @__PURE__ */ __name((blobs) => {
|
|
28336
|
+
return blobs.reduce((sum, blob) => {
|
|
28337
|
+
return sum + utf8Length(blob);
|
|
28338
|
+
}, 0);
|
|
28339
|
+
}, "totalLength");
|
|
28340
|
+
var errorBlobs = /* @__PURE__ */ __name((app2, install, record2, rowId) => {
|
|
28341
|
+
const fixed = [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, rowId];
|
|
28342
|
+
let remaining = MAX_APP_DATA_POINT_BLOB_BYTES - totalLength(fixed);
|
|
28343
|
+
const description = truncateUtf8(record2.description, Math.max(0, remaining));
|
|
28344
|
+
remaining -= utf8Length(description);
|
|
28345
|
+
const payload = truncateUtf8(record2.payload ?? "", Math.max(0, remaining));
|
|
28346
|
+
remaining -= utf8Length(payload);
|
|
28347
|
+
const error61 = truncateUtf8(record2.error ?? "", Math.max(0, remaining));
|
|
28348
|
+
return [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, description, payload, error61, rowId];
|
|
28349
|
+
}, "errorBlobs");
|
|
28350
|
+
var eventBlobs = /* @__PURE__ */ __name((app2, install, record2) => {
|
|
28351
|
+
return [app2.slug, app2.name, install.id, install.label, app2.version, record2.name, record2.status];
|
|
28352
|
+
}, "eventBlobs");
|
|
28353
|
+
var writeEvents = /* @__PURE__ */ __name((env, target, byOrd, events) => {
|
|
28354
|
+
for (const [index, event] of events.entries()) {
|
|
28355
|
+
const install = byOrd.get(event.ord);
|
|
28356
|
+
if (install === void 0) {
|
|
28357
|
+
addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
|
|
28358
|
+
continue;
|
|
28359
|
+
}
|
|
28360
|
+
if (!target.budget.take("event")) {
|
|
28361
|
+
addSpanEvent("app observability dropped events at the data-point cap", {
|
|
28362
|
+
level: "warn",
|
|
28363
|
+
attrs: { app: target.app.slug, skipped: events.length - index }
|
|
28364
|
+
});
|
|
28365
|
+
break;
|
|
28366
|
+
}
|
|
28367
|
+
env.APP_EVENTS.writeDataPoint({ indexes: [`${install.id}:${event.status}`], blobs: eventBlobs(target.app, install, event) });
|
|
28368
|
+
}
|
|
28369
|
+
}, "writeEvents");
|
|
28370
|
+
var writeErrors = /* @__PURE__ */ __name((env, target, byOrd, errors) => {
|
|
28371
|
+
for (const [index, saved] of errors.entries()) {
|
|
28372
|
+
const install = byOrd.get(saved.ord);
|
|
28373
|
+
if (install === void 0) {
|
|
28374
|
+
addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
|
|
28375
|
+
continue;
|
|
28376
|
+
}
|
|
28377
|
+
if (!target.budget.take("error")) {
|
|
28378
|
+
addSpanEvent("app observability dropped saved errors at the data-point cap", {
|
|
28379
|
+
level: "warn",
|
|
28380
|
+
attrs: { app: target.app.slug, skipped: errors.length - index }
|
|
28381
|
+
});
|
|
28382
|
+
break;
|
|
28383
|
+
}
|
|
28384
|
+
env.APP_ERRORS.writeDataPoint({
|
|
28385
|
+
indexes: [`${install.id}:${saved.category}`],
|
|
28386
|
+
blobs: errorBlobs(target.app, install, saved, ulid3())
|
|
28387
|
+
});
|
|
28388
|
+
}
|
|
28389
|
+
}, "writeErrors");
|
|
28390
|
+
var guarded = /* @__PURE__ */ __name((target, write) => {
|
|
28391
|
+
try {
|
|
28392
|
+
write();
|
|
28393
|
+
return true;
|
|
28394
|
+
} catch (error61) {
|
|
28395
|
+
addSpanEvent("app observability write failed", { level: "warn", attrs: { app: target.app.slug, error: String(error61) } });
|
|
28396
|
+
return false;
|
|
28397
|
+
}
|
|
28398
|
+
}, "guarded");
|
|
28399
|
+
var writeAppObservabilityRecord = /* @__PURE__ */ __name((env, record2, target) => {
|
|
28400
|
+
const byOrd = new Map(
|
|
28401
|
+
target.installs.map((install) => {
|
|
28402
|
+
return [install.ord, install];
|
|
28403
|
+
})
|
|
28404
|
+
);
|
|
28405
|
+
const eventsWritten = guarded(target, () => {
|
|
28406
|
+
writeEvents(env, target, byOrd, record2.events);
|
|
28407
|
+
});
|
|
28408
|
+
const errorsWritten = guarded(target, () => {
|
|
28409
|
+
writeErrors(env, target, byOrd, record2.errors);
|
|
28410
|
+
});
|
|
28411
|
+
if (!eventsWritten || !errorsWritten) {
|
|
28412
|
+
return;
|
|
28413
|
+
}
|
|
28414
|
+
if (record2.truncated === true) {
|
|
28415
|
+
addSpanEvent("the app recorded more observability than one invocation carries", { level: "warn", attrs: { app: target.app.slug } });
|
|
28416
|
+
}
|
|
28417
|
+
}, "writeAppObservabilityRecord");
|
|
28418
|
+
var writeAppObservability = /* @__PURE__ */ __name((env, response, target) => {
|
|
28419
|
+
const record2 = parseAppObservability(response.headers.get(APP_OBSERVABILITY_HEADER));
|
|
28420
|
+
if (record2 !== null) {
|
|
28421
|
+
writeAppObservabilityRecord(env, record2, target);
|
|
28422
|
+
}
|
|
28423
|
+
}, "writeAppObservability");
|
|
28424
|
+
|
|
28425
|
+
// src/apps/read-bounded.ts
|
|
28426
|
+
var readBoundedBody = /* @__PURE__ */ __name(async (message, maxBytes) => {
|
|
28427
|
+
const reader = message.body?.getReader();
|
|
28428
|
+
if (reader === void 0) {
|
|
28429
|
+
return new Uint8Array(0);
|
|
28430
|
+
}
|
|
28431
|
+
const chunks = [];
|
|
28432
|
+
let received = 0;
|
|
28433
|
+
let result = await reader.read();
|
|
28434
|
+
while (!result.done) {
|
|
28435
|
+
received += result.value.byteLength;
|
|
28436
|
+
if (received > maxBytes) {
|
|
28437
|
+
await reader.cancel();
|
|
28438
|
+
return null;
|
|
28439
|
+
}
|
|
28440
|
+
chunks.push(result.value);
|
|
28441
|
+
result = await reader.read();
|
|
28442
|
+
}
|
|
28443
|
+
const bytes = new Uint8Array(received);
|
|
28444
|
+
let offset = 0;
|
|
28445
|
+
for (const chunk of chunks) {
|
|
28446
|
+
bytes.set(chunk, offset);
|
|
28447
|
+
offset += chunk.byteLength;
|
|
28448
|
+
}
|
|
28449
|
+
return bytes;
|
|
28450
|
+
}, "readBoundedBody");
|
|
28451
|
+
|
|
28452
|
+
// src/apps/spans.ts
|
|
28453
|
+
var adoptAppSpans = /* @__PURE__ */ __name((response) => {
|
|
28454
|
+
const header = response.headers.get(APP_SPANS_HEADER);
|
|
28455
|
+
if (header === null || header.length > MAX_APP_SPANS_BYTES) {
|
|
28456
|
+
return;
|
|
28457
|
+
}
|
|
28458
|
+
const trace = currentTraceContext();
|
|
28459
|
+
if (trace?.parent === void 0) {
|
|
28460
|
+
return;
|
|
28461
|
+
}
|
|
28462
|
+
const record2 = parseAppSpans(header);
|
|
28463
|
+
if (record2 === void 0) {
|
|
28464
|
+
return;
|
|
28465
|
+
}
|
|
28466
|
+
for (const span of adoptSpans(record2.spans, { traceId: trace.traceId, parent: trace.parent })) {
|
|
28467
|
+
appendSpan(span);
|
|
28468
|
+
}
|
|
28469
|
+
if (record2.truncated !== void 0 && record2.truncated > 0) {
|
|
28470
|
+
setSpanAttributes({ "app.spans_truncated": record2.truncated });
|
|
28471
|
+
}
|
|
28472
|
+
}, "adoptAppSpans");
|
|
28473
|
+
|
|
28474
|
+
// src/apps/invoke.ts
|
|
28475
|
+
var APP_INVOCATION_TIMEOUT_MS = 1e4;
|
|
28476
|
+
var APP_HOOK_TIMEOUT_MS = 25e3;
|
|
28477
|
+
var MAX_EMIT_DEPTH = 2;
|
|
28478
|
+
var HOOK_URL = "https://app/__qinto/hook";
|
|
28479
|
+
var invocationContext = /* @__PURE__ */ __name((env) => {
|
|
28480
|
+
const { APPS_KEY_MASTER: master, QINTO_DOMAIN_ID: domainId } = env;
|
|
28481
|
+
if (master === void 0 || domainId === void 0) {
|
|
28482
|
+
return void 0;
|
|
28483
|
+
}
|
|
28484
|
+
return { master, domainId, consentEnforced: consentEnforced(env) };
|
|
28485
|
+
}, "invocationContext");
|
|
28486
|
+
var postHookEnvelope = /* @__PURE__ */ __name(async (env, { binding, key, envelope, timeoutMs }) => {
|
|
28487
|
+
const service = env[binding];
|
|
28488
|
+
if (typeof service?.fetch !== "function") {
|
|
28489
|
+
return void 0;
|
|
28490
|
+
}
|
|
28491
|
+
const headers = { "content-type": "application/json", "x-qinto-app-key": key };
|
|
28492
|
+
const trace = currentTraceContext();
|
|
28493
|
+
if (trace !== void 0) {
|
|
28494
|
+
headers[TRACE_HEADER] = serializeTraceContext(trace);
|
|
28495
|
+
}
|
|
28496
|
+
const response = await service.fetch(HOOK_URL, {
|
|
28497
|
+
method: "POST",
|
|
28498
|
+
headers,
|
|
28499
|
+
body: JSON.stringify(envelope),
|
|
28500
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
28501
|
+
});
|
|
28502
|
+
adoptAppSpans(response);
|
|
28503
|
+
return response;
|
|
28504
|
+
}, "postHookEnvelope");
|
|
28505
|
+
var triggerDepth = /* @__PURE__ */ __name((trigger) => {
|
|
28506
|
+
return trigger.kind === "event" ? trigger.action.origin?.depth ?? 0 : 0;
|
|
28507
|
+
}, "triggerDepth");
|
|
28508
|
+
var stillDeclared = /* @__PURE__ */ __name((target, key, source) => {
|
|
28509
|
+
if (source === "app") {
|
|
28510
|
+
return true;
|
|
28511
|
+
}
|
|
28512
|
+
const { queryParams, cookies } = target.app.capture;
|
|
28513
|
+
return source === "query" ? queryParams.some((entry) => entry.name === key) : cookies.some((entry) => entry.name === key);
|
|
28514
|
+
}, "stillDeclared");
|
|
28515
|
+
var deliverableCaptures = /* @__PURE__ */ __name((target, rows) => {
|
|
28516
|
+
if (rows === void 0) {
|
|
28517
|
+
return void 0;
|
|
28518
|
+
}
|
|
28519
|
+
const deliverable = {};
|
|
28520
|
+
for (const [key, row] of Object.entries(rows)) {
|
|
28521
|
+
if (stillDeclared(target, key, row.source)) {
|
|
28522
|
+
deliverable[key] = { value: row.value, createdAt: row.createdAt, updatedAt: row.updatedAt, type: row.type };
|
|
28523
|
+
}
|
|
28524
|
+
}
|
|
28525
|
+
return Object.keys(deliverable).length === 0 ? void 0 : deliverable;
|
|
28526
|
+
}, "deliverableCaptures");
|
|
28527
|
+
var eventAction = /* @__PURE__ */ __name((target, trigger, snapshot) => {
|
|
28528
|
+
const buckets = target.app.data;
|
|
28529
|
+
return {
|
|
28530
|
+
actionId: trigger.action.actionId,
|
|
28531
|
+
type: trigger.action.type,
|
|
28532
|
+
userId: snapshot.anonId,
|
|
28533
|
+
payload: trigger.action.payload,
|
|
28534
|
+
receivedAt: trigger.action.receivedAt,
|
|
28535
|
+
source: trigger.action.source,
|
|
28536
|
+
...buckets.includes("eventContext") && trigger.action.context !== void 0 ? { context: trigger.action.context } : {},
|
|
28537
|
+
...trigger.action.browserResults?.[target.slug] === void 0 ? {} : { browser: trigger.action.browserResults[target.slug] },
|
|
28538
|
+
...trigger.action.origin !== void 0 ? { origin: trigger.action.origin } : {}
|
|
28539
|
+
};
|
|
28540
|
+
}, "eventAction");
|
|
28541
|
+
var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context) => {
|
|
28542
|
+
const buckets = target.app.data;
|
|
28543
|
+
const geoAllowed = buckets.includes("geo");
|
|
28544
|
+
const currentGeo = trigger.kind === "event" ? trigger.action.geo : trigger.geo;
|
|
28545
|
+
const consented = consentAllows(snapshot.consent, target.slug, target.app.consent.category, context.consentEnforced);
|
|
28546
|
+
const captures = consented ? deliverableCaptures(target, snapshot.captures[target.slug]) : void 0;
|
|
28547
|
+
const visitorContext = deliverableContext({ target, consented, ...snapshot.context });
|
|
28548
|
+
return {
|
|
28549
|
+
v: ENVELOPE_VERSION,
|
|
28550
|
+
kind: trigger.kind,
|
|
28551
|
+
app: { slug: target.slug, version: target.app.version },
|
|
28552
|
+
installs: target.installs,
|
|
28553
|
+
user: {
|
|
28554
|
+
anonId: snapshot.anonId,
|
|
28555
|
+
globalId: snapshot.globalId,
|
|
28556
|
+
...snapshot.createdAt === null ? {} : { createdAt: snapshot.createdAt },
|
|
28557
|
+
// Custom properties intentionally win on key collisions with PII, matching the profile read path.
|
|
28558
|
+
properties: {
|
|
28559
|
+
...buckets.includes("pii") ? snapshot.pii : {},
|
|
28560
|
+
...buckets.includes("customProperties") ? snapshot.custom : {}
|
|
28561
|
+
},
|
|
28562
|
+
...geoAllowed ? { geo: snapshot.userGeo } : {}
|
|
28563
|
+
},
|
|
28564
|
+
...buckets.includes("consentState") ? { consent: { enabled: context.consentEnforced, categories: snapshot.consent.categories, apps: snapshot.consent.apps } } : {},
|
|
28565
|
+
...geoAllowed && currentGeo !== void 0 ? { geo: currentGeo } : {},
|
|
28566
|
+
...trigger.kind === "connect" && trigger.page !== void 0 && buckets.includes("eventContext") ? { page: trigger.page } : {},
|
|
28567
|
+
// Not gated on a capabilities.data bucket: an app only ever sees what it captured or wrote.
|
|
28568
|
+
...captures === void 0 ? {} : { captures },
|
|
28569
|
+
...visitorContext === void 0 ? {} : { context: visitorContext },
|
|
28570
|
+
depth: triggerDepth(trigger),
|
|
28571
|
+
...trigger.kind === "event" ? { action: eventAction(target, trigger, snapshot) } : {}
|
|
28572
|
+
};
|
|
28573
|
+
}, "buildEnvelope");
|
|
28574
|
+
var ingestEmit = /* @__PURE__ */ __name(async (ingest2, { slug, ord, depth, emit, anonId, geo, host, context, fromSocket }) => {
|
|
28575
|
+
const parsed = emitPayloadSchema.safeParse(emit.payload);
|
|
28576
|
+
if (!parsed.success) {
|
|
28577
|
+
addSpanEvent("emit payload rejected", { level: "warn", attrs: { app: slug, ord } });
|
|
28578
|
+
return;
|
|
28579
|
+
}
|
|
28580
|
+
const { event, eventId, properties: properties2 } = parsed.data;
|
|
28581
|
+
const inherited = {
|
|
28582
|
+
...context ?? {},
|
|
28583
|
+
source: "app",
|
|
28584
|
+
occurredAt: emit.occurredAt,
|
|
28585
|
+
...context !== void 0 || host === void 0 ? {} : {
|
|
28586
|
+
network: {
|
|
28587
|
+
...host.ip === void 0 ? {} : { ip: host.ip },
|
|
28588
|
+
...host.userAgent === void 0 ? {} : { userAgent: host.userAgent },
|
|
28589
|
+
...host.locale === void 0 ? {} : { locale: host.locale },
|
|
28590
|
+
provenance: "edge"
|
|
28591
|
+
}
|
|
28592
|
+
},
|
|
28593
|
+
offline: context === void 0 || context.source === "browser" ? false : context.offline,
|
|
28594
|
+
replay: context === void 0 || context.source === "browser" ? false : context.replay
|
|
28595
|
+
};
|
|
28596
|
+
const action = buildAction({
|
|
28597
|
+
type: "track",
|
|
28598
|
+
userId: anonId,
|
|
28599
|
+
source: "app",
|
|
28600
|
+
payload: { event, eventId: emittedEventId(eventId, properties2), properties: properties2 ?? {} },
|
|
28601
|
+
context: inherited,
|
|
28602
|
+
origin: { app: slug, install: ord, depth: depth + 1 },
|
|
28603
|
+
fromSocket,
|
|
28604
|
+
geo,
|
|
28605
|
+
host
|
|
28606
|
+
});
|
|
28607
|
+
await ingest2(action);
|
|
28608
|
+
}, "ingestEmit");
|
|
28609
|
+
var deliverGroupPushes = /* @__PURE__ */ __name(async ({ slug, group, installs, deliverPush }) => {
|
|
28610
|
+
const install = installs.find((entry) => entry.ord === group.ord);
|
|
28611
|
+
for (const push of group.pushes) {
|
|
28612
|
+
const { topic, data } = push;
|
|
28613
|
+
switch (push.broadcast ?? "install") {
|
|
28614
|
+
case "install": {
|
|
28615
|
+
if (install === void 0) {
|
|
28616
|
+
addSpanEvent("push rejected: its group matches no invoked install", { level: "warn", attrs: { app: slug, ord: group.ord } });
|
|
28617
|
+
break;
|
|
28618
|
+
}
|
|
28619
|
+
await deliverPush({ slug, installId: install.id, topic, data, scope: "app" });
|
|
28620
|
+
break;
|
|
28621
|
+
}
|
|
28622
|
+
case "app": {
|
|
28623
|
+
await deliverPush({ slug, installId: null, topic, data, scope: "app" });
|
|
28624
|
+
break;
|
|
28625
|
+
}
|
|
28626
|
+
// Whether this app may broadcast site-wide is settled in `deliverAppPush`, the one path every
|
|
28627
|
+
// delivery funnels through; re-deciding it here would be a second authority to keep in sync.
|
|
28628
|
+
case "site": {
|
|
28629
|
+
await deliverPush({ slug, installId: null, topic, data, scope: "site" });
|
|
28630
|
+
break;
|
|
28631
|
+
}
|
|
28632
|
+
}
|
|
28633
|
+
}
|
|
28634
|
+
}, "deliverGroupPushes");
|
|
28635
|
+
var applyEffects = /* @__PURE__ */ __name(async ({
|
|
28636
|
+
slug,
|
|
28637
|
+
groups,
|
|
28638
|
+
depth,
|
|
28639
|
+
anonId,
|
|
28640
|
+
installs,
|
|
28641
|
+
deliverPush,
|
|
28642
|
+
writeCaptures: writeCaptures2,
|
|
28643
|
+
writeContext: writeContext2,
|
|
28644
|
+
ingest: ingest2,
|
|
28645
|
+
geo,
|
|
28646
|
+
host,
|
|
28647
|
+
context,
|
|
28648
|
+
fromSocket
|
|
28649
|
+
}) => {
|
|
28650
|
+
const captures = groups.flatMap((group) => group.captures);
|
|
28651
|
+
if (captures.length > 0) {
|
|
28652
|
+
await writeCaptures2(slug, captures);
|
|
28653
|
+
}
|
|
28654
|
+
const contextWrites = groups.flatMap((group) => group.context);
|
|
28655
|
+
if (contextWrites.length > 0) {
|
|
28656
|
+
await writeContext2(slug, contextWrites);
|
|
28657
|
+
}
|
|
28658
|
+
for (const group of groups) {
|
|
28659
|
+
await deliverGroupPushes({ slug, group, installs, deliverPush });
|
|
28660
|
+
if (group.emits.length === 0) {
|
|
28661
|
+
continue;
|
|
28662
|
+
}
|
|
28663
|
+
if (depth >= MAX_EMIT_DEPTH) {
|
|
28664
|
+
addSpanEvent("emit rejected at the depth cap", { level: "warn", attrs: { app: slug, ord: group.ord, depth } });
|
|
28665
|
+
continue;
|
|
28666
|
+
}
|
|
28667
|
+
for (const emit of group.emits) {
|
|
28668
|
+
await ingestEmit(ingest2, { slug, ord: group.ord, depth, emit, anonId, geo, host, context, fromSocket });
|
|
28669
|
+
}
|
|
28670
|
+
}
|
|
28671
|
+
}, "applyEffects");
|
|
28672
|
+
var hookResponseBody = /* @__PURE__ */ __name(async (response, slug) => {
|
|
28673
|
+
if (!response.ok) {
|
|
28674
|
+
addSpanEvent("app hook failed", { level: "warn", attrs: { app: slug, status: response.status } });
|
|
28675
|
+
return void 0;
|
|
28676
|
+
}
|
|
28677
|
+
const bytes = await readBoundedBody(response, MAX_RESPONSE_BYTES);
|
|
28678
|
+
if (bytes === null) {
|
|
28679
|
+
addSpanEvent("app hook response rejected", { level: "warn", attrs: { app: slug, reason: "The response exceeds the size cap." } });
|
|
28680
|
+
return void 0;
|
|
28681
|
+
}
|
|
28682
|
+
return new TextDecoder().decode(bytes);
|
|
28683
|
+
}, "hookResponseBody");
|
|
28684
|
+
var runInvokeApp = /* @__PURE__ */ __name(async (env, { target, trigger, snapshot, context, deliverEffects, budget }) => {
|
|
28685
|
+
try {
|
|
28686
|
+
const key = await deriveAppInternalKey(context.master, context.domainId, target.slug);
|
|
28687
|
+
const response = await postHookEnvelope(env, {
|
|
28688
|
+
binding: target.app.binding,
|
|
28689
|
+
key,
|
|
28690
|
+
envelope: buildEnvelope(target, trigger, snapshot, context),
|
|
28691
|
+
timeoutMs: APP_HOOK_TIMEOUT_MS
|
|
28692
|
+
});
|
|
28693
|
+
if (response === void 0) {
|
|
28694
|
+
addSpanEvent("app service binding is missing", { level: "warn", attrs: { app: target.slug, binding: target.app.binding } });
|
|
28695
|
+
return;
|
|
28696
|
+
}
|
|
28697
|
+
writeAppObservability(env, response, {
|
|
28698
|
+
app: observabilityApp(target.slug, target.app),
|
|
28699
|
+
installs: target.installs,
|
|
28700
|
+
budget
|
|
28701
|
+
});
|
|
28702
|
+
setSpanAttributes({ "app.status": response.status });
|
|
28703
|
+
const body = await hookResponseBody(response, target.slug);
|
|
28704
|
+
if (body === void 0) {
|
|
28705
|
+
return;
|
|
28706
|
+
}
|
|
28707
|
+
const verdict = validateEnvelopeResponse(JSON.parse(body), target.installs.length);
|
|
28708
|
+
if (!verdict.ok) {
|
|
28709
|
+
addSpanEvent("app hook response rejected", { level: "warn", attrs: { app: target.slug, reason: verdict.reason } });
|
|
28710
|
+
return;
|
|
28711
|
+
}
|
|
28712
|
+
if (verdict.error !== null) {
|
|
28713
|
+
addSpanEvent("app hook reported an error", { level: "warn", attrs: { app: target.slug, message: redactUrlParams(verdict.error) } });
|
|
28714
|
+
}
|
|
28715
|
+
for (const ord of verdict.effects.rejectedOrds) {
|
|
28716
|
+
addSpanEvent("app effects rejected over the per-install caps", { level: "warn", attrs: { app: target.slug, ord } });
|
|
28717
|
+
}
|
|
28718
|
+
if (verdict.effects.accepted.length === 0) {
|
|
28719
|
+
return;
|
|
28720
|
+
}
|
|
28721
|
+
await deliverEffects({
|
|
28722
|
+
anonId: snapshot.anonId,
|
|
28723
|
+
slug: target.slug,
|
|
28724
|
+
groups: verdict.effects.accepted,
|
|
28725
|
+
depth: triggerDepth(trigger),
|
|
28726
|
+
installs: target.installs,
|
|
28727
|
+
geo: trigger.kind === "event" ? trigger.action.geo : trigger.geo,
|
|
28728
|
+
host: trigger.kind === "event" ? trigger.action.host : trigger.host,
|
|
28729
|
+
context: trigger.kind === "event" ? trigger.action.context : void 0,
|
|
28730
|
+
...startedOnSocket(trigger) ? { fromSocket: true } : {}
|
|
28731
|
+
});
|
|
28732
|
+
} catch (error61) {
|
|
28733
|
+
addSpanEvent("app invocation failed", { level: "warn", attrs: { app: target.slug, error: String(error61) } });
|
|
28734
|
+
}
|
|
28735
|
+
}, "runInvokeApp");
|
|
28736
|
+
var invokeApp = /* @__PURE__ */ __name(async (env, input2) => {
|
|
28737
|
+
const { target, trigger } = input2;
|
|
28738
|
+
return traced(
|
|
28739
|
+
`app.${trigger.kind} ${target.slug}`,
|
|
28740
|
+
{
|
|
28741
|
+
attrs: {
|
|
28742
|
+
"app.slug": target.slug,
|
|
28743
|
+
"app.version": target.app.version,
|
|
28744
|
+
"app.binding": target.app.binding,
|
|
28745
|
+
"app.hook": trigger.kind === "event" ? `onEvent.${trigger.action.type}` : "onConnect",
|
|
28746
|
+
"app.installs": target.installs.length
|
|
28747
|
+
}
|
|
28748
|
+
},
|
|
28749
|
+
() => {
|
|
28750
|
+
return runInvokeApp(env, input2);
|
|
28751
|
+
}
|
|
28752
|
+
);
|
|
28753
|
+
}, "invokeApp");
|
|
28754
|
+
|
|
28755
|
+
// src/apps/broker.ts
|
|
28756
|
+
var MAX_RPC_DEPTH = 3;
|
|
28757
|
+
var BROKER_PATHS = ["/__qinto/push", "/__qinto/rpc"];
|
|
28758
|
+
var pushBody = external_exports.object({
|
|
28759
|
+
...identityFields,
|
|
28760
|
+
kind: external_exports.literal("push"),
|
|
28761
|
+
userId: external_exports.string().min(1).max(64),
|
|
28762
|
+
installId: external_exports.string().min(1).max(64).optional(),
|
|
28763
|
+
broadcast: external_exports.enum(PUSH_BROADCASTS).optional(),
|
|
28764
|
+
topic: external_exports.string().min(1).max(64),
|
|
28765
|
+
data: external_exports.unknown().optional()
|
|
28766
|
+
}).strict();
|
|
28767
|
+
var rpcBody = external_exports.object({
|
|
28768
|
+
...identityFields,
|
|
28769
|
+
kind: external_exports.literal("rpc"),
|
|
28770
|
+
// Absent when the caller is an endpoint, a schedule or a ui handler; the callee then sees no user.
|
|
28771
|
+
userId: external_exports.string().min(1).max(64).optional(),
|
|
28772
|
+
callee: external_exports.string().min(1).max(30),
|
|
28773
|
+
fn: external_exports.string().min(1).max(64),
|
|
28774
|
+
args: external_exports.unknown().optional(),
|
|
28775
|
+
installId: external_exports.string().min(1).max(64).optional(),
|
|
28776
|
+
depth: external_exports.number().int().min(0)
|
|
28777
|
+
}).strict();
|
|
28778
|
+
var installsBody = external_exports.object({
|
|
28779
|
+
...identityFields,
|
|
28780
|
+
kind: external_exports.literal("installs"),
|
|
28781
|
+
callee: external_exports.string().min(1).max(30)
|
|
28782
|
+
}).strict();
|
|
28783
|
+
var activeBody = external_exports.object({
|
|
28784
|
+
...identityFields,
|
|
28785
|
+
kind: external_exports.literal("active"),
|
|
28786
|
+
ord: external_exports.number().int().min(1),
|
|
28787
|
+
userId: external_exports.string().min(1).max(64).optional()
|
|
28788
|
+
}).strict();
|
|
28789
|
+
var contextReadBody = external_exports.object({
|
|
28790
|
+
...identityFields,
|
|
28791
|
+
kind: external_exports.literal("contextRead"),
|
|
28792
|
+
userId: external_exports.string().min(1).max(64)
|
|
28793
|
+
}).strict();
|
|
28794
|
+
var contextWriteBody = external_exports.object({
|
|
28795
|
+
...identityFields,
|
|
28796
|
+
kind: external_exports.literal("contextWrite"),
|
|
28797
|
+
userId: external_exports.string().min(1).max(64),
|
|
28798
|
+
writes: external_exports.array(contextWriteSchema).min(1).max(MAX_CONTEXT_WRITES_PER_INSTALL)
|
|
28799
|
+
}).strict();
|
|
28800
|
+
var factsQuery = external_exports.object({
|
|
28801
|
+
kinds: external_exports.array(external_exports.enum(APP_FACT_KINDS)).min(1).max(APP_FACT_KINDS.length).optional(),
|
|
28802
|
+
observedAtGte: external_exports.number().int().nonnegative().safe().optional(),
|
|
28803
|
+
observedAtLt: external_exports.number().int().nonnegative().safe().optional(),
|
|
28804
|
+
limit: external_exports.number().int().min(1).max(MAX_APP_FACTS_PER_PAGE).optional()
|
|
28805
|
+
}).strict().refine((query) => query.observedAtGte === void 0 || query.observedAtLt === void 0 || query.observedAtGte < query.observedAtLt);
|
|
28806
|
+
var factsReadBody = external_exports.object({
|
|
28807
|
+
...identityFields,
|
|
28808
|
+
kind: external_exports.literal("factsRead"),
|
|
28809
|
+
userId: external_exports.string().min(1).max(64),
|
|
28810
|
+
query: factsQuery.optional(),
|
|
28811
|
+
cursor: external_exports.string().min(1).max(4096).optional()
|
|
28812
|
+
}).strict().refine((body) => body.query === void 0 !== (body.cursor === void 0));
|
|
28813
|
+
var identityReadBody = external_exports.object({
|
|
28814
|
+
...identityFields,
|
|
28815
|
+
kind: external_exports.literal("identityRead"),
|
|
28816
|
+
identifier: identifierField2,
|
|
28817
|
+
cursor: external_exports.string().regex(/^\d+$/).optional()
|
|
28818
|
+
}).strict();
|
|
28819
|
+
var brokerBody = external_exports.discriminatedUnion("kind", [
|
|
28820
|
+
pushBody,
|
|
28821
|
+
rpcBody,
|
|
28822
|
+
installsBody,
|
|
28823
|
+
activeBody,
|
|
28824
|
+
contextReadBody,
|
|
28825
|
+
contextWriteBody,
|
|
28826
|
+
factsReadBody,
|
|
28827
|
+
identityReadBody,
|
|
28828
|
+
identityUpsertBody,
|
|
28829
|
+
currencyRatesBody,
|
|
28830
|
+
trackBody
|
|
28831
|
+
]);
|
|
28832
|
+
var BODY_ENVELOPE_ALLOWANCE_BYTES = 2 * 1024;
|
|
28833
|
+
var MAX_BODY_BYTES = MAX_RPC_BYTES + BODY_ENVELOPE_ALLOWANCE_BYTES;
|
|
28834
|
+
var parseJson = /* @__PURE__ */ __name((raw2) => {
|
|
28835
|
+
try {
|
|
28836
|
+
return JSON.parse(raw2);
|
|
28837
|
+
} catch {
|
|
28838
|
+
return null;
|
|
28839
|
+
}
|
|
28840
|
+
}, "parseJson");
|
|
28841
|
+
var factsCursor = external_exports.object({
|
|
28842
|
+
userId: external_exports.string().min(1).max(64),
|
|
28843
|
+
kinds: external_exports.array(external_exports.enum(APP_FACT_KINDS)).min(1).max(APP_FACT_KINDS.length),
|
|
28844
|
+
observedAtGte: external_exports.number().int().nonnegative().safe().nullable(),
|
|
28845
|
+
observedAtLt: external_exports.number().int().nonnegative().safe().nullable(),
|
|
28846
|
+
limit: external_exports.number().int().min(1).max(MAX_APP_FACTS_PER_PAGE),
|
|
28847
|
+
afterRowId: external_exports.number().int().nonnegative().safe(),
|
|
28848
|
+
maxRowId: external_exports.number().int().positive().safe().nullable()
|
|
28849
|
+
}).strict();
|
|
28850
|
+
var cursorKey = /* @__PURE__ */ __name(async (hex3) => {
|
|
28851
|
+
const bytes = Uint8Array.from(hex3.matchAll(/.{2}/g), ([pair]) => Number.parseInt(pair, 16));
|
|
28852
|
+
return crypto.subtle.importKey("raw", bytes, "AES-GCM", false, ["encrypt", "decrypt"]);
|
|
28853
|
+
}, "cursorKey");
|
|
28854
|
+
var encodeBase64Url = /* @__PURE__ */ __name((bytes) => {
|
|
28855
|
+
let binary = "";
|
|
28856
|
+
for (const byte of bytes) {
|
|
28857
|
+
binary += String.fromCharCode(byte);
|
|
28858
|
+
}
|
|
28859
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
28860
|
+
}, "encodeBase64Url");
|
|
28861
|
+
var decodeBase64Url = /* @__PURE__ */ __name((value) => {
|
|
28862
|
+
const base643 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
28863
|
+
const padded = base643.padEnd(Math.ceil(base643.length / 4) * 4, "=");
|
|
28864
|
+
const binary = atob(padded);
|
|
28865
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
28866
|
+
}, "decodeBase64Url");
|
|
28867
|
+
var encryptFactsCursor = /* @__PURE__ */ __name(async (key, cursor) => {
|
|
28868
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
28869
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(cursor));
|
|
28870
|
+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, await cursorKey(key), plaintext));
|
|
28871
|
+
const packed = new Uint8Array(iv.byteLength + ciphertext.byteLength);
|
|
28872
|
+
packed.set(iv);
|
|
28873
|
+
packed.set(ciphertext, iv.byteLength);
|
|
28874
|
+
return encodeBase64Url(packed);
|
|
28875
|
+
}, "encryptFactsCursor");
|
|
28876
|
+
var decryptFactsCursor = /* @__PURE__ */ __name(async (key, encoded) => {
|
|
28877
|
+
try {
|
|
28878
|
+
const packed = decodeBase64Url(encoded);
|
|
28879
|
+
if (packed.byteLength <= 12) {
|
|
28880
|
+
return null;
|
|
28881
|
+
}
|
|
28882
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: packed.slice(0, 12) }, await cursorKey(key), packed.slice(12));
|
|
28883
|
+
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
28884
|
+
} catch {
|
|
28885
|
+
return null;
|
|
28886
|
+
}
|
|
28887
|
+
}, "decryptFactsCursor");
|
|
28888
|
+
var invalidBody = /* @__PURE__ */ __name(() => {
|
|
28889
|
+
return Response.json({ error: "invalid_body" }, { status: 400 });
|
|
28890
|
+
}, "invalidBody");
|
|
28891
|
+
var forbidden = /* @__PURE__ */ __name(() => {
|
|
28892
|
+
return Response.json({ error: "forbidden" }, { status: 403 });
|
|
28893
|
+
}, "forbidden");
|
|
28894
|
+
var factsPermissionDenied = /* @__PURE__ */ __name(() => {
|
|
28895
|
+
return Response.json({ error: "facts_permission_denied" }, { status: 403 });
|
|
28896
|
+
}, "factsPermissionDenied");
|
|
28897
|
+
var handlePush = /* @__PURE__ */ __name(async (env, body) => {
|
|
28898
|
+
if (new TextEncoder().encode(JSON.stringify(body.data ?? null)).length > MAX_PUSH_BYTES) {
|
|
28899
|
+
return invalidBody();
|
|
28900
|
+
}
|
|
28901
|
+
const broadcast = body.broadcast ?? "install";
|
|
28902
|
+
if (broadcast === "install" && body.installId === void 0) {
|
|
28903
|
+
return invalidBody();
|
|
28904
|
+
}
|
|
28905
|
+
const installId = broadcast === "install" ? body.installId : null;
|
|
28906
|
+
if (await rateLimited(env, `${body.slug}:${installId ?? ""}:push`)) {
|
|
28907
|
+
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
28908
|
+
}
|
|
28909
|
+
const app2 = siteRegistry().registry.apps[body.slug];
|
|
28910
|
+
if (broadcast === "site" && (app2 === void 0 || !app2.events.publishes.includes(body.topic) || !app2.data.includes(SHARED_CONTEXT_CAPABILITY))) {
|
|
28911
|
+
return Response.json({ error: "topic_not_public" }, { status: 400 });
|
|
28912
|
+
}
|
|
28913
|
+
await anonStub(env, body.userId).appPush(
|
|
28914
|
+
{ slug: body.slug, installId, topic: body.topic, data: body.data, scope: broadcast === "site" ? "site" : "app" },
|
|
28915
|
+
ownSite(env),
|
|
28916
|
+
currentTraceContext()
|
|
28917
|
+
);
|
|
28918
|
+
return Response.json({ accepted: true }, { status: 202 });
|
|
28919
|
+
}, "handlePush");
|
|
28920
|
+
var resolveInstall = /* @__PURE__ */ __name((callee, installId) => {
|
|
28921
|
+
const active = callee.installs.filter((install) => install.active);
|
|
28922
|
+
if (installId !== void 0) {
|
|
28923
|
+
const install = active.find((entry) => entry.id === installId);
|
|
28924
|
+
return install === void 0 ? "invalid_install" : { id: install.id, ord: install.ord, label: install.label };
|
|
28925
|
+
}
|
|
28926
|
+
const [only, second] = active;
|
|
28927
|
+
return only === void 0 || second !== void 0 ? "install_required" : { id: only.id, ord: only.ord, label: only.label };
|
|
28928
|
+
}, "resolveInstall");
|
|
28929
|
+
var calleeFailed = /* @__PURE__ */ __name((callee, reason) => {
|
|
28930
|
+
addSpanEvent("rpc callee failed", { level: "warn", attrs: { app: callee, reason } });
|
|
28931
|
+
return Response.json({ error: "callee_failed" }, { status: 502 });
|
|
28932
|
+
}, "calleeFailed");
|
|
28933
|
+
var deliverRpcEffects = /* @__PURE__ */ __name(async (env, { body, userId, effects, install }) => {
|
|
28934
|
+
for (const ord of effects.rejectedOrds) {
|
|
28935
|
+
addSpanEvent("app effects rejected over the per-install caps", { level: "warn", attrs: { app: body.callee, ord } });
|
|
28936
|
+
}
|
|
28937
|
+
const site = ownSite(env);
|
|
28938
|
+
await applyEffects({
|
|
28939
|
+
slug: body.callee,
|
|
28940
|
+
groups: effects.accepted,
|
|
28941
|
+
depth: body.depth,
|
|
28942
|
+
anonId: userId,
|
|
28943
|
+
installs: [install],
|
|
28944
|
+
deliverPush: /* @__PURE__ */ __name(async (push) => {
|
|
28945
|
+
await anonStub(env, userId).appPush(push, site, currentTraceContext());
|
|
28946
|
+
}, "deliverPush"),
|
|
28947
|
+
writeCaptures: /* @__PURE__ */ __name(async (slug, writes) => {
|
|
28948
|
+
await anonStub(env, userId).appCaptures({ slug, writes }, site, currentTraceContext());
|
|
28949
|
+
}, "writeCaptures"),
|
|
28950
|
+
writeContext: /* @__PURE__ */ __name(async (slug, writes) => {
|
|
28951
|
+
await anonStub(env, userId).appContext({ slug, writes }, site, currentTraceContext());
|
|
28952
|
+
}, "writeContext"),
|
|
28953
|
+
ingest: /* @__PURE__ */ __name((action) => {
|
|
28954
|
+
return ingest(env, action, site);
|
|
28955
|
+
}, "ingest")
|
|
28956
|
+
});
|
|
28957
|
+
}, "deliverRpcEffects");
|
|
28958
|
+
var runRelayRpc = /* @__PURE__ */ __name(async (env, { context, body, callee, install }) => {
|
|
28959
|
+
let raw2;
|
|
28960
|
+
try {
|
|
28961
|
+
const key = await deriveAppInternalKey(context.master, context.domainId, body.callee);
|
|
28962
|
+
const response = await postHookEnvelope(env, {
|
|
28963
|
+
binding: callee.binding,
|
|
28964
|
+
key,
|
|
28965
|
+
envelope: {
|
|
28966
|
+
v: ENVELOPE_VERSION,
|
|
28967
|
+
kind: "rpc",
|
|
28968
|
+
app: { slug: body.callee, version: callee.version },
|
|
28969
|
+
installs: [install],
|
|
28970
|
+
// No bucket projection for rpc: the callee sees only the subject's id, never its profile.
|
|
28971
|
+
// Omitted entirely when the caller had no visitor, so the callee's ctx.user is null.
|
|
28972
|
+
...body.userId === void 0 ? {} : { user: { anonId: body.userId, globalId: null, properties: {} } },
|
|
28973
|
+
depth: body.depth,
|
|
28974
|
+
// JSON.stringify drops an undefined key, and the envelope requires `args` to be present.
|
|
28975
|
+
rpc: { caller: body.slug, fn: body.fn, args: body.args ?? null }
|
|
28976
|
+
},
|
|
28977
|
+
timeoutMs: APP_INVOCATION_TIMEOUT_MS
|
|
28978
|
+
});
|
|
28979
|
+
if (response === void 0) {
|
|
28980
|
+
return calleeFailed(body.callee, "The callee service binding is missing.");
|
|
28981
|
+
}
|
|
28982
|
+
writeAppObservability(env, response, {
|
|
28983
|
+
app: observabilityApp(body.callee, callee),
|
|
28984
|
+
installs: [install],
|
|
28985
|
+
budget: createDataPointBudget()
|
|
28986
|
+
});
|
|
28987
|
+
setSpanAttributes({ "app.status": response.status });
|
|
28988
|
+
if (!response.ok) {
|
|
28989
|
+
return calleeFailed(body.callee, `The callee answered status ${response.status}.`);
|
|
28990
|
+
}
|
|
28991
|
+
const bytes = await readBoundedBody(response, MAX_RESPONSE_BYTES);
|
|
28992
|
+
if (bytes === null) {
|
|
28993
|
+
return calleeFailed(body.callee, "The response exceeds the size cap.");
|
|
28994
|
+
}
|
|
28995
|
+
raw2 = new TextDecoder().decode(bytes);
|
|
28996
|
+
} catch (error61) {
|
|
28997
|
+
return calleeFailed(body.callee, String(error61));
|
|
28998
|
+
}
|
|
28999
|
+
const verdict = validateEnvelopeResponse(parseJson(raw2), 1);
|
|
29000
|
+
if (!verdict.ok) {
|
|
29001
|
+
return calleeFailed(body.callee, verdict.reason);
|
|
29002
|
+
}
|
|
29003
|
+
if (verdict.error !== null) {
|
|
29004
|
+
const reason = redactUrlParams(verdict.error);
|
|
29005
|
+
if (verdict.errorCode === "invalid_arguments") {
|
|
29006
|
+
return Response.json({ error: "invalid_arguments", message: reason }, { status: 400 });
|
|
29007
|
+
}
|
|
29008
|
+
return calleeFailed(body.callee, reason);
|
|
29009
|
+
}
|
|
29010
|
+
if (verdict.rpc === null) {
|
|
29011
|
+
return calleeFailed(body.callee, "The response carries no rpc result.");
|
|
29012
|
+
}
|
|
29013
|
+
if (body.userId === void 0) {
|
|
29014
|
+
if (verdict.effects.accepted.length > 0) {
|
|
29015
|
+
addSpanEvent("rpc effects dropped: the call carries no subject", { level: "warn", attrs: { app: body.callee } });
|
|
29016
|
+
}
|
|
29017
|
+
} else {
|
|
29018
|
+
await deliverRpcEffects(env, { body, userId: body.userId, effects: verdict.effects, install });
|
|
29019
|
+
}
|
|
29020
|
+
return Response.json({ result: verdict.rpc.result });
|
|
29021
|
+
}, "runRelayRpc");
|
|
29022
|
+
var relayRpc = /* @__PURE__ */ __name(async (env, input2) => {
|
|
29023
|
+
const { body, callee, install } = input2;
|
|
29024
|
+
return traced(
|
|
29025
|
+
`app.rpc ${body.callee}`,
|
|
29026
|
+
{
|
|
29027
|
+
attrs: {
|
|
29028
|
+
"app.slug": body.callee,
|
|
29029
|
+
"app.version": callee.version,
|
|
29030
|
+
"app.binding": callee.binding,
|
|
29031
|
+
"app.hook": `rpc.${body.fn}`,
|
|
29032
|
+
"app.install_id": install.id,
|
|
29033
|
+
"app.install_ord": install.ord,
|
|
29034
|
+
"app.install_label": install.label,
|
|
29035
|
+
"app.rpc_caller": body.slug,
|
|
29036
|
+
"app.rpc_depth": body.depth
|
|
29037
|
+
}
|
|
29038
|
+
},
|
|
29039
|
+
() => {
|
|
29040
|
+
return runRelayRpc(env, input2);
|
|
29041
|
+
}
|
|
29042
|
+
);
|
|
29043
|
+
}, "relayRpc");
|
|
29044
|
+
var handleRpc = /* @__PURE__ */ __name(async (env, context, body) => {
|
|
29045
|
+
if (body.depth > MAX_RPC_DEPTH) {
|
|
29046
|
+
return Response.json({ error: "depth_exceeded" }, { status: 400 });
|
|
29047
|
+
}
|
|
29048
|
+
const callee = siteRegistry().registry.apps[body.callee];
|
|
29049
|
+
if (callee === void 0) {
|
|
29050
|
+
return forbidden();
|
|
29051
|
+
}
|
|
29052
|
+
const visibility = callee.rpc.exposes[body.fn];
|
|
29053
|
+
if (visibility !== "public" && !(visibility === "private" && (callee.rpc.grants[body.slug] ?? []).includes(body.fn))) {
|
|
29054
|
+
return forbidden();
|
|
29055
|
+
}
|
|
29056
|
+
const install = resolveInstall(callee, body.installId);
|
|
29057
|
+
if (install === "invalid_install" || install === "install_required") {
|
|
29058
|
+
return Response.json({ error: install }, { status: 400 });
|
|
29059
|
+
}
|
|
29060
|
+
if (new TextEncoder().encode(JSON.stringify(body.args ?? null)).length > MAX_RPC_BYTES) {
|
|
29061
|
+
return Response.json({ error: "too_large" }, { status: 413 });
|
|
29062
|
+
}
|
|
29063
|
+
if (await rateLimited(env, `${body.slug}:${install.id}:rpc`)) {
|
|
29064
|
+
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
29065
|
+
}
|
|
29066
|
+
return relayRpc(env, { context, body, callee, install });
|
|
29067
|
+
}, "handleRpc");
|
|
29068
|
+
var handleInstalls = /* @__PURE__ */ __name(async (env, body) => {
|
|
29069
|
+
const callee = siteRegistry().registry.apps[body.callee];
|
|
29070
|
+
if (callee === void 0 || !(Object.values(callee.rpc.exposes).includes("public") || (callee.rpc.grants[body.slug] ?? []).length > 0)) {
|
|
29071
|
+
return forbidden();
|
|
29072
|
+
}
|
|
29073
|
+
if (await rateLimited(env, `${body.slug}::installs`)) {
|
|
29074
|
+
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
29075
|
+
}
|
|
29076
|
+
const installs = callee.installs.filter((install) => install.active).map(({ id, label }) => ({ id, label }));
|
|
29077
|
+
return Response.json({ installs });
|
|
29078
|
+
}, "handleInstalls");
|
|
29079
|
+
var handleContextRead = /* @__PURE__ */ __name(async (env, body) => {
|
|
29080
|
+
if (await rateLimited(env, `${body.slug}::context`)) {
|
|
29081
|
+
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
29082
|
+
}
|
|
29083
|
+
return Response.json(await anonStub(env, body.userId).getAppContext(body.slug, ownSite(env), currentTraceContext()));
|
|
29084
|
+
}, "handleContextRead");
|
|
29085
|
+
var handleContextWrite = /* @__PURE__ */ __name(async (env, body) => {
|
|
29086
|
+
if (await rateLimited(env, `${body.slug}::context`)) {
|
|
29087
|
+
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
29088
|
+
}
|
|
29089
|
+
const stored = await anonStub(env, body.userId).appContext({ slug: body.slug, writes: body.writes }, ownSite(env), currentTraceContext());
|
|
29090
|
+
return Response.json({ stored }, { status: 202 });
|
|
29091
|
+
}, "handleContextWrite");
|
|
29092
|
+
var initialFactsCursor = /* @__PURE__ */ __name((body) => {
|
|
29093
|
+
const query = body.query;
|
|
29094
|
+
return {
|
|
29095
|
+
userId: body.userId,
|
|
29096
|
+
kinds: query.kinds ?? [...APP_FACT_KINDS],
|
|
29097
|
+
observedAtGte: query.observedAtGte ?? null,
|
|
29098
|
+
observedAtLt: query.observedAtLt ?? null,
|
|
29099
|
+
limit: query.limit ?? MAX_APP_FACTS_PER_PAGE,
|
|
29100
|
+
afterRowId: 0,
|
|
29101
|
+
maxRowId: null
|
|
29102
|
+
};
|
|
29103
|
+
}, "initialFactsCursor");
|
|
29104
|
+
var handleFactsRead = /* @__PURE__ */ __name(async (env, body) => {
|
|
29105
|
+
const app2 = siteRegistry().registry.apps[body.slug];
|
|
29106
|
+
if (app2 === void 0 || !app2.data.includes("facts")) {
|
|
29107
|
+
return factsPermissionDenied();
|
|
29108
|
+
}
|
|
29109
|
+
if (await rateLimited(env, `${body.slug}::facts`)) {
|
|
29110
|
+
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
29111
|
+
}
|
|
29112
|
+
const decoded = body.cursor === void 0 ? initialFactsCursor(body) : await decryptFactsCursor(body.key, body.cursor);
|
|
29113
|
+
if (decoded === null || decoded.userId !== body.userId || body.cursor !== void 0 && decoded.maxRowId === null) {
|
|
29114
|
+
return Response.json({ error: "invalid_cursor" }, { status: 400 });
|
|
29115
|
+
}
|
|
29116
|
+
const query = {
|
|
29117
|
+
kinds: decoded.kinds,
|
|
29118
|
+
observedAtGte: decoded.observedAtGte,
|
|
29119
|
+
observedAtLt: decoded.observedAtLt,
|
|
29120
|
+
limit: decoded.limit,
|
|
29121
|
+
afterRowId: decoded.afterRowId,
|
|
29122
|
+
maxRowId: decoded.maxRowId
|
|
29123
|
+
};
|
|
29124
|
+
const page = await anonStub(env, body.userId).getAppFacts(body.slug, query, ownSite(env), currentTraceContext());
|
|
29125
|
+
if (page === null) {
|
|
29126
|
+
return factsPermissionDenied();
|
|
29127
|
+
}
|
|
29128
|
+
const nextCursor = page.next === null ? null : await encryptFactsCursor(body.key, {
|
|
29129
|
+
...decoded,
|
|
29130
|
+
afterRowId: page.next.afterRowId,
|
|
29131
|
+
maxRowId: page.next.maxRowId
|
|
29132
|
+
});
|
|
29133
|
+
return Response.json({ items: page.items, nextCursor });
|
|
29134
|
+
}, "handleFactsRead");
|
|
29135
|
+
var handleIdentityRead = /* @__PURE__ */ __name(async (env, body) => {
|
|
29136
|
+
if (!identityGranted(body.slug)) {
|
|
29137
|
+
return identityPermissionDenied();
|
|
29138
|
+
}
|
|
29139
|
+
if (await rateLimited(env, `${body.slug}::identity`)) {
|
|
29140
|
+
return Response.json({ error: "rate_limited" }, { status: 429 });
|
|
29141
|
+
}
|
|
29142
|
+
const person2 = await lookupPersonForApp(env, body.slug, body.identifier, body.cursor === void 0 ? 0 : Number(body.cursor));
|
|
29143
|
+
if (person2 === null) {
|
|
29144
|
+
return identityPermissionDenied();
|
|
29145
|
+
}
|
|
29146
|
+
return Response.json(person2);
|
|
29147
|
+
}, "handleIdentityRead");
|
|
29148
|
+
var handleActive = /* @__PURE__ */ __name(async (env, body) => {
|
|
29149
|
+
const app2 = siteRegistry().registry.apps[body.slug];
|
|
29150
|
+
if (app2 === void 0) {
|
|
29151
|
+
return forbidden();
|
|
29152
|
+
}
|
|
29153
|
+
if (!app2.installs.some((install) => install.active && install.ord === body.ord)) {
|
|
29154
|
+
return forbidden();
|
|
29155
|
+
}
|
|
29156
|
+
if (body.userId !== void 0 && !await anonStub(env, body.userId).appAllowed(body.slug, ownSite(env), currentTraceContext())) {
|
|
29157
|
+
return forbidden();
|
|
29158
|
+
}
|
|
29159
|
+
return Response.json({ active: true });
|
|
29160
|
+
}, "handleActive");
|
|
29161
|
+
var handleBroker = /* @__PURE__ */ __name(async (request, env, ctx) => {
|
|
29162
|
+
const bytes = await readBoundedBody(request, MAX_BODY_BYTES);
|
|
29163
|
+
if (bytes === null) {
|
|
29164
|
+
return Response.json({ error: "too_large" }, { status: 413 });
|
|
29165
|
+
}
|
|
29166
|
+
const candidate = parseJson(new TextDecoder().decode(bytes));
|
|
29167
|
+
const parsed = brokerBody.safeParse(candidate);
|
|
29168
|
+
if (!parsed.success) {
|
|
29169
|
+
if (typeof candidate === "object" && candidate !== null && "kind" in candidate && candidate.kind === "factsRead") {
|
|
29170
|
+
const malformedFacts = candidate;
|
|
29171
|
+
const error61 = malformedFacts.cursor !== void 0 && malformedFacts.query === void 0 ? "invalid_cursor" : "invalid_query";
|
|
29172
|
+
return Response.json({ error: error61 }, { status: 400 });
|
|
29173
|
+
}
|
|
29174
|
+
return invalidBody();
|
|
29175
|
+
}
|
|
29176
|
+
const body = parsed.data;
|
|
29177
|
+
if (siteRegistry().registry.apps[body.slug] === void 0) {
|
|
29178
|
+
return forbidden();
|
|
29179
|
+
}
|
|
29180
|
+
const context = invocationContext(env);
|
|
29181
|
+
if (context === void 0) {
|
|
29182
|
+
return forbidden();
|
|
29183
|
+
}
|
|
29184
|
+
const expected = await deriveAppInternalKey(context.master, context.domainId, body.slug);
|
|
29185
|
+
if (!constantTimeEqual(expected, body.key)) {
|
|
29186
|
+
return forbidden();
|
|
29187
|
+
}
|
|
29188
|
+
switch (body.kind) {
|
|
29189
|
+
case "push": {
|
|
29190
|
+
return handlePush(env, body);
|
|
29191
|
+
}
|
|
29192
|
+
case "rpc": {
|
|
29193
|
+
return handleRpc(env, context, body);
|
|
29194
|
+
}
|
|
29195
|
+
case "installs": {
|
|
29196
|
+
return handleInstalls(env, body);
|
|
29197
|
+
}
|
|
29198
|
+
case "active": {
|
|
29199
|
+
return handleActive(env, body);
|
|
29200
|
+
}
|
|
29201
|
+
case "contextRead": {
|
|
29202
|
+
return handleContextRead(env, body);
|
|
29203
|
+
}
|
|
29204
|
+
case "contextWrite": {
|
|
29205
|
+
return handleContextWrite(env, body);
|
|
29206
|
+
}
|
|
29207
|
+
case "factsRead": {
|
|
29208
|
+
return handleFactsRead(env, body);
|
|
29209
|
+
}
|
|
29210
|
+
case "identityRead": {
|
|
29211
|
+
return handleIdentityRead(env, body);
|
|
29212
|
+
}
|
|
29213
|
+
case "identityUpsert": {
|
|
29214
|
+
return handleIdentityUpsert(env, body);
|
|
29215
|
+
}
|
|
29216
|
+
case "currencyRates": {
|
|
29217
|
+
return handleCurrencyRates(ctx);
|
|
29218
|
+
}
|
|
29219
|
+
case "track": {
|
|
29220
|
+
return handleTrack(env, body);
|
|
29221
|
+
}
|
|
29222
|
+
}
|
|
29223
|
+
}, "handleBroker");
|
|
29224
|
+
var brokerRoutes = new Hono2();
|
|
29225
|
+
var dispatchGated = /* @__PURE__ */ __name((c) => {
|
|
29226
|
+
if (c.env.DEPLOY_TARGET !== "dispatch") {
|
|
29227
|
+
return c.notFound();
|
|
29228
|
+
}
|
|
29229
|
+
return handleBroker(c.req.raw, c.env, c.executionCtx);
|
|
29230
|
+
}, "dispatchGated");
|
|
29231
|
+
for (const path of BROKER_PATHS) {
|
|
29232
|
+
brokerRoutes.post(path, dispatchGated);
|
|
29233
|
+
}
|
|
29234
|
+
var Broker = class extends WorkerEntrypoint {
|
|
29235
|
+
static {
|
|
29236
|
+
__name(this, "Broker");
|
|
29237
|
+
}
|
|
29238
|
+
fetch(request) {
|
|
29239
|
+
const trace = parseTraceContext(request.headers.get(TRACE_HEADER));
|
|
29240
|
+
return runInvocation({ name: "broker", trace }, () => {
|
|
29241
|
+
return handleBroker(request, this.env, this.ctx);
|
|
29242
|
+
});
|
|
29243
|
+
}
|
|
29244
|
+
};
|
|
29245
|
+
|
|
29246
|
+
// src/apps-control.ts
|
|
29247
|
+
import controlText from "./apps-control.json";
|
|
29248
|
+
var apps_control_default = controlText;
|
|
29249
|
+
|
|
29250
|
+
// src/apps/control-targets.ts
|
|
29251
|
+
var controlTargetsSchema = external_exports.object({
|
|
29252
|
+
v: external_exports.literal(1),
|
|
29253
|
+
apps: external_exports.record(
|
|
29254
|
+
external_exports.string(),
|
|
29255
|
+
external_exports.object({
|
|
29256
|
+
binding: external_exports.string().min(1),
|
|
29257
|
+
versions: external_exports.array(external_exports.object({ version: external_exports.string().min(1), versionId: external_exports.string().min(1) }).strict()).min(1)
|
|
29258
|
+
}).strict()
|
|
29259
|
+
)
|
|
29260
|
+
}).strict();
|
|
29261
|
+
var EMPTY_TARGETS = Object.freeze({ v: 1, apps: Object.freeze({}) });
|
|
29262
|
+
var parseControlTargets = /* @__PURE__ */ __name((raw2) => {
|
|
29263
|
+
try {
|
|
29264
|
+
const parsed = controlTargetsSchema.safeParse(JSON.parse(raw2));
|
|
29265
|
+
return parsed.success ? parsed.data : EMPTY_TARGETS;
|
|
29266
|
+
} catch {
|
|
29267
|
+
return EMPTY_TARGETS;
|
|
29268
|
+
}
|
|
29269
|
+
}, "parseControlTargets");
|
|
29270
|
+
var cachedRaw3;
|
|
29271
|
+
var cachedTargets = EMPTY_TARGETS;
|
|
29272
|
+
var controlTargets = /* @__PURE__ */ __name(() => {
|
|
29273
|
+
if (apps_control_default !== cachedRaw3) {
|
|
29274
|
+
cachedTargets = parseControlTargets(apps_control_default);
|
|
29275
|
+
cachedRaw3 = apps_control_default;
|
|
29276
|
+
}
|
|
29277
|
+
return cachedTargets;
|
|
29278
|
+
}, "controlTargets");
|
|
29279
|
+
|
|
29280
|
+
// src/apps/control.ts
|
|
29281
|
+
var CONTROL_PATH = "/__qinto/control";
|
|
29282
|
+
var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
|
|
29283
|
+
var observabilityTarget = /* @__PURE__ */ __name((envelope) => {
|
|
29284
|
+
const installs = envelope.schedule?.installs ?? (envelope.install === void 0 ? [] : [envelope.install]);
|
|
29285
|
+
const app2 = siteRegistry().registry.apps[envelope.app.slug];
|
|
29286
|
+
if (installs.length === 0 || app2 === void 0) {
|
|
29287
|
+
return void 0;
|
|
29288
|
+
}
|
|
29289
|
+
return {
|
|
29290
|
+
app: observabilityApp(envelope.app.slug, { name: app2.name, version: envelope.app.version }),
|
|
29291
|
+
installs,
|
|
29292
|
+
budget: createDataPointBudget()
|
|
29293
|
+
};
|
|
29294
|
+
}, "observabilityTarget");
|
|
29295
|
+
var writeControlObservability = /* @__PURE__ */ __name((env, upstream, envelope) => {
|
|
29296
|
+
const target = observabilityTarget(envelope);
|
|
29297
|
+
if (target !== void 0) {
|
|
29298
|
+
writeAppObservability(env, upstream, target);
|
|
29299
|
+
}
|
|
29300
|
+
}, "writeControlObservability");
|
|
29301
|
+
var timedOutAction = /* @__PURE__ */ __name((envelope) => {
|
|
29302
|
+
if (envelope.schedule !== void 0) {
|
|
29303
|
+
return `schedules.${envelope.schedule.id}`;
|
|
29304
|
+
}
|
|
29305
|
+
if (envelope.tool !== void 0) {
|
|
29306
|
+
return `tools.${envelope.tool.name}`;
|
|
29307
|
+
}
|
|
29308
|
+
return envelope.lifecycle === void 0 ? envelope.kind : `lifecycle.${envelope.lifecycle.type}`;
|
|
29309
|
+
}, "timedOutAction");
|
|
29310
|
+
var writeControlTimeout = /* @__PURE__ */ __name((env, envelope, deadlineMs) => {
|
|
29311
|
+
const target = observabilityTarget(envelope);
|
|
29312
|
+
if (target === void 0) {
|
|
29313
|
+
return;
|
|
29314
|
+
}
|
|
29315
|
+
const action = timedOutAction(envelope);
|
|
29316
|
+
const description = `${action} did not answer within ${deadlineMs / 1e3} s, so the site stopped waiting.`;
|
|
29317
|
+
writeAppObservabilityRecord(
|
|
29318
|
+
env,
|
|
29319
|
+
{
|
|
29320
|
+
v: APP_OBSERVABILITY_VERSION,
|
|
29321
|
+
events: [],
|
|
29322
|
+
errors: target.installs.map((install) => {
|
|
29323
|
+
return { ord: install.ord, category: "internal", action, description };
|
|
29324
|
+
})
|
|
29325
|
+
},
|
|
29326
|
+
target
|
|
29327
|
+
);
|
|
29328
|
+
}, "writeControlTimeout");
|
|
29329
|
+
var forwardControl = /* @__PURE__ */ __name(async (env, service, envelope, init) => {
|
|
29330
|
+
const deadlineMs = CONTROL_DEADLINES_MS[envelope.kind].site;
|
|
29331
|
+
try {
|
|
29332
|
+
return await service.fetch("https://app/__qinto/control", { method: "POST", ...init, signal: AbortSignal.timeout(deadlineMs) });
|
|
29333
|
+
} catch (error61) {
|
|
29334
|
+
if (!(error61 instanceof Error && error61.name === "TimeoutError")) {
|
|
29335
|
+
throw error61;
|
|
29336
|
+
}
|
|
29337
|
+
writeControlTimeout(env, envelope, deadlineMs);
|
|
29338
|
+
return void 0;
|
|
29339
|
+
}
|
|
29340
|
+
}, "forwardControl");
|
|
29341
|
+
var textBody = /* @__PURE__ */ __name(async (request) => {
|
|
29342
|
+
const bytes = await readBoundedBody(request, MAX_CONTROL_BYTES);
|
|
29343
|
+
return bytes === null ? null : new TextDecoder().decode(bytes);
|
|
29344
|
+
}, "textBody");
|
|
29345
|
+
var parseJson2 = /* @__PURE__ */ __name((value) => {
|
|
29346
|
+
try {
|
|
29347
|
+
return JSON.parse(value);
|
|
29348
|
+
} catch {
|
|
29349
|
+
return void 0;
|
|
29350
|
+
}
|
|
29351
|
+
}, "parseJson");
|
|
29352
|
+
var authenticateControl = /* @__PURE__ */ __name(async (request, env, body) => {
|
|
29353
|
+
const timestamp = request.headers.get(CONTROL_TIMESTAMP_HEADER);
|
|
29354
|
+
const signature = request.headers.get(CONTROL_SIGNATURE_HEADER);
|
|
29355
|
+
const { APPS_KEY_MASTER: master, QINTO_CONTROL_KEY: controlKey, QINTO_DOMAIN_ID: domainId } = env;
|
|
29356
|
+
if (timestamp === null || signature === null || master === void 0 || controlKey === void 0 || domainId === void 0) {
|
|
29357
|
+
return null;
|
|
29358
|
+
}
|
|
29359
|
+
const timestampMs = Number(timestamp);
|
|
29360
|
+
if (!Number.isSafeInteger(timestampMs) || Math.abs(Date.now() - timestampMs) > MAX_CLOCK_SKEW_MS) {
|
|
29361
|
+
return null;
|
|
29362
|
+
}
|
|
29363
|
+
return await verifyControlRequestSignature(controlKey, timestamp, body, signature) ? { master, domainId } : null;
|
|
29364
|
+
}, "authenticateControl");
|
|
29365
|
+
var handleControl = /* @__PURE__ */ __name(async (request, env) => {
|
|
29366
|
+
const body = await textBody(request);
|
|
29367
|
+
if (body === null) {
|
|
29368
|
+
return Response.json({ error: "too_large" }, { status: 413 });
|
|
29369
|
+
}
|
|
29370
|
+
const authentication = await authenticateControl(request, env, body);
|
|
29371
|
+
if (authentication === null) {
|
|
29372
|
+
return Response.json({ error: "unauthorized" }, { status: 401 });
|
|
29373
|
+
}
|
|
29374
|
+
const raw2 = parseJson2(body);
|
|
29375
|
+
if (raw2 === void 0) {
|
|
29376
|
+
return Response.json({ error: "invalid_body" }, { status: 400 });
|
|
29377
|
+
}
|
|
29378
|
+
const parsed = controlEnvelopeSchema.safeParse(raw2);
|
|
29379
|
+
if (!parsed.success) {
|
|
29380
|
+
return Response.json({ error: "invalid_body" }, { status: 400 });
|
|
29381
|
+
}
|
|
29382
|
+
const envelope = parsed.data;
|
|
29383
|
+
const target = controlTargets().apps[envelope.app.slug];
|
|
29384
|
+
if (target === void 0) {
|
|
29385
|
+
return Response.json({ error: "target_missing" }, { status: 404 });
|
|
29386
|
+
}
|
|
29387
|
+
if (!target.versions.some((version2) => version2.version === envelope.app.version)) {
|
|
29388
|
+
return Response.json({ error: "target_version_mismatch" }, { status: 409 });
|
|
29389
|
+
}
|
|
29390
|
+
const service = env[target.binding];
|
|
29391
|
+
if (typeof service?.fetch !== "function") {
|
|
29392
|
+
return Response.json({ error: "binding_missing" }, { status: 503 });
|
|
29393
|
+
}
|
|
29394
|
+
const headers = {
|
|
29395
|
+
"content-type": "application/json",
|
|
29396
|
+
"x-qinto-app-key": await deriveAppInternalKey(authentication.master, authentication.domainId, envelope.app.slug)
|
|
29397
|
+
};
|
|
29398
|
+
const trace = currentTraceContext();
|
|
29399
|
+
if (trace !== void 0) {
|
|
29400
|
+
headers[TRACE_HEADER] = serializeTraceContext(trace);
|
|
29401
|
+
}
|
|
29402
|
+
const upstream = await forwardControl(env, service, envelope, { headers, body });
|
|
29403
|
+
if (upstream === void 0) {
|
|
29404
|
+
return Response.json({ error: "upstream_timeout" }, { status: 504 });
|
|
29405
|
+
}
|
|
29406
|
+
adoptAppSpans(upstream);
|
|
29407
|
+
writeControlObservability(env, upstream, envelope);
|
|
29408
|
+
const responseBytes = await readBoundedBody(upstream, MAX_CONTROL_BYTES);
|
|
29409
|
+
if (responseBytes === null) {
|
|
29410
|
+
return Response.json({ error: "invalid_response" }, { status: 502 });
|
|
29411
|
+
}
|
|
29412
|
+
const responseBody = parseJson2(new TextDecoder().decode(responseBytes));
|
|
29413
|
+
if (responseBody === void 0) {
|
|
29414
|
+
return Response.json({ error: "invalid_response" }, { status: 502 });
|
|
29415
|
+
}
|
|
29416
|
+
const response = controlResponseSchema.safeParse(responseBody);
|
|
29417
|
+
if (!upstream.ok || !response.success) {
|
|
29418
|
+
return Response.json({ error: "invalid_response" }, { status: 502 });
|
|
29419
|
+
}
|
|
29420
|
+
return Response.json(response.data);
|
|
29421
|
+
}, "handleControl");
|
|
29422
|
+
var appControlRoutes = new Hono2().post(CONTROL_PATH, (c) => {
|
|
29423
|
+
return handleControl(c.req.raw, c.env);
|
|
29424
|
+
});
|
|
29425
|
+
|
|
29426
|
+
// ../../libs/worker-access-token/src/endpoints.ts
|
|
29427
|
+
var APP_ENDPOINT_PREFIX = "/app/";
|
|
29428
|
+
var MAX_CUSTOM_ENDPOINT_LENGTH = 128;
|
|
29429
|
+
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
29430
|
+
var HTTP_METHOD_BIT = { GET: 1, POST: 2, PUT: 4, PATCH: 8, DELETE: 16 };
|
|
29431
|
+
var ALL_METHODS_MASK = HTTP_METHODS.reduce((mask, method) => mask | HTTP_METHOD_BIT[method], 0);
|
|
29432
|
+
var ALLOWED_CHARS = /^[A-Za-z0-9._~/-]+$/;
|
|
29433
|
+
var matchesAppEndpoint = /* @__PURE__ */ __name((endpoints, path, method) => {
|
|
29434
|
+
const bit = HTTP_METHODS.includes(method) ? HTTP_METHOD_BIT[method] : 0;
|
|
29435
|
+
return endpoints.some((endpoint) => {
|
|
29436
|
+
if ((endpoint.methods & bit) === 0) {
|
|
29437
|
+
return false;
|
|
29438
|
+
}
|
|
29439
|
+
return endpoint.path.endsWith("/*") ? path.startsWith(endpoint.path.slice(0, -1)) : path === endpoint.path;
|
|
29440
|
+
});
|
|
29441
|
+
}, "matchesAppEndpoint");
|
|
29442
|
+
var validateAppEndpoint = /* @__PURE__ */ __name((raw2) => {
|
|
29443
|
+
if (!Number.isInteger(raw2.methods) || raw2.methods <= 0 || raw2.methods > ALL_METHODS_MASK) {
|
|
29444
|
+
return { ok: false, reason: "Choose at least one HTTP method." };
|
|
29445
|
+
}
|
|
29446
|
+
const path = raw2.path.trim();
|
|
29447
|
+
if (!path.startsWith(APP_ENDPOINT_PREFIX)) {
|
|
29448
|
+
return { ok: false, reason: "Custom endpoints must start with /app/." };
|
|
29449
|
+
}
|
|
29450
|
+
if (path.length > MAX_CUSTOM_ENDPOINT_LENGTH) {
|
|
29451
|
+
return { ok: false, reason: `Custom endpoint must be ${MAX_CUSTOM_ENDPOINT_LENGTH} characters or fewer.` };
|
|
29452
|
+
}
|
|
29453
|
+
const isWildcard = path.endsWith("/*");
|
|
29454
|
+
if (path.includes("*") && (!isWildcard || path.indexOf("*") !== path.length - 1)) {
|
|
29455
|
+
return { ok: false, reason: "A wildcard is only allowed as a trailing /* (e.g. /app/reports/*)." };
|
|
29456
|
+
}
|
|
29457
|
+
if (!isWildcard && path.endsWith("/")) {
|
|
29458
|
+
return { ok: false, reason: "Custom endpoint must not end with a slash." };
|
|
29459
|
+
}
|
|
29460
|
+
const body = isWildcard ? path.slice(0, -1) : path;
|
|
29461
|
+
if (!ALLOWED_CHARS.test(body)) {
|
|
29462
|
+
return { ok: false, reason: "Custom endpoint contains invalid characters." };
|
|
29463
|
+
}
|
|
29464
|
+
if (body.includes("..")) {
|
|
29465
|
+
return { ok: false, reason: 'Custom endpoint must not contain "..".' };
|
|
29466
|
+
}
|
|
29467
|
+
return { ok: true, value: { methods: raw2.methods, path } };
|
|
29468
|
+
}, "validateAppEndpoint");
|
|
29469
|
+
|
|
29470
|
+
// ../../libs/worker-access-token/src/scopes.ts
|
|
29471
|
+
var SCOPE = {
|
|
29472
|
+
track: 1,
|
|
29473
|
+
identify: 2,
|
|
29474
|
+
alias: 4,
|
|
29475
|
+
consentWrite: 8,
|
|
29476
|
+
propertiesRead: 16,
|
|
29477
|
+
consentRead: 32,
|
|
29478
|
+
sessionRead: 64,
|
|
29479
|
+
idGraphInspect: 128,
|
|
29480
|
+
capture: 256
|
|
29481
|
+
};
|
|
29482
|
+
var ALL_SCOPE_MASK = Object.values(SCOPE).reduce((mask, bit) => mask | bit, 0);
|
|
29483
|
+
var WORKER_ACCESS_TOKEN_SCOPES = [
|
|
29484
|
+
{ key: "track", bit: SCOPE.track, label: "Track events", description: "POST /v1/track \u2014 record events a user performed." },
|
|
29485
|
+
{ key: "identify", bit: SCOPE.identify, label: "Identify users", description: "POST /v1/identify \u2014 attach identity traits to a user." },
|
|
29486
|
+
{ key: "alias", bit: SCOPE.alias, label: "Link identifiers", description: "POST /v1/alias \u2014 link an identifier of your own to a user." },
|
|
29487
|
+
{
|
|
29488
|
+
key: "consentWrite",
|
|
29489
|
+
bit: SCOPE.consentWrite,
|
|
29490
|
+
label: "Save consent",
|
|
29491
|
+
description: "POST /v1/consent \u2014 record a user\u2019s consent choices."
|
|
29492
|
+
},
|
|
29493
|
+
{
|
|
29494
|
+
key: "propertiesRead",
|
|
29495
|
+
bit: SCOPE.propertiesRead,
|
|
29496
|
+
label: "Read properties",
|
|
29497
|
+
description: "GET /v1/properties \u2014 read the properties stored for a user."
|
|
29498
|
+
},
|
|
29499
|
+
{
|
|
29500
|
+
key: "consentRead",
|
|
29501
|
+
bit: SCOPE.consentRead,
|
|
29502
|
+
label: "Read consent",
|
|
29503
|
+
description: "GET /v1/consent \u2014 read the consent stored for a user."
|
|
29504
|
+
},
|
|
29505
|
+
{
|
|
29506
|
+
key: "sessionRead",
|
|
29507
|
+
bit: SCOPE.sessionRead,
|
|
29508
|
+
label: "Read session ids",
|
|
29509
|
+
description: "GET /v1/session, GET /v1/person \u2014 read the anonymous and person ids for a user, and the sessions linked to a person."
|
|
29510
|
+
},
|
|
29511
|
+
{
|
|
29512
|
+
key: "capture",
|
|
29513
|
+
bit: SCOPE.capture,
|
|
29514
|
+
label: "Write captures",
|
|
29515
|
+
description: "POST /v1/capture \u2014 record click ids and vendor ids an installed app declares."
|
|
29516
|
+
}
|
|
29517
|
+
];
|
|
29518
|
+
|
|
29519
|
+
// ../../libs/worker-access-token/src/env.ts
|
|
29520
|
+
var validMask = /* @__PURE__ */ __name((mask) => {
|
|
29521
|
+
return typeof mask === "number" && Number.isSafeInteger(mask) && mask >= 0 && mask <= ALL_SCOPE_MASK;
|
|
29522
|
+
}, "validMask");
|
|
29523
|
+
var validApp = /* @__PURE__ */ __name((app2) => {
|
|
29524
|
+
return app2 === void 0 || typeof app2 === "string" && app2.length > 0 && app2.length <= 30;
|
|
29525
|
+
}, "validApp");
|
|
29526
|
+
var decodeEndpoints = /* @__PURE__ */ __name((value) => {
|
|
29527
|
+
if (value === void 0) {
|
|
29528
|
+
return { endpoints: [], dropped: 0 };
|
|
29529
|
+
}
|
|
29530
|
+
if (!Array.isArray(value)) {
|
|
29531
|
+
return { endpoints: [], dropped: 1 };
|
|
29532
|
+
}
|
|
29533
|
+
const endpoints = [];
|
|
29534
|
+
let dropped = 0;
|
|
29535
|
+
for (const encoded of value) {
|
|
29536
|
+
if (!Array.isArray(encoded) || encoded.length !== 2 || typeof encoded[0] !== "number" || typeof encoded[1] !== "string") {
|
|
29537
|
+
dropped += 1;
|
|
29538
|
+
continue;
|
|
29539
|
+
}
|
|
29540
|
+
const result = validateAppEndpoint({ methods: encoded[0], path: encoded[1] });
|
|
29541
|
+
if (!result.ok) {
|
|
29542
|
+
dropped += 1;
|
|
29543
|
+
continue;
|
|
29544
|
+
}
|
|
29545
|
+
endpoints.push(Object.freeze(result.value));
|
|
29546
|
+
}
|
|
29547
|
+
return { endpoints: Object.freeze(endpoints), dropped };
|
|
29548
|
+
}, "decodeEndpoints");
|
|
29549
|
+
var parseTokenEntry = /* @__PURE__ */ __name((value) => {
|
|
29550
|
+
if (typeof value === "number") {
|
|
29551
|
+
return { entry: validMask(value) && value > 0 ? Object.freeze({ mask: value, app: null, endpoints: null }) : null, dropped: 0 };
|
|
29552
|
+
}
|
|
29553
|
+
if (typeof value !== "object" || value === null) {
|
|
29554
|
+
return { entry: null, dropped: 0 };
|
|
29555
|
+
}
|
|
29556
|
+
const entry = value;
|
|
29557
|
+
if (!validMask(entry.m) || !validApp(entry.a)) {
|
|
29558
|
+
return { entry: null, dropped: 0 };
|
|
29559
|
+
}
|
|
29560
|
+
const decoded = decodeEndpoints(entry.e);
|
|
29561
|
+
if (entry.m === 0 && decoded.endpoints.length === 0) {
|
|
29562
|
+
return { entry: null, dropped: decoded.dropped };
|
|
29563
|
+
}
|
|
29564
|
+
return {
|
|
29565
|
+
entry: Object.freeze({
|
|
29566
|
+
mask: entry.m,
|
|
29567
|
+
app: typeof entry.a === "string" ? entry.a : null,
|
|
29568
|
+
endpoints: decoded.endpoints.length === 0 ? null : decoded.endpoints
|
|
29569
|
+
}),
|
|
29570
|
+
dropped: decoded.dropped
|
|
29571
|
+
};
|
|
29572
|
+
}, "parseTokenEntry");
|
|
29573
|
+
var parseWorkerAccessTokensEnv = /* @__PURE__ */ __name((raw2) => {
|
|
29574
|
+
if (raw2.trim() === "") {
|
|
29575
|
+
return { tokens: /* @__PURE__ */ new Map() };
|
|
29576
|
+
}
|
|
29577
|
+
let parsed;
|
|
29578
|
+
try {
|
|
29579
|
+
parsed = JSON.parse(raw2);
|
|
29580
|
+
} catch {
|
|
29581
|
+
return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS is not valid JSON" };
|
|
29352
29582
|
}
|
|
29353
|
-
|
|
29354
|
-
|
|
29355
|
-
var isCountryBlocked = /* @__PURE__ */ __name((country, isEU, rule) => {
|
|
29356
|
-
if (rule === null || rule.list.length === 0) {
|
|
29357
|
-
return false;
|
|
29583
|
+
if (typeof parsed !== "object" || parsed === null || parsed.v !== 2) {
|
|
29584
|
+
return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS has an unsupported version" };
|
|
29358
29585
|
}
|
|
29359
|
-
const
|
|
29360
|
-
|
|
29361
|
-
|
|
29586
|
+
const table = parsed.t;
|
|
29587
|
+
if (typeof table !== "object" || table === null) {
|
|
29588
|
+
return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS is missing its token table" };
|
|
29589
|
+
}
|
|
29590
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
29591
|
+
let rejected = 0;
|
|
29592
|
+
for (const [hash2, value] of Object.entries(table)) {
|
|
29593
|
+
const parsedEntry = parseTokenEntry(value);
|
|
29594
|
+
rejected += parsedEntry.dropped;
|
|
29595
|
+
if (parsedEntry.entry === null) {
|
|
29596
|
+
rejected += 1;
|
|
29597
|
+
continue;
|
|
29598
|
+
}
|
|
29599
|
+
tokens.set(hash2, parsedEntry.entry);
|
|
29600
|
+
}
|
|
29601
|
+
if (rejected > 0) {
|
|
29602
|
+
return { tokens, error: `WORKER_ACCESS_TOKENS dropped ${rejected} invalid entr${rejected === 1 ? "y" : "ies"}` };
|
|
29603
|
+
}
|
|
29604
|
+
return { tokens };
|
|
29605
|
+
}, "parseWorkerAccessTokensEnv");
|
|
29362
29606
|
|
|
29363
|
-
// ../../libs/
|
|
29364
|
-
var
|
|
29365
|
-
|
|
29607
|
+
// ../../libs/worker-access-token/src/hash.ts
|
|
29608
|
+
var hashWorkerAccessToken = /* @__PURE__ */ __name(async (raw2) => {
|
|
29609
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(raw2));
|
|
29610
|
+
let binary = "";
|
|
29611
|
+
for (const byte of new Uint8Array(digest)) {
|
|
29612
|
+
binary += String.fromCharCode(byte);
|
|
29613
|
+
}
|
|
29614
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
29615
|
+
}, "hashWorkerAccessToken");
|
|
29366
29616
|
|
|
29367
|
-
//
|
|
29368
|
-
var
|
|
29369
|
-
var
|
|
29370
|
-
|
|
29371
|
-
|
|
29617
|
+
// src/common/auth.ts
|
|
29618
|
+
var cachedRaw4;
|
|
29619
|
+
var cachedTokens = /* @__PURE__ */ new Map();
|
|
29620
|
+
var tokensFor = /* @__PURE__ */ __name((raw2) => {
|
|
29621
|
+
if (raw2 === cachedRaw4) {
|
|
29622
|
+
return cachedTokens;
|
|
29372
29623
|
}
|
|
29373
|
-
|
|
29374
|
-
|
|
29624
|
+
const parsed = parseWorkerAccessTokensEnv(raw2);
|
|
29625
|
+
if (parsed.error !== void 0) {
|
|
29626
|
+
addSpanEvent("WORKER_ACCESS_TOKENS is misconfigured; rejecting affected tokens until fixed", {
|
|
29627
|
+
level: "error",
|
|
29628
|
+
attrs: { error: String(parsed.error) }
|
|
29629
|
+
});
|
|
29375
29630
|
}
|
|
29376
|
-
|
|
29377
|
-
|
|
29378
|
-
|
|
29379
|
-
|
|
29380
|
-
|
|
29631
|
+
cachedRaw4 = raw2;
|
|
29632
|
+
cachedTokens = parsed.tokens;
|
|
29633
|
+
return cachedTokens;
|
|
29634
|
+
}, "tokensFor");
|
|
29635
|
+
var readBearerToken = /* @__PURE__ */ __name((request) => {
|
|
29636
|
+
const header = request.headers.get("Authorization");
|
|
29637
|
+
if (header === null) {
|
|
29381
29638
|
return null;
|
|
29382
29639
|
}
|
|
29383
|
-
const
|
|
29384
|
-
if (
|
|
29385
|
-
problems.push("has an invalid countries rule");
|
|
29640
|
+
const prefix = "Bearer ";
|
|
29641
|
+
if (!header.startsWith(prefix)) {
|
|
29386
29642
|
return null;
|
|
29387
29643
|
}
|
|
29388
|
-
const
|
|
29389
|
-
|
|
29390
|
-
|
|
29391
|
-
|
|
29392
|
-
|
|
29393
|
-
|
|
29394
|
-
|
|
29644
|
+
const token = header.slice(prefix.length).trim();
|
|
29645
|
+
return token.length > 0 ? token : null;
|
|
29646
|
+
}, "readBearerToken");
|
|
29647
|
+
var workerAccessTokenEntry = /* @__PURE__ */ __name(async (env, token) => {
|
|
29648
|
+
const raw2 = env.WORKER_ACCESS_TOKENS ?? "";
|
|
29649
|
+
return tokensFor(raw2).get(await hashWorkerAccessToken(token));
|
|
29650
|
+
}, "workerAccessTokenEntry");
|
|
29651
|
+
var requireScope = /* @__PURE__ */ __name((bit) => {
|
|
29652
|
+
return async (c, next) => {
|
|
29653
|
+
const token = readBearerToken(c.req.raw);
|
|
29654
|
+
if (token === null) {
|
|
29655
|
+
return c.json({ error: "unauthenticated" }, 401);
|
|
29395
29656
|
}
|
|
29396
|
-
|
|
29397
|
-
|
|
29398
|
-
|
|
29399
|
-
|
|
29400
|
-
|
|
29401
|
-
},
|
|
29402
|
-
|
|
29403
|
-
|
|
29404
|
-
return
|
|
29405
|
-
}
|
|
29406
|
-
let value;
|
|
29407
|
-
try {
|
|
29408
|
-
value = JSON.parse(raw2);
|
|
29409
|
-
} catch {
|
|
29410
|
-
return { config: NO_PROTECTION, error: "BOT_PROTECTION is not valid JSON" };
|
|
29411
|
-
}
|
|
29412
|
-
if (typeof value !== "object" || value === null) {
|
|
29413
|
-
return { config: NO_PROTECTION, error: "BOT_PROTECTION is not an object" };
|
|
29414
|
-
}
|
|
29415
|
-
const record2 = value;
|
|
29416
|
-
const problems = [];
|
|
29417
|
-
const config2 = {
|
|
29418
|
-
minBotScore: normalizeScore(record2.minBotScore, problems),
|
|
29419
|
-
countries: normalizeCountries(record2.countries, problems)
|
|
29657
|
+
const entry = await workerAccessTokenEntry(c.env, token);
|
|
29658
|
+
if (entry === void 0) {
|
|
29659
|
+
return c.json({ error: "unauthenticated" }, 401);
|
|
29660
|
+
}
|
|
29661
|
+
if ((entry.mask & bit) === 0) {
|
|
29662
|
+
return c.json({ error: "forbidden" }, 403);
|
|
29663
|
+
}
|
|
29664
|
+
c.set("actionSource", entry.app === null ? "api" : "app");
|
|
29665
|
+
return next();
|
|
29420
29666
|
};
|
|
29421
|
-
|
|
29422
|
-
|
|
29423
|
-
|
|
29424
|
-
|
|
29425
|
-
|
|
29667
|
+
}, "requireScope");
|
|
29668
|
+
|
|
29669
|
+
// src/helpers/header-json.ts
|
|
29670
|
+
var headerJson = /* @__PURE__ */ __name((value) => {
|
|
29671
|
+
return JSON.stringify(value).replace(/[\u0080-\uffff]/g, (char) => {
|
|
29672
|
+
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
|
29673
|
+
});
|
|
29674
|
+
}, "headerJson");
|
|
29675
|
+
|
|
29676
|
+
// src/apps/stream-deadlines.ts
|
|
29677
|
+
var streamWithDeadlines = /* @__PURE__ */ __name((source, { idleMs, totalMs }) => {
|
|
29678
|
+
if (source === null) {
|
|
29679
|
+
return null;
|
|
29426
29680
|
}
|
|
29427
|
-
|
|
29428
|
-
|
|
29681
|
+
const reader = source.getReader();
|
|
29682
|
+
let target;
|
|
29683
|
+
let totalTimer;
|
|
29684
|
+
let idleTimer = null;
|
|
29685
|
+
let stopped = false;
|
|
29686
|
+
const clearIdleTimer = /* @__PURE__ */ __name(() => {
|
|
29687
|
+
if (idleTimer !== null) {
|
|
29688
|
+
clearTimeout(idleTimer);
|
|
29689
|
+
idleTimer = null;
|
|
29690
|
+
}
|
|
29691
|
+
}, "clearIdleTimer");
|
|
29692
|
+
const clearTimers = /* @__PURE__ */ __name(() => {
|
|
29693
|
+
clearTimeout(totalTimer);
|
|
29694
|
+
clearIdleTimer();
|
|
29695
|
+
}, "clearTimers");
|
|
29696
|
+
const stop = /* @__PURE__ */ __name((message) => {
|
|
29697
|
+
stopped = true;
|
|
29698
|
+
clearTimers();
|
|
29699
|
+
const error61 = new DOMException(message, "TimeoutError");
|
|
29700
|
+
target.error(error61);
|
|
29701
|
+
void reader.cancel(error61).catch(() => void 0);
|
|
29702
|
+
}, "stop");
|
|
29703
|
+
return new ReadableStream({
|
|
29704
|
+
start: /* @__PURE__ */ __name((controller) => {
|
|
29705
|
+
target = controller;
|
|
29706
|
+
totalTimer = setTimeout(() => {
|
|
29707
|
+
stop("The app endpoint stream exceeded its maximum duration.");
|
|
29708
|
+
}, totalMs);
|
|
29709
|
+
}, "start"),
|
|
29710
|
+
pull: /* @__PURE__ */ __name(async (controller) => {
|
|
29711
|
+
idleTimer = setTimeout(() => {
|
|
29712
|
+
stop("The app endpoint stream stopped producing data.");
|
|
29713
|
+
}, idleMs);
|
|
29714
|
+
let result;
|
|
29715
|
+
try {
|
|
29716
|
+
result = await reader.read();
|
|
29717
|
+
} catch (error61) {
|
|
29718
|
+
stopped = true;
|
|
29719
|
+
clearTimers();
|
|
29720
|
+
throw error61;
|
|
29721
|
+
}
|
|
29722
|
+
clearIdleTimer();
|
|
29723
|
+
if (stopped) {
|
|
29724
|
+
return;
|
|
29725
|
+
}
|
|
29726
|
+
if (result.done) {
|
|
29727
|
+
stopped = true;
|
|
29728
|
+
clearTimers();
|
|
29729
|
+
controller.close();
|
|
29730
|
+
return;
|
|
29731
|
+
}
|
|
29732
|
+
controller.enqueue(result.value);
|
|
29733
|
+
}, "pull"),
|
|
29734
|
+
cancel: /* @__PURE__ */ __name(async (reason) => {
|
|
29735
|
+
stopped = true;
|
|
29736
|
+
clearTimers();
|
|
29737
|
+
await reader.cancel(reason);
|
|
29738
|
+
}, "cancel")
|
|
29739
|
+
});
|
|
29740
|
+
}, "streamWithDeadlines");
|
|
29429
29741
|
|
|
29430
29742
|
// src/helpers/bot-check.ts
|
|
29431
29743
|
var botRejected = /* @__PURE__ */ __name((env, geo) => {
|
|
@@ -29524,7 +29836,8 @@ var withCors = /* @__PURE__ */ __name((response, request, method) => {
|
|
|
29524
29836
|
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
29525
29837
|
}, "withCors");
|
|
29526
29838
|
var authorize = /* @__PURE__ */ __name(async (c, { slug, app: app2 }) => {
|
|
29527
|
-
|
|
29839
|
+
const geo = geoFromRequest(c.req.raw);
|
|
29840
|
+
if (await botRejected(c.env, geo)) {
|
|
29528
29841
|
return refuse(403, "traffic_rejected");
|
|
29529
29842
|
}
|
|
29530
29843
|
const anonId = getCookie(c, COOKIE_NAME2);
|
|
@@ -29533,8 +29846,8 @@ var authorize = /* @__PURE__ */ __name(async (c, { slug, app: app2 }) => {
|
|
|
29533
29846
|
return refuse(401, "visitor_required");
|
|
29534
29847
|
}
|
|
29535
29848
|
const installId = c.req.header(VISITOR_INSTALL_HEADER);
|
|
29536
|
-
const install = app2.
|
|
29537
|
-
return candidate.
|
|
29849
|
+
const install = geoRunnableInstalls(slug, app2, geoLocation(geo)).runs.find((candidate) => {
|
|
29850
|
+
return candidate.id === installId;
|
|
29538
29851
|
});
|
|
29539
29852
|
if (install === void 0) {
|
|
29540
29853
|
return refuse(403, "forbidden");
|
|
@@ -30030,6 +30343,19 @@ var preflight = /* @__PURE__ */ __name((origin) => {
|
|
|
30030
30343
|
var uiInstall = /* @__PURE__ */ __name((install) => {
|
|
30031
30344
|
return { id: install.id, ord: install.ord, label: install.label, enabled: install.enabled ?? install.active };
|
|
30032
30345
|
}, "uiInstall");
|
|
30346
|
+
var otherAppInstalls = /* @__PURE__ */ __name((ownSlug) => {
|
|
30347
|
+
const installs = Object.entries(siteRegistry().registry.apps).flatMap(([appSlug, entry]) => {
|
|
30348
|
+
if (appSlug === ownSlug) {
|
|
30349
|
+
return [];
|
|
30350
|
+
}
|
|
30351
|
+
return entry.installs.filter((install) => install.enabled ?? install.active).map((install) => ({ id: install.id, appSlug, appName: entry.name, label: install.label, ord: install.ord }));
|
|
30352
|
+
});
|
|
30353
|
+
const listed = boundSiteInstalls(installs);
|
|
30354
|
+
if (listed.length < installs.length) {
|
|
30355
|
+
addSpanEvent("app ui site installs trimmed", { level: "warn", attrs: { app: ownSlug, listed: listed.length, total: installs.length } });
|
|
30356
|
+
}
|
|
30357
|
+
return listed;
|
|
30358
|
+
}, "otherAppInstalls");
|
|
30033
30359
|
var runForwardToApp2 = /* @__PURE__ */ __name(async (c, input2) => {
|
|
30034
30360
|
const { slug, origin, app: app2, install, viewer, writeTicket, body } = input2;
|
|
30035
30361
|
const context = invocationContext(c.env);
|
|
@@ -30053,9 +30379,10 @@ var runForwardToApp2 = /* @__PURE__ */ __name(async (c, input2) => {
|
|
|
30053
30379
|
viewer,
|
|
30054
30380
|
install: uiInstall(install),
|
|
30055
30381
|
installs: app2.installs.map(uiInstall),
|
|
30382
|
+
...app2.data.includes("siteInstalls") ? { siteInstalls: otherAppInstalls(slug) } : {},
|
|
30056
30383
|
...writeTicket === void 0 ? {} : { writeTicket }
|
|
30057
30384
|
};
|
|
30058
|
-
headers.set(UI_CONTEXT_HEADER,
|
|
30385
|
+
headers.set(UI_CONTEXT_HEADER, encodeUiContext(uiContext));
|
|
30059
30386
|
const trace = currentTraceContext();
|
|
30060
30387
|
if (trace !== void 0) {
|
|
30061
30388
|
headers.set(TRACE_HEADER, serializeTraceContext(trace));
|
|
@@ -30897,14 +31224,14 @@ var open2 = /* @__PURE__ */ __name(async (c) => {
|
|
|
30897
31224
|
|
|
30898
31225
|
// browser-artifact/meta.json
|
|
30899
31226
|
var meta_default = {
|
|
30900
|
-
version: "0.1.
|
|
30901
|
-
gitSha: "
|
|
30902
|
-
builtAt: "2026-
|
|
30903
|
-
hash: "
|
|
31227
|
+
version: "0.1.13",
|
|
31228
|
+
gitSha: "21a2eff7e038bc98c8c087656c9793c8f3a82434",
|
|
31229
|
+
builtAt: "2026-10-01T08:32:14.748Z",
|
|
31230
|
+
hash: "3d5d7fe29d7e160413d4cb0e08de724613f89eb314feed6d5d1ec2e7473f7afd"
|
|
30904
31231
|
};
|
|
30905
31232
|
|
|
30906
31233
|
// src/paths/q-js.ts
|
|
30907
|
-
import qScript from "./
|
|
31234
|
+
import qScript from "./61afaba4cf160e30ef4b71a7ce30a398f71f13e8-q.js.txt";
|
|
30908
31235
|
|
|
30909
31236
|
// src/apps-q.ts
|
|
30910
31237
|
import appsQScript from "./apps-q.json";
|
|
@@ -31391,7 +31718,11 @@ var trackBody2 = external_exports.object({
|
|
|
31391
31718
|
description: "Free-form properties describing the event; omit it when the event name alone says everything.",
|
|
31392
31719
|
example: { orderId: "9241", value: 129.9, currency: "USD" }
|
|
31393
31720
|
}),
|
|
31394
|
-
context: serverActionContextField
|
|
31721
|
+
context: serverActionContextField,
|
|
31722
|
+
installs: installRuleSchema.optional().openapi({
|
|
31723
|
+
description: "Which of the site\u2019s app installs receive the event: `except` delivers to every install but the listed ids, `only` to the listed ids alone. An unknown id is ignored, and an empty `only` list reaches no install while the event is still stored when it qualifies. Omit it to deliver to every install.",
|
|
31724
|
+
example: { mode: "except", installIds: ["3f2a9c1e-8b4d-4e2a-9f6c-1d7b5e0a4c83"] }
|
|
31725
|
+
})
|
|
31395
31726
|
}).strict().openapi("TrackRequest", { description: "An event the user performed, with optional free-form properties." });
|
|
31396
31727
|
var definition10 = createRoute({
|
|
31397
31728
|
method: "post",
|
|
@@ -31421,12 +31752,18 @@ var definition10 = createRoute({
|
|
|
31421
31752
|
}
|
|
31422
31753
|
});
|
|
31423
31754
|
var track = createApiRouter().openapi(definition10, async (c) => {
|
|
31424
|
-
const { identifier, event, eventId, properties: properties2, context } = c.req.valid("json");
|
|
31755
|
+
const { identifier, event, eventId, properties: properties2, context, installs } = c.req.valid("json");
|
|
31425
31756
|
try {
|
|
31426
31757
|
const id = await submitAction(
|
|
31427
31758
|
c.env,
|
|
31428
31759
|
identifier,
|
|
31429
|
-
{
|
|
31760
|
+
{
|
|
31761
|
+
type: "track",
|
|
31762
|
+
source: c.get("actionSource"),
|
|
31763
|
+
payload: { event, eventId, properties: properties2 ?? {} },
|
|
31764
|
+
context,
|
|
31765
|
+
...installs === void 0 ? {} : { installs }
|
|
31766
|
+
},
|
|
31430
31767
|
c.req.raw
|
|
31431
31768
|
);
|
|
31432
31769
|
return respond(c, acceptedResponse, { ok: true, id });
|
|
@@ -31464,34 +31801,48 @@ var gatePasses = /* @__PURE__ */ __name((entry, trigger, consent2, enforced) =>
|
|
|
31464
31801
|
return consentAllows(consent2, entry.slug, entry.app.consent.category, enforced);
|
|
31465
31802
|
}, "gatePasses");
|
|
31466
31803
|
var eligibleApps = /* @__PURE__ */ __name((registry3, trigger, consent2, enforced) => {
|
|
31804
|
+
const location = visitorLocation(trigger);
|
|
31805
|
+
const rule = trigger.kind === "event" && trigger.action.type === "track" ? trigger.action.installs : void 0;
|
|
31806
|
+
const listed = new Set(rule?.installIds);
|
|
31807
|
+
const keepListed = rule?.mode === "only";
|
|
31467
31808
|
const targets = [];
|
|
31809
|
+
const geoSkipped = [];
|
|
31468
31810
|
for (const slug of registry3.order) {
|
|
31469
31811
|
const app2 = registry3.apps[slug];
|
|
31470
31812
|
if (app2 === void 0 || !wantsTrigger(app2, trigger) || !gatePasses({ slug, app: app2 }, trigger, consent2, enforced)) {
|
|
31471
31813
|
continue;
|
|
31472
31814
|
}
|
|
31473
|
-
const
|
|
31815
|
+
const { runs, skipped } = geoRunnableInstalls(slug, app2, location);
|
|
31816
|
+
geoSkipped.push(...skipped);
|
|
31817
|
+
const installs = runs.filter((install) => listed.has(install.id) === keepListed).map(({ id, ord, label }) => ({ id, ord, label }));
|
|
31474
31818
|
if (installs.length === 0) {
|
|
31475
31819
|
continue;
|
|
31476
31820
|
}
|
|
31477
31821
|
targets.push({ slug, app: app2, installs });
|
|
31478
31822
|
}
|
|
31479
|
-
return targets;
|
|
31823
|
+
return { targets, geoSkipped };
|
|
31480
31824
|
}, "eligibleApps");
|
|
31481
31825
|
|
|
31482
31826
|
// src/apps/fan-out.ts
|
|
31483
|
-
var MAX_APPS_PER_DOMAIN = 50;
|
|
31484
31827
|
var fanOutToApps = /* @__PURE__ */ __name(async (env, { trigger, snapshot, trace }, { deliverEffects }) => {
|
|
31485
31828
|
const { registry: registry3, error: error61 } = siteRegistry();
|
|
31486
|
-
const targets = eligibleApps(registry3, trigger, snapshot.consent, consentEnforced(env));
|
|
31487
|
-
if (error61 === void 0 && targets.length === 0) {
|
|
31829
|
+
const { targets, geoSkipped } = eligibleApps(registry3, trigger, snapshot.consent, consentEnforced(env));
|
|
31830
|
+
if (error61 === void 0 && targets.length === 0 && geoSkipped.length === 0) {
|
|
31488
31831
|
return;
|
|
31489
31832
|
}
|
|
31490
|
-
|
|
31833
|
+
const attrs = {
|
|
31834
|
+
"fan_out.kind": trigger.kind,
|
|
31835
|
+
"fan_out.apps": targets.length,
|
|
31836
|
+
...geoSkipped.length === 0 ? {} : { "fan_out.geo_skipped": geoSkipped.join(",") }
|
|
31837
|
+
};
|
|
31838
|
+
await runInvocation({ name: "app.fan_out", trace, attrs }, async () => {
|
|
31491
31839
|
if (error61 !== void 0) {
|
|
31492
31840
|
addSpanEvent("apps registry is malformed; apps are off", { level: "error", attrs: { error: error61 } });
|
|
31493
31841
|
return;
|
|
31494
31842
|
}
|
|
31843
|
+
if (targets.length === 0) {
|
|
31844
|
+
return;
|
|
31845
|
+
}
|
|
31495
31846
|
const context = invocationContext(env);
|
|
31496
31847
|
if (context === void 0) {
|
|
31497
31848
|
addSpanEvent("apps skipped: APPS_KEY_MASTER or QINTO_DOMAIN_ID is missing", { level: "warn" });
|
|
@@ -31557,20 +31908,22 @@ var TransformChainError = class extends Error {
|
|
|
31557
31908
|
this.name = "TransformChainError";
|
|
31558
31909
|
}
|
|
31559
31910
|
};
|
|
31560
|
-
var chainEntries = /* @__PURE__ */ __name((registry3,
|
|
31911
|
+
var chainEntries = /* @__PURE__ */ __name((registry3, action) => {
|
|
31912
|
+
const location = visitorLocation({ kind: "event", action });
|
|
31561
31913
|
const entries = [];
|
|
31914
|
+
const geoSkipped = [];
|
|
31562
31915
|
for (const slug of registry3.order) {
|
|
31563
31916
|
const app2 = registry3.apps[slug];
|
|
31564
|
-
if (app2 === void 0 || !app2.hooks.transform.includes(type)) {
|
|
31917
|
+
if (app2 === void 0 || !app2.hooks.transform.includes(action.type)) {
|
|
31565
31918
|
continue;
|
|
31566
31919
|
}
|
|
31567
|
-
|
|
31568
|
-
|
|
31569
|
-
|
|
31570
|
-
}
|
|
31920
|
+
const { runs, skipped } = geoRunnableInstalls(slug, app2, location);
|
|
31921
|
+
geoSkipped.push(...skipped);
|
|
31922
|
+
for (const install of runs) {
|
|
31923
|
+
entries.push({ slug, app: app2, install: { id: install.id, ord: install.ord, label: install.label } });
|
|
31571
31924
|
}
|
|
31572
31925
|
}
|
|
31573
|
-
return entries;
|
|
31926
|
+
return { entries, geoSkipped };
|
|
31574
31927
|
}, "chainEntries");
|
|
31575
31928
|
var trackPayloadSchema = external_exports.object({
|
|
31576
31929
|
event: external_exports.string().min(1),
|
|
@@ -31595,6 +31948,16 @@ var withReplacedPayload = /* @__PURE__ */ __name((entry, action, payload) => {
|
|
|
31595
31948
|
}
|
|
31596
31949
|
return { ...action, payload: parsed.data };
|
|
31597
31950
|
}, "withReplacedPayload");
|
|
31951
|
+
var applyTransformResult = /* @__PURE__ */ __name((entry, action, { payload, installs }) => {
|
|
31952
|
+
const replaced = payload === void 0 ? action : withReplacedPayload(entry, action, payload);
|
|
31953
|
+
if (installs === void 0) {
|
|
31954
|
+
return replaced;
|
|
31955
|
+
}
|
|
31956
|
+
if (replaced.type !== "track") {
|
|
31957
|
+
throw entryFailure(entry, "an identify transform cannot set an install rule");
|
|
31958
|
+
}
|
|
31959
|
+
return { ...replaced, installs: replaced.installs === void 0 ? installs : intersectInstallRules(replaced.installs, installs) };
|
|
31960
|
+
}, "applyTransformResult");
|
|
31598
31961
|
var postTransform = /* @__PURE__ */ __name(async (env, entry, post, dataPoints) => {
|
|
31599
31962
|
let response;
|
|
31600
31963
|
try {
|
|
@@ -31692,7 +32055,8 @@ var buildEffectsRelease = /* @__PURE__ */ __name(({ effects, action, deliveries
|
|
|
31692
32055
|
...deliveries,
|
|
31693
32056
|
geo: action.geo,
|
|
31694
32057
|
host: action.host,
|
|
31695
|
-
context: action.context
|
|
32058
|
+
context: action.context,
|
|
32059
|
+
...startedOnSocket({ kind: "event", action }) ? { fromSocket: true } : {}
|
|
31696
32060
|
});
|
|
31697
32061
|
}
|
|
31698
32062
|
};
|
|
@@ -31702,15 +32066,22 @@ var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, d
|
|
|
31702
32066
|
if (error61 !== void 0) {
|
|
31703
32067
|
throw new TransformChainError(`the apps registry is malformed: ${error61}`);
|
|
31704
32068
|
}
|
|
31705
|
-
const entries = chainEntries(registry3, action
|
|
32069
|
+
const { entries, geoSkipped } = chainEntries(registry3, action);
|
|
32070
|
+
const attrs = {
|
|
32071
|
+
"transform.entries": entries.length,
|
|
32072
|
+
...geoSkipped.length === 0 ? {} : { "transform.geo_skipped": geoSkipped.join(",") }
|
|
32073
|
+
};
|
|
31706
32074
|
if (entries.length === 0) {
|
|
32075
|
+
if (geoSkipped.length > 0) {
|
|
32076
|
+
await traced("apps.transform_chain", { attrs }, () => Promise.resolve());
|
|
32077
|
+
}
|
|
31707
32078
|
return { dropped: false, action, effects: [] };
|
|
31708
32079
|
}
|
|
31709
32080
|
const context = invocationContext(env);
|
|
31710
32081
|
if (context === void 0) {
|
|
31711
32082
|
throw new TransformChainError("APPS_KEY_MASTER or QINTO_DOMAIN_ID is missing");
|
|
31712
32083
|
}
|
|
31713
|
-
return traced("apps.transform_chain", { attrs
|
|
32084
|
+
return traced("apps.transform_chain", { attrs }, async () => {
|
|
31714
32085
|
const collected = [];
|
|
31715
32086
|
let budget = { totalDeadline: Date.now() + TRANSFORM_CHAIN_BUDGET_MS, appDeadline: 0 };
|
|
31716
32087
|
let current = action;
|
|
@@ -31733,7 +32104,7 @@ var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, d
|
|
|
31733
32104
|
return { dropped: true, effects: collected };
|
|
31734
32105
|
}
|
|
31735
32106
|
if (verdict.result !== "pass") {
|
|
31736
|
-
current =
|
|
32107
|
+
current = applyTransformResult(entry, current, verdict.result);
|
|
31737
32108
|
}
|
|
31738
32109
|
}
|
|
31739
32110
|
return { dropped: false, action: current, effects: collected };
|
|
@@ -31955,14 +32326,14 @@ var allCaptures = /* @__PURE__ */ __name((sql, now, cache) => {
|
|
|
31955
32326
|
}, "allCaptures");
|
|
31956
32327
|
|
|
31957
32328
|
// src/apps/capture.ts
|
|
31958
|
-
var appWriteAllowed = /* @__PURE__ */ __name((registry3, consent2,
|
|
32329
|
+
var appWriteAllowed = /* @__PURE__ */ __name((registry3, slug, { consent: consent2, enforced, location }) => {
|
|
31959
32330
|
const app2 = registry3.apps[slug];
|
|
31960
32331
|
if (app2 === void 0) {
|
|
31961
32332
|
addSpanEvent("capture dropped: the app is not in the registry", { level: "warn", attrs: { app: slug } });
|
|
31962
32333
|
return false;
|
|
31963
32334
|
}
|
|
31964
|
-
if (
|
|
31965
|
-
addSpanEvent("capture dropped: no
|
|
32335
|
+
if (geoRunnableInstalls(slug, app2, location).runs.length === 0) {
|
|
32336
|
+
addSpanEvent("capture dropped: no install runs for this visitor", { level: "warn", attrs: { app: slug } });
|
|
31966
32337
|
return false;
|
|
31967
32338
|
}
|
|
31968
32339
|
if (!consentAllows(consent2, slug, app2.consent.category, enforced)) {
|
|
@@ -31983,11 +32354,11 @@ var declaration = /* @__PURE__ */ __name((app2, key) => {
|
|
|
31983
32354
|
ttlMs: declared.expiresInSeconds === void 0 ? null : declared.expiresInSeconds * 1e3
|
|
31984
32355
|
};
|
|
31985
32356
|
}, "declaration");
|
|
31986
|
-
var acceptedCaptures = /* @__PURE__ */ __name((registry3,
|
|
32357
|
+
var acceptedCaptures = /* @__PURE__ */ __name((registry3, apps, gate) => {
|
|
31987
32358
|
const accepted = [];
|
|
31988
32359
|
for (const [slug, values] of Object.entries(apps)) {
|
|
31989
32360
|
const app2 = registry3.apps[slug];
|
|
31990
|
-
if (app2 === void 0 || !appWriteAllowed(registry3,
|
|
32361
|
+
if (app2 === void 0 || !appWriteAllowed(registry3, slug, gate)) {
|
|
31991
32362
|
continue;
|
|
31992
32363
|
}
|
|
31993
32364
|
for (const [key, value] of Object.entries(values)) {
|
|
@@ -32789,14 +33160,29 @@ var appPushFrame = /* @__PURE__ */ __name(({ push, context, consent: consent2 })
|
|
|
32789
33160
|
topic,
|
|
32790
33161
|
data: data ?? null
|
|
32791
33162
|
};
|
|
32792
|
-
const serialized = JSON.stringify(frame);
|
|
32793
33163
|
const overhead = scope === "site" ? PUSH_SITE_FRAME_OVERHEAD_BYTES : PUSH_FRAME_OVERHEAD_BYTES;
|
|
32794
|
-
if (new TextEncoder().encode(
|
|
33164
|
+
if (new TextEncoder().encode(JSON.stringify(frame)).length > MAX_PUSH_BYTES + overhead) {
|
|
32795
33165
|
addSpanEvent("app push dropped over the size cap", { level: "warn", attrs: { app: slug, topic } });
|
|
32796
33166
|
return null;
|
|
32797
33167
|
}
|
|
32798
|
-
return
|
|
33168
|
+
return frame;
|
|
32799
33169
|
}, "appPushFrame");
|
|
33170
|
+
var connectionPushFrame = /* @__PURE__ */ __name((frame, registry3, location) => {
|
|
33171
|
+
const runningApps = /* @__PURE__ */ new Set();
|
|
33172
|
+
const runningInstalls = /* @__PURE__ */ new Set();
|
|
33173
|
+
for (const [slug, app2] of Object.entries(registry3.apps)) {
|
|
33174
|
+
for (const install of geoRunnableInstalls(slug, app2, location).runs) {
|
|
33175
|
+
runningApps.add(slug);
|
|
33176
|
+
runningInstalls.add(install.id);
|
|
33177
|
+
}
|
|
33178
|
+
}
|
|
33179
|
+
if (frame.to !== void 0) {
|
|
33180
|
+
const to = frame.to.filter((slug) => runningApps.has(slug));
|
|
33181
|
+
return to.length === 0 ? null : JSON.stringify({ ...frame, to });
|
|
33182
|
+
}
|
|
33183
|
+
const reaches = frame.install === void 0 ? runningApps.has(frame.app) : runningInstalls.has(frame.install);
|
|
33184
|
+
return reaches ? JSON.stringify(frame) : null;
|
|
33185
|
+
}, "connectionPushFrame");
|
|
32800
33186
|
var sharedContextFrame = /* @__PURE__ */ __name((context, consent2, stored) => {
|
|
32801
33187
|
const readable = readableSharedContext(context.registry, consent2, context.consentEnforced, stored);
|
|
32802
33188
|
return JSON.stringify({ t: "sharedContext", context: sharedContextWire(readable), ...consent2 });
|
|
@@ -32967,7 +33353,8 @@ var trackFrame = external_exports.object({
|
|
|
32967
33353
|
eventId: external_exports.string().min(1),
|
|
32968
33354
|
properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
32969
33355
|
context: browserContextFrame,
|
|
32970
|
-
appResults: browserResults.optional()
|
|
33356
|
+
appResults: browserResults.optional(),
|
|
33357
|
+
installs: installRuleSchema.optional()
|
|
32971
33358
|
}).strict();
|
|
32972
33359
|
var aliasFrame = external_exports.object({
|
|
32973
33360
|
t: external_exports.literal("alias"),
|
|
@@ -32994,6 +33381,10 @@ var getSessionFrame = external_exports.object({
|
|
|
32994
33381
|
t: external_exports.literal("getSession"),
|
|
32995
33382
|
ref: external_exports.string().min(1).optional()
|
|
32996
33383
|
}).strict();
|
|
33384
|
+
var getGeoInstallsFrame = external_exports.object({
|
|
33385
|
+
t: external_exports.literal("getGeoInstalls"),
|
|
33386
|
+
ref: external_exports.string().min(1).optional()
|
|
33387
|
+
}).strict();
|
|
32997
33388
|
var clientFrame = external_exports.discriminatedUnion("t", [
|
|
32998
33389
|
identifyFrame,
|
|
32999
33390
|
trackFrame,
|
|
@@ -33001,7 +33392,8 @@ var clientFrame = external_exports.discriminatedUnion("t", [
|
|
|
33001
33392
|
consentFrame,
|
|
33002
33393
|
captureFrame,
|
|
33003
33394
|
getConsentFrame,
|
|
33004
|
-
getSessionFrame
|
|
33395
|
+
getSessionFrame,
|
|
33396
|
+
getGeoInstallsFrame
|
|
33005
33397
|
]);
|
|
33006
33398
|
var pluckRef = /* @__PURE__ */ __name((parsed) => {
|
|
33007
33399
|
if (typeof parsed !== "object" || parsed === null) {
|
|
@@ -33087,7 +33479,8 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
|
|
|
33087
33479
|
context: browserActionContext(frame.context, host, session2),
|
|
33088
33480
|
browserResults: frame.appResults,
|
|
33089
33481
|
geo,
|
|
33090
|
-
host
|
|
33482
|
+
host,
|
|
33483
|
+
...frame.installs === void 0 ? {} : { installs: frame.installs }
|
|
33091
33484
|
});
|
|
33092
33485
|
}
|
|
33093
33486
|
case "alias": {
|
|
@@ -33223,12 +33616,28 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33223
33616
|
return null;
|
|
33224
33617
|
}
|
|
33225
33618
|
if (frame.t === "capture") {
|
|
33226
|
-
const { accepted } = await this.capture({ apps: frame.apps }, site);
|
|
33619
|
+
const { accepted } = await this.capture({ apps: frame.apps, location: socketLocation(attachment.cf) }, site);
|
|
33227
33620
|
this.respond(ws, { t: "captureAck", ref, anonId: userId, accepted });
|
|
33228
33621
|
return null;
|
|
33229
33622
|
}
|
|
33623
|
+
if (frame.t === "getGeoInstalls") {
|
|
33624
|
+
await this.answerGeoInstalls(ws, ref, attachment, site);
|
|
33625
|
+
return null;
|
|
33626
|
+
}
|
|
33230
33627
|
return frame;
|
|
33231
33628
|
}
|
|
33629
|
+
// The browser holds the page's first events until this answers, so a failure answers at once.
|
|
33630
|
+
async answerGeoInstalls(ws, ref, attachment, site) {
|
|
33631
|
+
let registry3;
|
|
33632
|
+
try {
|
|
33633
|
+
registry3 = (await this.sites.resolve(site, Date.now())).registry;
|
|
33634
|
+
} catch (error61) {
|
|
33635
|
+
recordError(error61, { "site.id": site.domainId });
|
|
33636
|
+
this.respond(ws, { t: "error", ref, message: "geo filter read failed" });
|
|
33637
|
+
return;
|
|
33638
|
+
}
|
|
33639
|
+
this.respond(ws, { t: "geoInstalls", ref, installs: geoAllowedInstalls(registry3, attachment.cf) });
|
|
33640
|
+
}
|
|
33232
33641
|
admitFrame(ws) {
|
|
33233
33642
|
let bucket = this.frameBuckets.get(ws);
|
|
33234
33643
|
if (bucket === void 0) {
|
|
@@ -33353,7 +33762,7 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33353
33762
|
addSpanEvent("capture dropped: no visitor state", { level: "warn" });
|
|
33354
33763
|
return { accepted: {} };
|
|
33355
33764
|
}
|
|
33356
|
-
return this.runCaptures(input2.apps, site);
|
|
33765
|
+
return this.runCaptures(input2.apps, site, input2.location);
|
|
33357
33766
|
});
|
|
33358
33767
|
});
|
|
33359
33768
|
}
|
|
@@ -33367,7 +33776,7 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33367
33776
|
this.cleanupAlarm = await seedCleanupAlarm(this.ctx.storage, now, lastActivityAt(this.kv) ?? now, this.cleanupAlarm);
|
|
33368
33777
|
seedMeta(this.kv, input2.anonId, now);
|
|
33369
33778
|
touchActivity(this.kv, now);
|
|
33370
|
-
return this.runCaptures(input2.apps, site);
|
|
33779
|
+
return this.runCaptures(input2.apps, site, null);
|
|
33371
33780
|
});
|
|
33372
33781
|
});
|
|
33373
33782
|
}
|
|
@@ -33381,7 +33790,11 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33381
33790
|
return;
|
|
33382
33791
|
}
|
|
33383
33792
|
const context = await this.sites.resolve(site, Date.now());
|
|
33384
|
-
if (!appWriteAllowed(context.registry,
|
|
33793
|
+
if (!appWriteAllowed(context.registry, input2.slug, {
|
|
33794
|
+
consent: consentState2(this.kv),
|
|
33795
|
+
enforced: context.consentEnforced,
|
|
33796
|
+
location: null
|
|
33797
|
+
})) {
|
|
33385
33798
|
return;
|
|
33386
33799
|
}
|
|
33387
33800
|
this.storeCaptures(
|
|
@@ -33392,9 +33805,9 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33392
33805
|
});
|
|
33393
33806
|
});
|
|
33394
33807
|
}
|
|
33395
|
-
async runCaptures(apps, site) {
|
|
33808
|
+
async runCaptures(apps, site, location) {
|
|
33396
33809
|
const context = await this.sites.resolve(site, Date.now());
|
|
33397
|
-
const writes = acceptedCaptures(context.registry, consentState2(this.kv),
|
|
33810
|
+
const writes = acceptedCaptures(context.registry, apps, { consent: consentState2(this.kv), enforced: context.consentEnforced, location });
|
|
33398
33811
|
return { accepted: this.storeCaptures(writes) };
|
|
33399
33812
|
}
|
|
33400
33813
|
storeCaptures(writes) {
|
|
@@ -33423,7 +33836,7 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33423
33836
|
}
|
|
33424
33837
|
const context = await this.sites.resolve(site, Date.now());
|
|
33425
33838
|
const { registry: registry3 } = context;
|
|
33426
|
-
if (!appWriteAllowed(registry3, consentState2(this.kv),
|
|
33839
|
+
if (!appWriteAllowed(registry3, input2.slug, { consent: consentState2(this.kv), enforced: context.consentEnforced, location: null })) {
|
|
33427
33840
|
return [];
|
|
33428
33841
|
}
|
|
33429
33842
|
const granted = registry3.apps[input2.slug]?.data.includes(SHARED_CONTEXT_CAPABILITY) === true;
|
|
@@ -33557,12 +33970,16 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33557
33970
|
// Only the sockets opened on the push's own site: a linked sibling's browser holds no install of
|
|
33558
33971
|
// the app that produced it.
|
|
33559
33972
|
deliverAppPush(push, context) {
|
|
33560
|
-
const
|
|
33561
|
-
if (
|
|
33973
|
+
const frame = appPushFrame({ push, context, consent: consentState2(this.kv) });
|
|
33974
|
+
if (frame === null) {
|
|
33562
33975
|
return;
|
|
33563
33976
|
}
|
|
33564
33977
|
for (const socket of this.ctx.getWebSockets(context.domainId)) {
|
|
33565
|
-
|
|
33978
|
+
const { cf } = socket.deserializeAttachment();
|
|
33979
|
+
const serialized = connectionPushFrame(frame, context.registry, socketLocation(cf));
|
|
33980
|
+
if (serialized !== null) {
|
|
33981
|
+
sendFrame(socket, serialized);
|
|
33982
|
+
}
|
|
33566
33983
|
}
|
|
33567
33984
|
}
|
|
33568
33985
|
// A getConsent can arrive before the session is seeded, so the visitor is named only when there
|
|
@@ -33636,16 +34053,9 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33636
34053
|
);
|
|
33637
34054
|
seedMeta(this.kv, current.userId, current.receivedAt);
|
|
33638
34055
|
touchActivity(this.kv, current.receivedAt);
|
|
33639
|
-
|
|
33640
|
-
if (
|
|
33641
|
-
|
|
33642
|
-
this.sql,
|
|
33643
|
-
{ ...current.identifier, source: current.source, firstSeenAt: current.receivedAt },
|
|
33644
|
-
this.identifiersMirror
|
|
33645
|
-
);
|
|
33646
|
-
if (novelIdentifier && linkedGlobalId(this.kv) !== null) {
|
|
33647
|
-
recordSyncDebt(this.kv, "link");
|
|
33648
|
-
}
|
|
34056
|
+
const novelIdentifier = this.recordIdentifiers(current);
|
|
34057
|
+
if (novelIdentifier && linkedGlobalId(this.kv) !== null) {
|
|
34058
|
+
recordSyncDebt(this.kv, "link");
|
|
33649
34059
|
}
|
|
33650
34060
|
try {
|
|
33651
34061
|
if (current.type === "alias") {
|
|
@@ -33659,7 +34069,9 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33659
34069
|
} else {
|
|
33660
34070
|
await this.ingestConsent(current);
|
|
33661
34071
|
}
|
|
33662
|
-
|
|
34072
|
+
if (current.type !== "identify" || current.graphOnly !== true) {
|
|
34073
|
+
this.appFanOut({ kind: "event", action: current }, current.userId, context);
|
|
34074
|
+
}
|
|
33663
34075
|
this.releaseTransformEmissions(releaseEmissions);
|
|
33664
34076
|
} finally {
|
|
33665
34077
|
if (novelIdentifier) {
|
|
@@ -33667,6 +34079,17 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33667
34079
|
}
|
|
33668
34080
|
}
|
|
33669
34081
|
}
|
|
34082
|
+
recordIdentifiers(action) {
|
|
34083
|
+
const identifiers = [
|
|
34084
|
+
...action.identifier ? [action.identifier] : [],
|
|
34085
|
+
...action.type === "identify" ? action.identifiers ?? [] : []
|
|
34086
|
+
];
|
|
34087
|
+
let novel = false;
|
|
34088
|
+
for (const identifier of identifiers) {
|
|
34089
|
+
novel = upsertIdentifier(this.sql, { ...identifier, source: action.source, firstSeenAt: action.receivedAt }, this.identifiersMirror) || novel;
|
|
34090
|
+
}
|
|
34091
|
+
return novel;
|
|
34092
|
+
}
|
|
33670
34093
|
// null means a transform dropped the action: the turn succeeds with nothing persisted (§5.4).
|
|
33671
34094
|
// The chain runs on the originating site's worker, against its apps and key master; only the
|
|
33672
34095
|
// collected effects come back, and they are released here, past the turn outcome, so a delivery
|
|
@@ -33715,7 +34138,8 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33715
34138
|
}
|
|
33716
34139
|
appFanOut(trigger, anonId, context) {
|
|
33717
34140
|
const { registry: registry3, registryError, consentEnforced: consentEnforced2 } = context;
|
|
33718
|
-
|
|
34141
|
+
const { targets, geoSkipped } = eligibleApps(registry3, trigger, consentState2(this.kv), consentEnforced2);
|
|
34142
|
+
if (registryError === void 0 && targets.length === 0 && geoSkipped.length === 0) {
|
|
33719
34143
|
return;
|
|
33720
34144
|
}
|
|
33721
34145
|
const job = { trigger, snapshot: this.appsSnapshot(anonId, context), trace: currentTraceContext() };
|
|
@@ -33853,7 +34277,7 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33853
34277
|
}
|
|
33854
34278
|
async ingestIdentify(action) {
|
|
33855
34279
|
const classified = await traced("pii.classify", {}, () => {
|
|
33856
|
-
const result = classifyTraits(action.payload.traits, action.receivedAt);
|
|
34280
|
+
const result = classifyTraits(action.payload.traits, action.observedAt ?? action.receivedAt);
|
|
33857
34281
|
for (const key of result.dropped) {
|
|
33858
34282
|
addSpanEvent("invalid pii trait ignored", { level: "warn", attrs: { "action.id": action.actionId, key } });
|
|
33859
34283
|
}
|