@blotout/qinto-cli 0.1.24 → 0.1.25

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.
@@ -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 = 8;
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 = 150 * 1024;
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"];
@@ -26481,7 +26481,9 @@ var installSchema4 = external_exports.object({
26481
26481
  active: external_exports.boolean(),
26482
26482
  // Absent from a registry deployed before the ui data plane moved off `active`; falling back to it
26483
26483
  // keeps that site's panels reachable exactly as they were until it redeploys.
26484
- enabled: external_exports.boolean().optional()
26484
+ enabled: external_exports.boolean().optional(),
26485
+ // Optional by design: present only on an install that has a geo filter.
26486
+ geoFilter: external_exports.object({ include: external_exports.array(external_exports.string()), exclude: external_exports.array(external_exports.string()) }).optional()
26485
26487
  });
26486
26488
  var appSchema = external_exports.object({
26487
26489
  binding: external_exports.string().min(1),
@@ -27661,1434 +27663,6 @@ var readableContext = /* @__PURE__ */ __name((own2, shared) => {
27661
27663
  return { private: own2 === void 0 ? null : wireEntry(own2), shared: sharedContextWire(shared) };
27662
27664
  }, "readableContext");
27663
27665
 
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
27666
  // ../../libs/bot-protection/src/countries.ts
29093
27667
  var EU_TOKEN = "EU";
29094
27668
  var COUNTRY_CODES = [
@@ -29350,82 +27924,1648 @@ var matchesRule = /* @__PURE__ */ __name((country, isEU, list) => {
29350
27924
  if (country !== void 0 && list.includes(country)) {
29351
27925
  return true;
29352
27926
  }
29353
- return isEU && list.includes(EU_TOKEN);
29354
- }, "matchesRule");
29355
- var isCountryBlocked = /* @__PURE__ */ __name((country, isEU, rule) => {
29356
- if (rule === null || rule.list.length === 0) {
29357
- return false;
27927
+ return isEU && list.includes(EU_TOKEN);
27928
+ }, "matchesRule");
27929
+ var isCountryBlocked = /* @__PURE__ */ __name((country, isEU, rule) => {
27930
+ if (rule === null || rule.list.length === 0) {
27931
+ return false;
27932
+ }
27933
+ const matched = matchesRule(country, isEU, rule.list);
27934
+ return rule.mode === "block" ? matched : !matched;
27935
+ }, "isCountryBlocked");
27936
+
27937
+ // ../../libs/bot-protection/src/score.ts
27938
+ var BOT_SCORE_MIN = 1;
27939
+ var BOT_SCORE_MAX = 99;
27940
+
27941
+ // ../../libs/bot-protection/src/config.ts
27942
+ var NO_PROTECTION = { minBotScore: null, countries: null };
27943
+ var normalizeScore = /* @__PURE__ */ __name((value, problems) => {
27944
+ if (value === void 0 || value === null) {
27945
+ return null;
27946
+ }
27947
+ if (typeof value === "number" && Number.isInteger(value) && value >= BOT_SCORE_MIN && value <= BOT_SCORE_MAX) {
27948
+ return value;
27949
+ }
27950
+ problems.push("has an out-of-range minBotScore");
27951
+ return null;
27952
+ }, "normalizeScore");
27953
+ var normalizeCountries = /* @__PURE__ */ __name((value, problems) => {
27954
+ if (value === void 0 || value === null) {
27955
+ return null;
27956
+ }
27957
+ const record2 = value;
27958
+ if (record2.mode !== "allow" && record2.mode !== "block" || !Array.isArray(record2.list)) {
27959
+ problems.push("has an invalid countries rule");
27960
+ return null;
27961
+ }
27962
+ const list = [];
27963
+ let dropped = 0;
27964
+ for (const token of record2.list) {
27965
+ if (typeof token === "string" && isSelectableCountryToken(token)) {
27966
+ list.push(token);
27967
+ } else {
27968
+ dropped += 1;
27969
+ }
27970
+ }
27971
+ if (dropped > 0) {
27972
+ problems.push(`dropped ${dropped} invalid country token(s)`);
27973
+ }
27974
+ return list.length === 0 ? null : { mode: record2.mode, list };
27975
+ }, "normalizeCountries");
27976
+ var parseBotProtection = /* @__PURE__ */ __name((raw2) => {
27977
+ if (raw2.trim() === "") {
27978
+ return { config: NO_PROTECTION };
27979
+ }
27980
+ let value;
27981
+ try {
27982
+ value = JSON.parse(raw2);
27983
+ } catch {
27984
+ return { config: NO_PROTECTION, error: "BOT_PROTECTION is not valid JSON" };
27985
+ }
27986
+ if (typeof value !== "object" || value === null) {
27987
+ return { config: NO_PROTECTION, error: "BOT_PROTECTION is not an object" };
27988
+ }
27989
+ const record2 = value;
27990
+ const problems = [];
27991
+ const config2 = {
27992
+ minBotScore: normalizeScore(record2.minBotScore, problems),
27993
+ countries: normalizeCountries(record2.countries, problems)
27994
+ };
27995
+ return problems.length > 0 ? { config: config2, error: `BOT_PROTECTION ${problems.join("; ")}` } : { config: config2 };
27996
+ }, "parseBotProtection");
27997
+ var isRequestBlocked = /* @__PURE__ */ __name((config2, signal) => {
27998
+ if (config2.minBotScore !== null && signal.botScore !== void 0 && signal.botScore < config2.minBotScore) {
27999
+ return true;
28000
+ }
28001
+ return isCountryBlocked(signal.country, signal.isEU ?? false, config2.countries);
28002
+ }, "isRequestBlocked");
28003
+
28004
+ // ../../libs/geo-filter/src/us-states.ts
28005
+ var US_STATE_NAMES = {
28006
+ "US-AK": "Alaska",
28007
+ "US-AL": "Alabama",
28008
+ "US-AR": "Arkansas",
28009
+ "US-AZ": "Arizona",
28010
+ "US-CA": "California",
28011
+ "US-CO": "Colorado",
28012
+ "US-CT": "Connecticut",
28013
+ "US-DC": "District of Columbia",
28014
+ "US-DE": "Delaware",
28015
+ "US-FL": "Florida",
28016
+ "US-GA": "Georgia",
28017
+ "US-HI": "Hawaii",
28018
+ "US-IA": "Iowa",
28019
+ "US-ID": "Idaho",
28020
+ "US-IL": "Illinois",
28021
+ "US-IN": "Indiana",
28022
+ "US-KS": "Kansas",
28023
+ "US-KY": "Kentucky",
28024
+ "US-LA": "Louisiana",
28025
+ "US-MA": "Massachusetts",
28026
+ "US-MD": "Maryland",
28027
+ "US-ME": "Maine",
28028
+ "US-MI": "Michigan",
28029
+ "US-MN": "Minnesota",
28030
+ "US-MO": "Missouri",
28031
+ "US-MS": "Mississippi",
28032
+ "US-MT": "Montana",
28033
+ "US-NC": "North Carolina",
28034
+ "US-ND": "North Dakota",
28035
+ "US-NE": "Nebraska",
28036
+ "US-NH": "New Hampshire",
28037
+ "US-NJ": "New Jersey",
28038
+ "US-NM": "New Mexico",
28039
+ "US-NV": "Nevada",
28040
+ "US-NY": "New York",
28041
+ "US-OH": "Ohio",
28042
+ "US-OK": "Oklahoma",
28043
+ "US-OR": "Oregon",
28044
+ "US-PA": "Pennsylvania",
28045
+ "US-RI": "Rhode Island",
28046
+ "US-SC": "South Carolina",
28047
+ "US-SD": "South Dakota",
28048
+ "US-TN": "Tennessee",
28049
+ "US-TX": "Texas",
28050
+ "US-UT": "Utah",
28051
+ "US-VA": "Virginia",
28052
+ "US-VT": "Vermont",
28053
+ "US-WA": "Washington",
28054
+ "US-WI": "Wisconsin",
28055
+ "US-WV": "West Virginia",
28056
+ "US-WY": "Wyoming"
28057
+ };
28058
+ var US_STATE_TOKENS = Object.keys(US_STATE_NAMES);
28059
+ var STATE_TOKENS = new Set(US_STATE_TOKENS);
28060
+
28061
+ // ../../libs/geo-filter/src/geo-filter.ts
28062
+ var UNKNOWN_COUNTRIES = /* @__PURE__ */ new Set(["XX", "T1"]);
28063
+ var tokenMatches = /* @__PURE__ */ __name((token, location) => {
28064
+ if (token === EU_TOKEN) {
28065
+ return location.isEU === true;
28066
+ }
28067
+ if (STATE_TOKENS.has(token)) {
28068
+ return location.country === "US" && `US-${location.regionCode}` === token;
28069
+ }
28070
+ return token === location.country;
28071
+ }, "tokenMatches");
28072
+ var locationKnown = /* @__PURE__ */ __name((filter, location) => {
28073
+ if (location.country === void 0 || UNKNOWN_COUNTRIES.has(location.country)) {
28074
+ return false;
28075
+ }
28076
+ const tokens = [...filter.include, ...filter.exclude];
28077
+ if (location.country === "US" && !location.regionCode && tokens.some((token) => STATE_TOKENS.has(token))) {
28078
+ return false;
28079
+ }
28080
+ return location.isEU !== void 0 || !tokens.includes(EU_TOKEN);
28081
+ }, "locationKnown");
28082
+ var geoFilterAllows = /* @__PURE__ */ __name((filter, location) => {
28083
+ if (!locationKnown(filter, location)) {
28084
+ return false;
28085
+ }
28086
+ const included = filter.include.length === 0 || filter.include.some((token) => {
28087
+ return tokenMatches(token, location);
28088
+ });
28089
+ return included && !filter.exclude.some((token) => {
28090
+ return tokenMatches(token, location);
28091
+ });
28092
+ }, "geoFilterAllows");
28093
+
28094
+ // ../../libs/geo-filter/src/validate.ts
28095
+ var KNOWN_TOKENS = /* @__PURE__ */ new Set([EU_TOKEN, ...COUNTRY_CODES, ...US_STATE_TOKENS]);
28096
+
28097
+ // src/apps/geo-filter.ts
28098
+ var startedOnSocket = /* @__PURE__ */ __name((trigger) => {
28099
+ return trigger.kind === "connect" || trigger.action.source === "website" || trigger.action.fromSocket === true;
28100
+ }, "startedOnSocket");
28101
+ var geoLocation = /* @__PURE__ */ __name((geo) => {
28102
+ return {
28103
+ country: geo.country,
28104
+ regionCode: geo.regionCode,
28105
+ isEU: geo.isEUCountry === "unknown" ? void 0 : geo.isEUCountry === "yes"
28106
+ };
28107
+ }, "geoLocation");
28108
+ var socketLocation = /* @__PURE__ */ __name((cf) => {
28109
+ return geoLocation(geoFromCf(cf));
28110
+ }, "socketLocation");
28111
+ var visitorLocation = /* @__PURE__ */ __name((trigger) => {
28112
+ if (!startedOnSocket(trigger)) {
28113
+ return null;
28114
+ }
28115
+ return socketLocation(trigger.kind === "connect" ? trigger.geo : trigger.action.geo);
28116
+ }, "visitorLocation");
28117
+ var geoRunnableInstalls = /* @__PURE__ */ __name((slug, app2, location) => {
28118
+ const runs = [];
28119
+ const skipped = [];
28120
+ for (const install of app2.installs) {
28121
+ if (!install.active) {
28122
+ continue;
28123
+ }
28124
+ if (install.geoFilter === void 0 || location === null || geoFilterAllows(install.geoFilter, location)) {
28125
+ runs.push(install);
28126
+ } else {
28127
+ skipped.push(`${slug}:${install.id}`);
28128
+ }
28129
+ }
28130
+ return { runs, skipped };
28131
+ }, "geoRunnableInstalls");
28132
+ var geoAllowedInstalls = /* @__PURE__ */ __name((registry3, cf) => {
28133
+ const location = socketLocation(cf);
28134
+ return Object.entries(registry3.apps).flatMap(([slug, app2]) => {
28135
+ return geoRunnableInstalls(slug, app2, location).runs.filter((install) => install.geoFilter !== void 0).map((install) => install.id);
28136
+ });
28137
+ }, "geoAllowedInstalls");
28138
+
28139
+ // src/apps/observability.ts
28140
+ var APP_ERROR_DATA_POINT_RESERVE = 50;
28141
+ var observabilityApp = /* @__PURE__ */ __name((slug, app2) => {
28142
+ return { slug, name: app2.name, version: app2.version };
28143
+ }, "observabilityApp");
28144
+ var createDataPointBudget = /* @__PURE__ */ __name(() => {
28145
+ let events = 0;
28146
+ let total = 0;
28147
+ return {
28148
+ take: /* @__PURE__ */ __name((kind2) => {
28149
+ if (total >= MAX_APP_DATA_POINTS_PER_INVOCATION) {
28150
+ return false;
28151
+ }
28152
+ if (kind2 === "event") {
28153
+ if (events >= MAX_APP_DATA_POINTS_PER_INVOCATION - APP_ERROR_DATA_POINT_RESERVE) {
28154
+ return false;
28155
+ }
28156
+ events += 1;
28157
+ }
28158
+ total += 1;
28159
+ return true;
28160
+ }, "take")
28161
+ };
28162
+ }, "createDataPointBudget");
28163
+ var totalLength = /* @__PURE__ */ __name((blobs) => {
28164
+ return blobs.reduce((sum, blob) => {
28165
+ return sum + utf8Length(blob);
28166
+ }, 0);
28167
+ }, "totalLength");
28168
+ var errorBlobs = /* @__PURE__ */ __name((app2, install, record2, rowId) => {
28169
+ const fixed = [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, rowId];
28170
+ let remaining = MAX_APP_DATA_POINT_BLOB_BYTES - totalLength(fixed);
28171
+ const description = truncateUtf8(record2.description, Math.max(0, remaining));
28172
+ remaining -= utf8Length(description);
28173
+ const payload = truncateUtf8(record2.payload ?? "", Math.max(0, remaining));
28174
+ remaining -= utf8Length(payload);
28175
+ const error61 = truncateUtf8(record2.error ?? "", Math.max(0, remaining));
28176
+ return [app2.slug, app2.name, install.id, install.label, app2.version, record2.category, record2.action, description, payload, error61, rowId];
28177
+ }, "errorBlobs");
28178
+ var eventBlobs = /* @__PURE__ */ __name((app2, install, record2) => {
28179
+ return [app2.slug, app2.name, install.id, install.label, app2.version, record2.name, record2.status];
28180
+ }, "eventBlobs");
28181
+ var writeEvents = /* @__PURE__ */ __name((env, target, byOrd, events) => {
28182
+ for (const [index, event] of events.entries()) {
28183
+ const install = byOrd.get(event.ord);
28184
+ if (install === void 0) {
28185
+ addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
28186
+ continue;
28187
+ }
28188
+ if (!target.budget.take("event")) {
28189
+ addSpanEvent("app observability dropped events at the data-point cap", {
28190
+ level: "warn",
28191
+ attrs: { app: target.app.slug, skipped: events.length - index }
28192
+ });
28193
+ break;
28194
+ }
28195
+ env.APP_EVENTS.writeDataPoint({ indexes: [`${install.id}:${event.status}`], blobs: eventBlobs(target.app, install, event) });
28196
+ }
28197
+ }, "writeEvents");
28198
+ var writeErrors = /* @__PURE__ */ __name((env, target, byOrd, errors) => {
28199
+ for (const [index, saved] of errors.entries()) {
28200
+ const install = byOrd.get(saved.ord);
28201
+ if (install === void 0) {
28202
+ addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
28203
+ continue;
28204
+ }
28205
+ if (!target.budget.take("error")) {
28206
+ addSpanEvent("app observability dropped saved errors at the data-point cap", {
28207
+ level: "warn",
28208
+ attrs: { app: target.app.slug, skipped: errors.length - index }
28209
+ });
28210
+ break;
28211
+ }
28212
+ env.APP_ERRORS.writeDataPoint({
28213
+ indexes: [`${install.id}:${saved.category}`],
28214
+ blobs: errorBlobs(target.app, install, saved, ulid3())
28215
+ });
28216
+ }
28217
+ }, "writeErrors");
28218
+ var guarded = /* @__PURE__ */ __name((target, write) => {
28219
+ try {
28220
+ write();
28221
+ return true;
28222
+ } catch (error61) {
28223
+ addSpanEvent("app observability write failed", { level: "warn", attrs: { app: target.app.slug, error: String(error61) } });
28224
+ return false;
28225
+ }
28226
+ }, "guarded");
28227
+ var writeAppObservabilityRecord = /* @__PURE__ */ __name((env, record2, target) => {
28228
+ const byOrd = new Map(
28229
+ target.installs.map((install) => {
28230
+ return [install.ord, install];
28231
+ })
28232
+ );
28233
+ const eventsWritten = guarded(target, () => {
28234
+ writeEvents(env, target, byOrd, record2.events);
28235
+ });
28236
+ const errorsWritten = guarded(target, () => {
28237
+ writeErrors(env, target, byOrd, record2.errors);
28238
+ });
28239
+ if (!eventsWritten || !errorsWritten) {
28240
+ return;
28241
+ }
28242
+ if (record2.truncated === true) {
28243
+ addSpanEvent("the app recorded more observability than one invocation carries", { level: "warn", attrs: { app: target.app.slug } });
28244
+ }
28245
+ }, "writeAppObservabilityRecord");
28246
+ var writeAppObservability = /* @__PURE__ */ __name((env, response, target) => {
28247
+ const record2 = parseAppObservability(response.headers.get(APP_OBSERVABILITY_HEADER));
28248
+ if (record2 !== null) {
28249
+ writeAppObservabilityRecord(env, record2, target);
28250
+ }
28251
+ }, "writeAppObservability");
28252
+
28253
+ // src/apps/read-bounded.ts
28254
+ var readBoundedBody = /* @__PURE__ */ __name(async (message, maxBytes) => {
28255
+ const reader = message.body?.getReader();
28256
+ if (reader === void 0) {
28257
+ return new Uint8Array(0);
28258
+ }
28259
+ const chunks = [];
28260
+ let received = 0;
28261
+ let result = await reader.read();
28262
+ while (!result.done) {
28263
+ received += result.value.byteLength;
28264
+ if (received > maxBytes) {
28265
+ await reader.cancel();
28266
+ return null;
28267
+ }
28268
+ chunks.push(result.value);
28269
+ result = await reader.read();
28270
+ }
28271
+ const bytes = new Uint8Array(received);
28272
+ let offset = 0;
28273
+ for (const chunk of chunks) {
28274
+ bytes.set(chunk, offset);
28275
+ offset += chunk.byteLength;
28276
+ }
28277
+ return bytes;
28278
+ }, "readBoundedBody");
28279
+
28280
+ // src/apps/spans.ts
28281
+ var adoptAppSpans = /* @__PURE__ */ __name((response) => {
28282
+ const header = response.headers.get(APP_SPANS_HEADER);
28283
+ if (header === null || header.length > MAX_APP_SPANS_BYTES) {
28284
+ return;
28285
+ }
28286
+ const trace = currentTraceContext();
28287
+ if (trace?.parent === void 0) {
28288
+ return;
28289
+ }
28290
+ const record2 = parseAppSpans(header);
28291
+ if (record2 === void 0) {
28292
+ return;
28293
+ }
28294
+ for (const span of adoptSpans(record2.spans, { traceId: trace.traceId, parent: trace.parent })) {
28295
+ appendSpan(span);
28296
+ }
28297
+ if (record2.truncated !== void 0 && record2.truncated > 0) {
28298
+ setSpanAttributes({ "app.spans_truncated": record2.truncated });
28299
+ }
28300
+ }, "adoptAppSpans");
28301
+
28302
+ // src/apps/invoke.ts
28303
+ var APP_INVOCATION_TIMEOUT_MS = 1e4;
28304
+ var APP_HOOK_TIMEOUT_MS = 25e3;
28305
+ var MAX_EMIT_DEPTH = 2;
28306
+ var HOOK_URL = "https://app/__qinto/hook";
28307
+ var invocationContext = /* @__PURE__ */ __name((env) => {
28308
+ const { APPS_KEY_MASTER: master, QINTO_DOMAIN_ID: domainId } = env;
28309
+ if (master === void 0 || domainId === void 0) {
28310
+ return void 0;
28311
+ }
28312
+ return { master, domainId, consentEnforced: consentEnforced(env) };
28313
+ }, "invocationContext");
28314
+ var postHookEnvelope = /* @__PURE__ */ __name(async (env, { binding, key, envelope, timeoutMs }) => {
28315
+ const service = env[binding];
28316
+ if (typeof service?.fetch !== "function") {
28317
+ return void 0;
28318
+ }
28319
+ const headers = { "content-type": "application/json", "x-qinto-app-key": key };
28320
+ const trace = currentTraceContext();
28321
+ if (trace !== void 0) {
28322
+ headers[TRACE_HEADER] = serializeTraceContext(trace);
28323
+ }
28324
+ const response = await service.fetch(HOOK_URL, {
28325
+ method: "POST",
28326
+ headers,
28327
+ body: JSON.stringify(envelope),
28328
+ signal: AbortSignal.timeout(timeoutMs)
28329
+ });
28330
+ adoptAppSpans(response);
28331
+ return response;
28332
+ }, "postHookEnvelope");
28333
+ var triggerDepth = /* @__PURE__ */ __name((trigger) => {
28334
+ return trigger.kind === "event" ? trigger.action.origin?.depth ?? 0 : 0;
28335
+ }, "triggerDepth");
28336
+ var stillDeclared = /* @__PURE__ */ __name((target, key, source) => {
28337
+ if (source === "app") {
28338
+ return true;
28339
+ }
28340
+ const { queryParams, cookies } = target.app.capture;
28341
+ return source === "query" ? queryParams.some((entry) => entry.name === key) : cookies.some((entry) => entry.name === key);
28342
+ }, "stillDeclared");
28343
+ var deliverableCaptures = /* @__PURE__ */ __name((target, rows) => {
28344
+ if (rows === void 0) {
28345
+ return void 0;
28346
+ }
28347
+ const deliverable = {};
28348
+ for (const [key, row] of Object.entries(rows)) {
28349
+ if (stillDeclared(target, key, row.source)) {
28350
+ deliverable[key] = { value: row.value, createdAt: row.createdAt, updatedAt: row.updatedAt, type: row.type };
28351
+ }
28352
+ }
28353
+ return Object.keys(deliverable).length === 0 ? void 0 : deliverable;
28354
+ }, "deliverableCaptures");
28355
+ var eventAction = /* @__PURE__ */ __name((target, trigger, snapshot) => {
28356
+ const buckets = target.app.data;
28357
+ return {
28358
+ actionId: trigger.action.actionId,
28359
+ type: trigger.action.type,
28360
+ userId: snapshot.anonId,
28361
+ payload: trigger.action.payload,
28362
+ receivedAt: trigger.action.receivedAt,
28363
+ source: trigger.action.source,
28364
+ ...buckets.includes("eventContext") && trigger.action.context !== void 0 ? { context: trigger.action.context } : {},
28365
+ ...trigger.action.browserResults?.[target.slug] === void 0 ? {} : { browser: trigger.action.browserResults[target.slug] },
28366
+ ...trigger.action.origin !== void 0 ? { origin: trigger.action.origin } : {}
28367
+ };
28368
+ }, "eventAction");
28369
+ var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context) => {
28370
+ const buckets = target.app.data;
28371
+ const geoAllowed = buckets.includes("geo");
28372
+ const currentGeo = trigger.kind === "event" ? trigger.action.geo : trigger.geo;
28373
+ const consented = consentAllows(snapshot.consent, target.slug, target.app.consent.category, context.consentEnforced);
28374
+ const captures = consented ? deliverableCaptures(target, snapshot.captures[target.slug]) : void 0;
28375
+ const visitorContext = deliverableContext({ target, consented, ...snapshot.context });
28376
+ return {
28377
+ v: ENVELOPE_VERSION,
28378
+ kind: trigger.kind,
28379
+ app: { slug: target.slug, version: target.app.version },
28380
+ installs: target.installs,
28381
+ user: {
28382
+ anonId: snapshot.anonId,
28383
+ globalId: snapshot.globalId,
28384
+ ...snapshot.createdAt === null ? {} : { createdAt: snapshot.createdAt },
28385
+ // Custom properties intentionally win on key collisions with PII, matching the profile read path.
28386
+ properties: {
28387
+ ...buckets.includes("pii") ? snapshot.pii : {},
28388
+ ...buckets.includes("customProperties") ? snapshot.custom : {}
28389
+ },
28390
+ ...geoAllowed ? { geo: snapshot.userGeo } : {}
28391
+ },
28392
+ ...buckets.includes("consentState") ? { consent: { enabled: context.consentEnforced, categories: snapshot.consent.categories, apps: snapshot.consent.apps } } : {},
28393
+ ...geoAllowed && currentGeo !== void 0 ? { geo: currentGeo } : {},
28394
+ ...trigger.kind === "connect" && trigger.page !== void 0 && buckets.includes("eventContext") ? { page: trigger.page } : {},
28395
+ // Not gated on a capabilities.data bucket: an app only ever sees what it captured or wrote.
28396
+ ...captures === void 0 ? {} : { captures },
28397
+ ...visitorContext === void 0 ? {} : { context: visitorContext },
28398
+ depth: triggerDepth(trigger),
28399
+ ...trigger.kind === "event" ? { action: eventAction(target, trigger, snapshot) } : {}
28400
+ };
28401
+ }, "buildEnvelope");
28402
+ var ingestEmit = /* @__PURE__ */ __name(async (ingest2, { slug, ord, depth, emit, anonId, geo, host, context, fromSocket }) => {
28403
+ const parsed = emitPayloadSchema.safeParse(emit.payload);
28404
+ if (!parsed.success) {
28405
+ addSpanEvent("emit payload rejected", { level: "warn", attrs: { app: slug, ord } });
28406
+ return;
28407
+ }
28408
+ const { event, eventId, properties: properties2 } = parsed.data;
28409
+ const inherited = {
28410
+ ...context ?? {},
28411
+ source: "app",
28412
+ occurredAt: emit.occurredAt,
28413
+ ...context !== void 0 || host === void 0 ? {} : {
28414
+ network: {
28415
+ ...host.ip === void 0 ? {} : { ip: host.ip },
28416
+ ...host.userAgent === void 0 ? {} : { userAgent: host.userAgent },
28417
+ ...host.locale === void 0 ? {} : { locale: host.locale },
28418
+ provenance: "edge"
28419
+ }
28420
+ },
28421
+ offline: context === void 0 || context.source === "browser" ? false : context.offline,
28422
+ replay: context === void 0 || context.source === "browser" ? false : context.replay
28423
+ };
28424
+ const action = buildAction({
28425
+ type: "track",
28426
+ userId: anonId,
28427
+ source: "app",
28428
+ payload: { event, eventId: emittedEventId(eventId, properties2), properties: properties2 ?? {} },
28429
+ context: inherited,
28430
+ origin: { app: slug, install: ord, depth: depth + 1 },
28431
+ fromSocket,
28432
+ geo,
28433
+ host
28434
+ });
28435
+ await ingest2(action);
28436
+ }, "ingestEmit");
28437
+ var deliverGroupPushes = /* @__PURE__ */ __name(async ({ slug, group, installs, deliverPush }) => {
28438
+ const install = installs.find((entry) => entry.ord === group.ord);
28439
+ for (const push of group.pushes) {
28440
+ const { topic, data } = push;
28441
+ switch (push.broadcast ?? "install") {
28442
+ case "install": {
28443
+ if (install === void 0) {
28444
+ addSpanEvent("push rejected: its group matches no invoked install", { level: "warn", attrs: { app: slug, ord: group.ord } });
28445
+ break;
28446
+ }
28447
+ await deliverPush({ slug, installId: install.id, topic, data, scope: "app" });
28448
+ break;
28449
+ }
28450
+ case "app": {
28451
+ await deliverPush({ slug, installId: null, topic, data, scope: "app" });
28452
+ break;
28453
+ }
28454
+ // Whether this app may broadcast site-wide is settled in `deliverAppPush`, the one path every
28455
+ // delivery funnels through; re-deciding it here would be a second authority to keep in sync.
28456
+ case "site": {
28457
+ await deliverPush({ slug, installId: null, topic, data, scope: "site" });
28458
+ break;
28459
+ }
28460
+ }
28461
+ }
28462
+ }, "deliverGroupPushes");
28463
+ var applyEffects = /* @__PURE__ */ __name(async ({
28464
+ slug,
28465
+ groups,
28466
+ depth,
28467
+ anonId,
28468
+ installs,
28469
+ deliverPush,
28470
+ writeCaptures: writeCaptures2,
28471
+ writeContext: writeContext2,
28472
+ ingest: ingest2,
28473
+ geo,
28474
+ host,
28475
+ context,
28476
+ fromSocket
28477
+ }) => {
28478
+ const captures = groups.flatMap((group) => group.captures);
28479
+ if (captures.length > 0) {
28480
+ await writeCaptures2(slug, captures);
28481
+ }
28482
+ const contextWrites = groups.flatMap((group) => group.context);
28483
+ if (contextWrites.length > 0) {
28484
+ await writeContext2(slug, contextWrites);
28485
+ }
28486
+ for (const group of groups) {
28487
+ await deliverGroupPushes({ slug, group, installs, deliverPush });
28488
+ if (group.emits.length === 0) {
28489
+ continue;
28490
+ }
28491
+ if (depth >= MAX_EMIT_DEPTH) {
28492
+ addSpanEvent("emit rejected at the depth cap", { level: "warn", attrs: { app: slug, ord: group.ord, depth } });
28493
+ continue;
28494
+ }
28495
+ for (const emit of group.emits) {
28496
+ await ingestEmit(ingest2, { slug, ord: group.ord, depth, emit, anonId, geo, host, context, fromSocket });
28497
+ }
28498
+ }
28499
+ }, "applyEffects");
28500
+ var hookResponseBody = /* @__PURE__ */ __name(async (response, slug) => {
28501
+ if (!response.ok) {
28502
+ addSpanEvent("app hook failed", { level: "warn", attrs: { app: slug, status: response.status } });
28503
+ return void 0;
28504
+ }
28505
+ const bytes = await readBoundedBody(response, MAX_RESPONSE_BYTES);
28506
+ if (bytes === null) {
28507
+ addSpanEvent("app hook response rejected", { level: "warn", attrs: { app: slug, reason: "The response exceeds the size cap." } });
28508
+ return void 0;
28509
+ }
28510
+ return new TextDecoder().decode(bytes);
28511
+ }, "hookResponseBody");
28512
+ var runInvokeApp = /* @__PURE__ */ __name(async (env, { target, trigger, snapshot, context, deliverEffects, budget }) => {
28513
+ try {
28514
+ const key = await deriveAppInternalKey(context.master, context.domainId, target.slug);
28515
+ const response = await postHookEnvelope(env, {
28516
+ binding: target.app.binding,
28517
+ key,
28518
+ envelope: buildEnvelope(target, trigger, snapshot, context),
28519
+ timeoutMs: APP_HOOK_TIMEOUT_MS
28520
+ });
28521
+ if (response === void 0) {
28522
+ addSpanEvent("app service binding is missing", { level: "warn", attrs: { app: target.slug, binding: target.app.binding } });
28523
+ return;
28524
+ }
28525
+ writeAppObservability(env, response, {
28526
+ app: observabilityApp(target.slug, target.app),
28527
+ installs: target.installs,
28528
+ budget
28529
+ });
28530
+ setSpanAttributes({ "app.status": response.status });
28531
+ const body = await hookResponseBody(response, target.slug);
28532
+ if (body === void 0) {
28533
+ return;
28534
+ }
28535
+ const verdict = validateEnvelopeResponse(JSON.parse(body), target.installs.length);
28536
+ if (!verdict.ok) {
28537
+ addSpanEvent("app hook response rejected", { level: "warn", attrs: { app: target.slug, reason: verdict.reason } });
28538
+ return;
28539
+ }
28540
+ if (verdict.error !== null) {
28541
+ addSpanEvent("app hook reported an error", { level: "warn", attrs: { app: target.slug, message: redactUrlParams(verdict.error) } });
28542
+ }
28543
+ for (const ord of verdict.effects.rejectedOrds) {
28544
+ addSpanEvent("app effects rejected over the per-install caps", { level: "warn", attrs: { app: target.slug, ord } });
28545
+ }
28546
+ if (verdict.effects.accepted.length === 0) {
28547
+ return;
28548
+ }
28549
+ await deliverEffects({
28550
+ anonId: snapshot.anonId,
28551
+ slug: target.slug,
28552
+ groups: verdict.effects.accepted,
28553
+ depth: triggerDepth(trigger),
28554
+ installs: target.installs,
28555
+ geo: trigger.kind === "event" ? trigger.action.geo : trigger.geo,
28556
+ host: trigger.kind === "event" ? trigger.action.host : trigger.host,
28557
+ context: trigger.kind === "event" ? trigger.action.context : void 0,
28558
+ ...startedOnSocket(trigger) ? { fromSocket: true } : {}
28559
+ });
28560
+ } catch (error61) {
28561
+ addSpanEvent("app invocation failed", { level: "warn", attrs: { app: target.slug, error: String(error61) } });
28562
+ }
28563
+ }, "runInvokeApp");
28564
+ var invokeApp = /* @__PURE__ */ __name(async (env, input2) => {
28565
+ const { target, trigger } = input2;
28566
+ return traced(
28567
+ `app.${trigger.kind} ${target.slug}`,
28568
+ {
28569
+ attrs: {
28570
+ "app.slug": target.slug,
28571
+ "app.version": target.app.version,
28572
+ "app.binding": target.app.binding,
28573
+ "app.hook": trigger.kind === "event" ? `onEvent.${trigger.action.type}` : "onConnect",
28574
+ "app.installs": target.installs.length
28575
+ }
28576
+ },
28577
+ () => {
28578
+ return runInvokeApp(env, input2);
28579
+ }
28580
+ );
28581
+ }, "invokeApp");
28582
+
28583
+ // src/apps/broker.ts
28584
+ var MAX_RPC_DEPTH = 3;
28585
+ var BROKER_PATHS = ["/__qinto/push", "/__qinto/rpc"];
28586
+ var pushBody = external_exports.object({
28587
+ ...identityFields,
28588
+ kind: external_exports.literal("push"),
28589
+ userId: external_exports.string().min(1).max(64),
28590
+ installId: external_exports.string().min(1).max(64).optional(),
28591
+ broadcast: external_exports.enum(PUSH_BROADCASTS).optional(),
28592
+ topic: external_exports.string().min(1).max(64),
28593
+ data: external_exports.unknown().optional()
28594
+ }).strict();
28595
+ var rpcBody = external_exports.object({
28596
+ ...identityFields,
28597
+ kind: external_exports.literal("rpc"),
28598
+ // Absent when the caller is an endpoint, a schedule or a ui handler; the callee then sees no user.
28599
+ userId: external_exports.string().min(1).max(64).optional(),
28600
+ callee: external_exports.string().min(1).max(30),
28601
+ fn: external_exports.string().min(1).max(64),
28602
+ args: external_exports.unknown().optional(),
28603
+ installId: external_exports.string().min(1).max(64).optional(),
28604
+ depth: external_exports.number().int().min(0)
28605
+ }).strict();
28606
+ var installsBody = external_exports.object({
28607
+ ...identityFields,
28608
+ kind: external_exports.literal("installs"),
28609
+ callee: external_exports.string().min(1).max(30)
28610
+ }).strict();
28611
+ var activeBody = external_exports.object({
28612
+ ...identityFields,
28613
+ kind: external_exports.literal("active"),
28614
+ ord: external_exports.number().int().min(1),
28615
+ userId: external_exports.string().min(1).max(64).optional()
28616
+ }).strict();
28617
+ var contextReadBody = external_exports.object({
28618
+ ...identityFields,
28619
+ kind: external_exports.literal("contextRead"),
28620
+ userId: external_exports.string().min(1).max(64)
28621
+ }).strict();
28622
+ var contextWriteBody = external_exports.object({
28623
+ ...identityFields,
28624
+ kind: external_exports.literal("contextWrite"),
28625
+ userId: external_exports.string().min(1).max(64),
28626
+ writes: external_exports.array(contextWriteSchema).min(1).max(MAX_CONTEXT_WRITES_PER_INSTALL)
28627
+ }).strict();
28628
+ var factsQuery = external_exports.object({
28629
+ kinds: external_exports.array(external_exports.enum(APP_FACT_KINDS)).min(1).max(APP_FACT_KINDS.length).optional(),
28630
+ observedAtGte: external_exports.number().int().nonnegative().safe().optional(),
28631
+ observedAtLt: external_exports.number().int().nonnegative().safe().optional(),
28632
+ limit: external_exports.number().int().min(1).max(MAX_APP_FACTS_PER_PAGE).optional()
28633
+ }).strict().refine((query) => query.observedAtGte === void 0 || query.observedAtLt === void 0 || query.observedAtGte < query.observedAtLt);
28634
+ var factsReadBody = external_exports.object({
28635
+ ...identityFields,
28636
+ kind: external_exports.literal("factsRead"),
28637
+ userId: external_exports.string().min(1).max(64),
28638
+ query: factsQuery.optional(),
28639
+ cursor: external_exports.string().min(1).max(4096).optional()
28640
+ }).strict().refine((body) => body.query === void 0 !== (body.cursor === void 0));
28641
+ var identityReadBody = external_exports.object({
28642
+ ...identityFields,
28643
+ kind: external_exports.literal("identityRead"),
28644
+ identifier: identifierField2,
28645
+ cursor: external_exports.string().regex(/^\d+$/).optional()
28646
+ }).strict();
28647
+ var brokerBody = external_exports.discriminatedUnion("kind", [
28648
+ pushBody,
28649
+ rpcBody,
28650
+ installsBody,
28651
+ activeBody,
28652
+ contextReadBody,
28653
+ contextWriteBody,
28654
+ factsReadBody,
28655
+ identityReadBody,
28656
+ currencyRatesBody,
28657
+ trackBody
28658
+ ]);
28659
+ var BODY_ENVELOPE_ALLOWANCE_BYTES = 2 * 1024;
28660
+ var MAX_BODY_BYTES = MAX_RPC_BYTES + BODY_ENVELOPE_ALLOWANCE_BYTES;
28661
+ var parseJson = /* @__PURE__ */ __name((raw2) => {
28662
+ try {
28663
+ return JSON.parse(raw2);
28664
+ } catch {
28665
+ return null;
28666
+ }
28667
+ }, "parseJson");
28668
+ var factsCursor = external_exports.object({
28669
+ userId: external_exports.string().min(1).max(64),
28670
+ kinds: external_exports.array(external_exports.enum(APP_FACT_KINDS)).min(1).max(APP_FACT_KINDS.length),
28671
+ observedAtGte: external_exports.number().int().nonnegative().safe().nullable(),
28672
+ observedAtLt: external_exports.number().int().nonnegative().safe().nullable(),
28673
+ limit: external_exports.number().int().min(1).max(MAX_APP_FACTS_PER_PAGE),
28674
+ afterRowId: external_exports.number().int().nonnegative().safe(),
28675
+ maxRowId: external_exports.number().int().positive().safe().nullable()
28676
+ }).strict();
28677
+ var cursorKey = /* @__PURE__ */ __name(async (hex3) => {
28678
+ const bytes = Uint8Array.from(hex3.matchAll(/.{2}/g), ([pair]) => Number.parseInt(pair, 16));
28679
+ return crypto.subtle.importKey("raw", bytes, "AES-GCM", false, ["encrypt", "decrypt"]);
28680
+ }, "cursorKey");
28681
+ var encodeBase64Url = /* @__PURE__ */ __name((bytes) => {
28682
+ let binary = "";
28683
+ for (const byte of bytes) {
28684
+ binary += String.fromCharCode(byte);
28685
+ }
28686
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
28687
+ }, "encodeBase64Url");
28688
+ var decodeBase64Url = /* @__PURE__ */ __name((value) => {
28689
+ const base643 = value.replaceAll("-", "+").replaceAll("_", "/");
28690
+ const padded = base643.padEnd(Math.ceil(base643.length / 4) * 4, "=");
28691
+ const binary = atob(padded);
28692
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
28693
+ }, "decodeBase64Url");
28694
+ var encryptFactsCursor = /* @__PURE__ */ __name(async (key, cursor) => {
28695
+ const iv = crypto.getRandomValues(new Uint8Array(12));
28696
+ const plaintext = new TextEncoder().encode(JSON.stringify(cursor));
28697
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, await cursorKey(key), plaintext));
28698
+ const packed = new Uint8Array(iv.byteLength + ciphertext.byteLength);
28699
+ packed.set(iv);
28700
+ packed.set(ciphertext, iv.byteLength);
28701
+ return encodeBase64Url(packed);
28702
+ }, "encryptFactsCursor");
28703
+ var decryptFactsCursor = /* @__PURE__ */ __name(async (key, encoded) => {
28704
+ try {
28705
+ const packed = decodeBase64Url(encoded);
28706
+ if (packed.byteLength <= 12) {
28707
+ return null;
28708
+ }
28709
+ const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: packed.slice(0, 12) }, await cursorKey(key), packed.slice(12));
28710
+ return JSON.parse(new TextDecoder().decode(plaintext));
28711
+ } catch {
28712
+ return null;
28713
+ }
28714
+ }, "decryptFactsCursor");
28715
+ var invalidBody = /* @__PURE__ */ __name(() => {
28716
+ return Response.json({ error: "invalid_body" }, { status: 400 });
28717
+ }, "invalidBody");
28718
+ var forbidden = /* @__PURE__ */ __name(() => {
28719
+ return Response.json({ error: "forbidden" }, { status: 403 });
28720
+ }, "forbidden");
28721
+ var factsPermissionDenied = /* @__PURE__ */ __name(() => {
28722
+ return Response.json({ error: "facts_permission_denied" }, { status: 403 });
28723
+ }, "factsPermissionDenied");
28724
+ var identityPermissionDenied = /* @__PURE__ */ __name(() => {
28725
+ return Response.json({ error: "identity_permission_denied" }, { status: 403 });
28726
+ }, "identityPermissionDenied");
28727
+ var handlePush = /* @__PURE__ */ __name(async (env, body) => {
28728
+ if (new TextEncoder().encode(JSON.stringify(body.data ?? null)).length > MAX_PUSH_BYTES) {
28729
+ return invalidBody();
28730
+ }
28731
+ const broadcast = body.broadcast ?? "install";
28732
+ if (broadcast === "install" && body.installId === void 0) {
28733
+ return invalidBody();
28734
+ }
28735
+ const installId = broadcast === "install" ? body.installId : null;
28736
+ if (await rateLimited(env, `${body.slug}:${installId ?? ""}:push`)) {
28737
+ return Response.json({ error: "rate_limited" }, { status: 429 });
28738
+ }
28739
+ const app2 = siteRegistry().registry.apps[body.slug];
28740
+ if (broadcast === "site" && (app2 === void 0 || !app2.events.publishes.includes(body.topic) || !app2.data.includes(SHARED_CONTEXT_CAPABILITY))) {
28741
+ return Response.json({ error: "topic_not_public" }, { status: 400 });
28742
+ }
28743
+ await anonStub(env, body.userId).appPush(
28744
+ { slug: body.slug, installId, topic: body.topic, data: body.data, scope: broadcast === "site" ? "site" : "app" },
28745
+ ownSite(env),
28746
+ currentTraceContext()
28747
+ );
28748
+ return Response.json({ accepted: true }, { status: 202 });
28749
+ }, "handlePush");
28750
+ var resolveInstall = /* @__PURE__ */ __name((callee, installId) => {
28751
+ const active = callee.installs.filter((install) => install.active);
28752
+ if (installId !== void 0) {
28753
+ const install = active.find((entry) => entry.id === installId);
28754
+ return install === void 0 ? "invalid_install" : { id: install.id, ord: install.ord, label: install.label };
28755
+ }
28756
+ const [only, second] = active;
28757
+ return only === void 0 || second !== void 0 ? "install_required" : { id: only.id, ord: only.ord, label: only.label };
28758
+ }, "resolveInstall");
28759
+ var calleeFailed = /* @__PURE__ */ __name((callee, reason) => {
28760
+ addSpanEvent("rpc callee failed", { level: "warn", attrs: { app: callee, reason } });
28761
+ return Response.json({ error: "callee_failed" }, { status: 502 });
28762
+ }, "calleeFailed");
28763
+ var deliverRpcEffects = /* @__PURE__ */ __name(async (env, { body, userId, effects, install }) => {
28764
+ for (const ord of effects.rejectedOrds) {
28765
+ addSpanEvent("app effects rejected over the per-install caps", { level: "warn", attrs: { app: body.callee, ord } });
28766
+ }
28767
+ const site = ownSite(env);
28768
+ await applyEffects({
28769
+ slug: body.callee,
28770
+ groups: effects.accepted,
28771
+ depth: body.depth,
28772
+ anonId: userId,
28773
+ installs: [install],
28774
+ deliverPush: /* @__PURE__ */ __name(async (push) => {
28775
+ await anonStub(env, userId).appPush(push, site, currentTraceContext());
28776
+ }, "deliverPush"),
28777
+ writeCaptures: /* @__PURE__ */ __name(async (slug, writes) => {
28778
+ await anonStub(env, userId).appCaptures({ slug, writes }, site, currentTraceContext());
28779
+ }, "writeCaptures"),
28780
+ writeContext: /* @__PURE__ */ __name(async (slug, writes) => {
28781
+ await anonStub(env, userId).appContext({ slug, writes }, site, currentTraceContext());
28782
+ }, "writeContext"),
28783
+ ingest: /* @__PURE__ */ __name((action) => {
28784
+ return ingest(env, action, site);
28785
+ }, "ingest")
28786
+ });
28787
+ }, "deliverRpcEffects");
28788
+ var runRelayRpc = /* @__PURE__ */ __name(async (env, { context, body, callee, install }) => {
28789
+ let raw2;
28790
+ try {
28791
+ const key = await deriveAppInternalKey(context.master, context.domainId, body.callee);
28792
+ const response = await postHookEnvelope(env, {
28793
+ binding: callee.binding,
28794
+ key,
28795
+ envelope: {
28796
+ v: ENVELOPE_VERSION,
28797
+ kind: "rpc",
28798
+ app: { slug: body.callee, version: callee.version },
28799
+ installs: [install],
28800
+ // No bucket projection for rpc: the callee sees only the subject's id, never its profile.
28801
+ // Omitted entirely when the caller had no visitor, so the callee's ctx.user is null.
28802
+ ...body.userId === void 0 ? {} : { user: { anonId: body.userId, globalId: null, properties: {} } },
28803
+ depth: body.depth,
28804
+ // JSON.stringify drops an undefined key, and the envelope requires `args` to be present.
28805
+ rpc: { caller: body.slug, fn: body.fn, args: body.args ?? null }
28806
+ },
28807
+ timeoutMs: APP_INVOCATION_TIMEOUT_MS
28808
+ });
28809
+ if (response === void 0) {
28810
+ return calleeFailed(body.callee, "The callee service binding is missing.");
28811
+ }
28812
+ writeAppObservability(env, response, {
28813
+ app: observabilityApp(body.callee, callee),
28814
+ installs: [install],
28815
+ budget: createDataPointBudget()
28816
+ });
28817
+ setSpanAttributes({ "app.status": response.status });
28818
+ if (!response.ok) {
28819
+ return calleeFailed(body.callee, `The callee answered status ${response.status}.`);
28820
+ }
28821
+ const bytes = await readBoundedBody(response, MAX_RESPONSE_BYTES);
28822
+ if (bytes === null) {
28823
+ return calleeFailed(body.callee, "The response exceeds the size cap.");
28824
+ }
28825
+ raw2 = new TextDecoder().decode(bytes);
28826
+ } catch (error61) {
28827
+ return calleeFailed(body.callee, String(error61));
28828
+ }
28829
+ const verdict = validateEnvelopeResponse(parseJson(raw2), 1);
28830
+ if (!verdict.ok) {
28831
+ return calleeFailed(body.callee, verdict.reason);
28832
+ }
28833
+ if (verdict.error !== null) {
28834
+ const reason = redactUrlParams(verdict.error);
28835
+ if (verdict.errorCode === "invalid_arguments") {
28836
+ return Response.json({ error: "invalid_arguments", message: reason }, { status: 400 });
28837
+ }
28838
+ return calleeFailed(body.callee, reason);
28839
+ }
28840
+ if (verdict.rpc === null) {
28841
+ return calleeFailed(body.callee, "The response carries no rpc result.");
28842
+ }
28843
+ if (body.userId === void 0) {
28844
+ if (verdict.effects.accepted.length > 0) {
28845
+ addSpanEvent("rpc effects dropped: the call carries no subject", { level: "warn", attrs: { app: body.callee } });
28846
+ }
28847
+ } else {
28848
+ await deliverRpcEffects(env, { body, userId: body.userId, effects: verdict.effects, install });
28849
+ }
28850
+ return Response.json({ result: verdict.rpc.result });
28851
+ }, "runRelayRpc");
28852
+ var relayRpc = /* @__PURE__ */ __name(async (env, input2) => {
28853
+ const { body, callee, install } = input2;
28854
+ return traced(
28855
+ `app.rpc ${body.callee}`,
28856
+ {
28857
+ attrs: {
28858
+ "app.slug": body.callee,
28859
+ "app.version": callee.version,
28860
+ "app.binding": callee.binding,
28861
+ "app.hook": `rpc.${body.fn}`,
28862
+ "app.install_id": install.id,
28863
+ "app.install_ord": install.ord,
28864
+ "app.install_label": install.label,
28865
+ "app.rpc_caller": body.slug,
28866
+ "app.rpc_depth": body.depth
28867
+ }
28868
+ },
28869
+ () => {
28870
+ return runRelayRpc(env, input2);
28871
+ }
28872
+ );
28873
+ }, "relayRpc");
28874
+ var handleRpc = /* @__PURE__ */ __name(async (env, context, body) => {
28875
+ if (body.depth > MAX_RPC_DEPTH) {
28876
+ return Response.json({ error: "depth_exceeded" }, { status: 400 });
28877
+ }
28878
+ const callee = siteRegistry().registry.apps[body.callee];
28879
+ if (callee === void 0) {
28880
+ return forbidden();
28881
+ }
28882
+ const visibility = callee.rpc.exposes[body.fn];
28883
+ if (visibility !== "public" && !(visibility === "private" && (callee.rpc.grants[body.slug] ?? []).includes(body.fn))) {
28884
+ return forbidden();
28885
+ }
28886
+ const install = resolveInstall(callee, body.installId);
28887
+ if (install === "invalid_install" || install === "install_required") {
28888
+ return Response.json({ error: install }, { status: 400 });
28889
+ }
28890
+ if (new TextEncoder().encode(JSON.stringify(body.args ?? null)).length > MAX_RPC_BYTES) {
28891
+ return Response.json({ error: "too_large" }, { status: 413 });
28892
+ }
28893
+ if (await rateLimited(env, `${body.slug}:${install.id}:rpc`)) {
28894
+ return Response.json({ error: "rate_limited" }, { status: 429 });
28895
+ }
28896
+ return relayRpc(env, { context, body, callee, install });
28897
+ }, "handleRpc");
28898
+ var handleInstalls = /* @__PURE__ */ __name(async (env, body) => {
28899
+ const callee = siteRegistry().registry.apps[body.callee];
28900
+ if (callee === void 0 || !(Object.values(callee.rpc.exposes).includes("public") || (callee.rpc.grants[body.slug] ?? []).length > 0)) {
28901
+ return forbidden();
28902
+ }
28903
+ if (await rateLimited(env, `${body.slug}::installs`)) {
28904
+ return Response.json({ error: "rate_limited" }, { status: 429 });
28905
+ }
28906
+ const installs = callee.installs.filter((install) => install.active).map(({ id, label }) => ({ id, label }));
28907
+ return Response.json({ installs });
28908
+ }, "handleInstalls");
28909
+ var handleContextRead = /* @__PURE__ */ __name(async (env, body) => {
28910
+ if (await rateLimited(env, `${body.slug}::context`)) {
28911
+ return Response.json({ error: "rate_limited" }, { status: 429 });
28912
+ }
28913
+ return Response.json(await anonStub(env, body.userId).getAppContext(body.slug, ownSite(env), currentTraceContext()));
28914
+ }, "handleContextRead");
28915
+ var handleContextWrite = /* @__PURE__ */ __name(async (env, body) => {
28916
+ if (await rateLimited(env, `${body.slug}::context`)) {
28917
+ return Response.json({ error: "rate_limited" }, { status: 429 });
28918
+ }
28919
+ const stored = await anonStub(env, body.userId).appContext({ slug: body.slug, writes: body.writes }, ownSite(env), currentTraceContext());
28920
+ return Response.json({ stored }, { status: 202 });
28921
+ }, "handleContextWrite");
28922
+ var initialFactsCursor = /* @__PURE__ */ __name((body) => {
28923
+ const query = body.query;
28924
+ return {
28925
+ userId: body.userId,
28926
+ kinds: query.kinds ?? [...APP_FACT_KINDS],
28927
+ observedAtGte: query.observedAtGte ?? null,
28928
+ observedAtLt: query.observedAtLt ?? null,
28929
+ limit: query.limit ?? MAX_APP_FACTS_PER_PAGE,
28930
+ afterRowId: 0,
28931
+ maxRowId: null
28932
+ };
28933
+ }, "initialFactsCursor");
28934
+ var handleFactsRead = /* @__PURE__ */ __name(async (env, body) => {
28935
+ const app2 = siteRegistry().registry.apps[body.slug];
28936
+ if (app2 === void 0 || !app2.data.includes("facts")) {
28937
+ return factsPermissionDenied();
28938
+ }
28939
+ if (await rateLimited(env, `${body.slug}::facts`)) {
28940
+ return Response.json({ error: "rate_limited" }, { status: 429 });
28941
+ }
28942
+ const decoded = body.cursor === void 0 ? initialFactsCursor(body) : await decryptFactsCursor(body.key, body.cursor);
28943
+ if (decoded === null || decoded.userId !== body.userId || body.cursor !== void 0 && decoded.maxRowId === null) {
28944
+ return Response.json({ error: "invalid_cursor" }, { status: 400 });
28945
+ }
28946
+ const query = {
28947
+ kinds: decoded.kinds,
28948
+ observedAtGte: decoded.observedAtGte,
28949
+ observedAtLt: decoded.observedAtLt,
28950
+ limit: decoded.limit,
28951
+ afterRowId: decoded.afterRowId,
28952
+ maxRowId: decoded.maxRowId
28953
+ };
28954
+ const page = await anonStub(env, body.userId).getAppFacts(body.slug, query, ownSite(env), currentTraceContext());
28955
+ if (page === null) {
28956
+ return factsPermissionDenied();
28957
+ }
28958
+ const nextCursor = page.next === null ? null : await encryptFactsCursor(body.key, {
28959
+ ...decoded,
28960
+ afterRowId: page.next.afterRowId,
28961
+ maxRowId: page.next.maxRowId
28962
+ });
28963
+ return Response.json({ items: page.items, nextCursor });
28964
+ }, "handleFactsRead");
28965
+ var handleIdentityRead = /* @__PURE__ */ __name(async (env, body) => {
28966
+ const app2 = siteRegistry().registry.apps[body.slug];
28967
+ if (app2 === void 0 || !app2.data.includes(IDENTITY_CAPABILITY)) {
28968
+ return identityPermissionDenied();
28969
+ }
28970
+ if (await rateLimited(env, `${body.slug}::identity`)) {
28971
+ return Response.json({ error: "rate_limited" }, { status: 429 });
28972
+ }
28973
+ const person2 = await lookupPersonForApp(env, body.slug, body.identifier, body.cursor === void 0 ? 0 : Number(body.cursor));
28974
+ if (person2 === null) {
28975
+ return identityPermissionDenied();
28976
+ }
28977
+ return Response.json(person2);
28978
+ }, "handleIdentityRead");
28979
+ var handleActive = /* @__PURE__ */ __name(async (env, body) => {
28980
+ const app2 = siteRegistry().registry.apps[body.slug];
28981
+ if (app2 === void 0) {
28982
+ return forbidden();
28983
+ }
28984
+ if (!app2.installs.some((install) => install.active && install.ord === body.ord)) {
28985
+ return forbidden();
28986
+ }
28987
+ if (body.userId !== void 0 && !await anonStub(env, body.userId).appAllowed(body.slug, ownSite(env), currentTraceContext())) {
28988
+ return forbidden();
28989
+ }
28990
+ return Response.json({ active: true });
28991
+ }, "handleActive");
28992
+ var handleBroker = /* @__PURE__ */ __name(async (request, env, ctx) => {
28993
+ const bytes = await readBoundedBody(request, MAX_BODY_BYTES);
28994
+ if (bytes === null) {
28995
+ return Response.json({ error: "too_large" }, { status: 413 });
28996
+ }
28997
+ const candidate = parseJson(new TextDecoder().decode(bytes));
28998
+ const parsed = brokerBody.safeParse(candidate);
28999
+ if (!parsed.success) {
29000
+ if (typeof candidate === "object" && candidate !== null && "kind" in candidate && candidate.kind === "factsRead") {
29001
+ const malformedFacts = candidate;
29002
+ const error61 = malformedFacts.cursor !== void 0 && malformedFacts.query === void 0 ? "invalid_cursor" : "invalid_query";
29003
+ return Response.json({ error: error61 }, { status: 400 });
29004
+ }
29005
+ return invalidBody();
29006
+ }
29007
+ const body = parsed.data;
29008
+ if (siteRegistry().registry.apps[body.slug] === void 0) {
29009
+ return forbidden();
29010
+ }
29011
+ const context = invocationContext(env);
29012
+ if (context === void 0) {
29013
+ return forbidden();
29014
+ }
29015
+ const expected = await deriveAppInternalKey(context.master, context.domainId, body.slug);
29016
+ if (!constantTimeEqual(expected, body.key)) {
29017
+ return forbidden();
29018
+ }
29019
+ switch (body.kind) {
29020
+ case "push": {
29021
+ return handlePush(env, body);
29022
+ }
29023
+ case "rpc": {
29024
+ return handleRpc(env, context, body);
29025
+ }
29026
+ case "installs": {
29027
+ return handleInstalls(env, body);
29028
+ }
29029
+ case "active": {
29030
+ return handleActive(env, body);
29031
+ }
29032
+ case "contextRead": {
29033
+ return handleContextRead(env, body);
29034
+ }
29035
+ case "contextWrite": {
29036
+ return handleContextWrite(env, body);
29037
+ }
29038
+ case "factsRead": {
29039
+ return handleFactsRead(env, body);
29040
+ }
29041
+ case "identityRead": {
29042
+ return handleIdentityRead(env, body);
29043
+ }
29044
+ case "currencyRates": {
29045
+ return handleCurrencyRates(ctx);
29046
+ }
29047
+ case "track": {
29048
+ return handleTrack(env, body);
29049
+ }
29050
+ }
29051
+ }, "handleBroker");
29052
+ var brokerRoutes = new Hono2();
29053
+ var dispatchGated = /* @__PURE__ */ __name((c) => {
29054
+ if (c.env.DEPLOY_TARGET !== "dispatch") {
29055
+ return c.notFound();
29056
+ }
29057
+ return handleBroker(c.req.raw, c.env, c.executionCtx);
29058
+ }, "dispatchGated");
29059
+ for (const path of BROKER_PATHS) {
29060
+ brokerRoutes.post(path, dispatchGated);
29061
+ }
29062
+ var Broker = class extends WorkerEntrypoint {
29063
+ static {
29064
+ __name(this, "Broker");
29065
+ }
29066
+ fetch(request) {
29067
+ const trace = parseTraceContext(request.headers.get(TRACE_HEADER));
29068
+ return runInvocation({ name: "broker", trace }, () => {
29069
+ return handleBroker(request, this.env, this.ctx);
29070
+ });
29071
+ }
29072
+ };
29073
+
29074
+ // src/apps-control.ts
29075
+ import controlText from "./apps-control.json";
29076
+ var apps_control_default = controlText;
29077
+
29078
+ // src/apps/control-targets.ts
29079
+ var controlTargetsSchema = external_exports.object({
29080
+ v: external_exports.literal(1),
29081
+ apps: external_exports.record(
29082
+ external_exports.string(),
29083
+ external_exports.object({
29084
+ binding: external_exports.string().min(1),
29085
+ versions: external_exports.array(external_exports.object({ version: external_exports.string().min(1), versionId: external_exports.string().min(1) }).strict()).min(1)
29086
+ }).strict()
29087
+ )
29088
+ }).strict();
29089
+ var EMPTY_TARGETS = Object.freeze({ v: 1, apps: Object.freeze({}) });
29090
+ var parseControlTargets = /* @__PURE__ */ __name((raw2) => {
29091
+ try {
29092
+ const parsed = controlTargetsSchema.safeParse(JSON.parse(raw2));
29093
+ return parsed.success ? parsed.data : EMPTY_TARGETS;
29094
+ } catch {
29095
+ return EMPTY_TARGETS;
29096
+ }
29097
+ }, "parseControlTargets");
29098
+ var cachedRaw3;
29099
+ var cachedTargets = EMPTY_TARGETS;
29100
+ var controlTargets = /* @__PURE__ */ __name(() => {
29101
+ if (apps_control_default !== cachedRaw3) {
29102
+ cachedTargets = parseControlTargets(apps_control_default);
29103
+ cachedRaw3 = apps_control_default;
29104
+ }
29105
+ return cachedTargets;
29106
+ }, "controlTargets");
29107
+
29108
+ // src/apps/control.ts
29109
+ var CONTROL_PATH = "/__qinto/control";
29110
+ var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
29111
+ var observabilityTarget = /* @__PURE__ */ __name((envelope) => {
29112
+ const installs = envelope.schedule?.installs ?? (envelope.install === void 0 ? [] : [envelope.install]);
29113
+ const app2 = siteRegistry().registry.apps[envelope.app.slug];
29114
+ if (installs.length === 0 || app2 === void 0) {
29115
+ return void 0;
29116
+ }
29117
+ return {
29118
+ app: observabilityApp(envelope.app.slug, { name: app2.name, version: envelope.app.version }),
29119
+ installs,
29120
+ budget: createDataPointBudget()
29121
+ };
29122
+ }, "observabilityTarget");
29123
+ var writeControlObservability = /* @__PURE__ */ __name((env, upstream, envelope) => {
29124
+ const target = observabilityTarget(envelope);
29125
+ if (target !== void 0) {
29126
+ writeAppObservability(env, upstream, target);
29358
29127
  }
29359
- const matched = matchesRule(country, isEU, rule.list);
29360
- return rule.mode === "block" ? matched : !matched;
29361
- }, "isCountryBlocked");
29362
-
29363
- // ../../libs/bot-protection/src/score.ts
29364
- var BOT_SCORE_MIN = 1;
29365
- var BOT_SCORE_MAX = 99;
29366
-
29367
- // ../../libs/bot-protection/src/config.ts
29368
- var NO_PROTECTION = { minBotScore: null, countries: null };
29369
- var normalizeScore = /* @__PURE__ */ __name((value, problems) => {
29370
- if (value === void 0 || value === null) {
29371
- return null;
29128
+ }, "writeControlObservability");
29129
+ var timedOutAction = /* @__PURE__ */ __name((envelope) => {
29130
+ if (envelope.schedule !== void 0) {
29131
+ return `schedules.${envelope.schedule.id}`;
29372
29132
  }
29373
- if (typeof value === "number" && Number.isInteger(value) && value >= BOT_SCORE_MIN && value <= BOT_SCORE_MAX) {
29374
- return value;
29133
+ if (envelope.tool !== void 0) {
29134
+ return `tools.${envelope.tool.name}`;
29375
29135
  }
29376
- problems.push("has an out-of-range minBotScore");
29377
- return null;
29378
- }, "normalizeScore");
29379
- var normalizeCountries = /* @__PURE__ */ __name((value, problems) => {
29380
- if (value === void 0 || value === null) {
29136
+ return envelope.lifecycle === void 0 ? envelope.kind : `lifecycle.${envelope.lifecycle.type}`;
29137
+ }, "timedOutAction");
29138
+ var writeControlTimeout = /* @__PURE__ */ __name((env, envelope, deadlineMs) => {
29139
+ const target = observabilityTarget(envelope);
29140
+ if (target === void 0) {
29141
+ return;
29142
+ }
29143
+ const action = timedOutAction(envelope);
29144
+ const description = `${action} did not answer within ${deadlineMs / 1e3} s, so the site stopped waiting.`;
29145
+ writeAppObservabilityRecord(
29146
+ env,
29147
+ {
29148
+ v: APP_OBSERVABILITY_VERSION,
29149
+ events: [],
29150
+ errors: target.installs.map((install) => {
29151
+ return { ord: install.ord, category: "internal", action, description };
29152
+ })
29153
+ },
29154
+ target
29155
+ );
29156
+ }, "writeControlTimeout");
29157
+ var forwardControl = /* @__PURE__ */ __name(async (env, service, envelope, init) => {
29158
+ const deadlineMs = CONTROL_DEADLINES_MS[envelope.kind].site;
29159
+ try {
29160
+ return await service.fetch("https://app/__qinto/control", { method: "POST", ...init, signal: AbortSignal.timeout(deadlineMs) });
29161
+ } catch (error61) {
29162
+ if (!(error61 instanceof Error && error61.name === "TimeoutError")) {
29163
+ throw error61;
29164
+ }
29165
+ writeControlTimeout(env, envelope, deadlineMs);
29166
+ return void 0;
29167
+ }
29168
+ }, "forwardControl");
29169
+ var textBody = /* @__PURE__ */ __name(async (request) => {
29170
+ const bytes = await readBoundedBody(request, MAX_CONTROL_BYTES);
29171
+ return bytes === null ? null : new TextDecoder().decode(bytes);
29172
+ }, "textBody");
29173
+ var parseJson2 = /* @__PURE__ */ __name((value) => {
29174
+ try {
29175
+ return JSON.parse(value);
29176
+ } catch {
29177
+ return void 0;
29178
+ }
29179
+ }, "parseJson");
29180
+ var authenticateControl = /* @__PURE__ */ __name(async (request, env, body) => {
29181
+ const timestamp = request.headers.get(CONTROL_TIMESTAMP_HEADER);
29182
+ const signature = request.headers.get(CONTROL_SIGNATURE_HEADER);
29183
+ const { APPS_KEY_MASTER: master, QINTO_CONTROL_KEY: controlKey, QINTO_DOMAIN_ID: domainId } = env;
29184
+ if (timestamp === null || signature === null || master === void 0 || controlKey === void 0 || domainId === void 0) {
29185
+ return null;
29186
+ }
29187
+ const timestampMs = Number(timestamp);
29188
+ if (!Number.isSafeInteger(timestampMs) || Math.abs(Date.now() - timestampMs) > MAX_CLOCK_SKEW_MS) {
29381
29189
  return null;
29382
29190
  }
29383
- const record2 = value;
29384
- if (record2.mode !== "allow" && record2.mode !== "block" || !Array.isArray(record2.list)) {
29385
- problems.push("has an invalid countries rule");
29386
- return null;
29191
+ return await verifyControlRequestSignature(controlKey, timestamp, body, signature) ? { master, domainId } : null;
29192
+ }, "authenticateControl");
29193
+ var handleControl = /* @__PURE__ */ __name(async (request, env) => {
29194
+ const body = await textBody(request);
29195
+ if (body === null) {
29196
+ return Response.json({ error: "too_large" }, { status: 413 });
29197
+ }
29198
+ const authentication = await authenticateControl(request, env, body);
29199
+ if (authentication === null) {
29200
+ return Response.json({ error: "unauthorized" }, { status: 401 });
29201
+ }
29202
+ const raw2 = parseJson2(body);
29203
+ if (raw2 === void 0) {
29204
+ return Response.json({ error: "invalid_body" }, { status: 400 });
29205
+ }
29206
+ const parsed = controlEnvelopeSchema.safeParse(raw2);
29207
+ if (!parsed.success) {
29208
+ return Response.json({ error: "invalid_body" }, { status: 400 });
29209
+ }
29210
+ const envelope = parsed.data;
29211
+ const target = controlTargets().apps[envelope.app.slug];
29212
+ if (target === void 0) {
29213
+ return Response.json({ error: "target_missing" }, { status: 404 });
29214
+ }
29215
+ if (!target.versions.some((version2) => version2.version === envelope.app.version)) {
29216
+ return Response.json({ error: "target_version_mismatch" }, { status: 409 });
29217
+ }
29218
+ const service = env[target.binding];
29219
+ if (typeof service?.fetch !== "function") {
29220
+ return Response.json({ error: "binding_missing" }, { status: 503 });
29221
+ }
29222
+ const headers = {
29223
+ "content-type": "application/json",
29224
+ "x-qinto-app-key": await deriveAppInternalKey(authentication.master, authentication.domainId, envelope.app.slug)
29225
+ };
29226
+ const trace = currentTraceContext();
29227
+ if (trace !== void 0) {
29228
+ headers[TRACE_HEADER] = serializeTraceContext(trace);
29229
+ }
29230
+ const upstream = await forwardControl(env, service, envelope, { headers, body });
29231
+ if (upstream === void 0) {
29232
+ return Response.json({ error: "upstream_timeout" }, { status: 504 });
29233
+ }
29234
+ adoptAppSpans(upstream);
29235
+ writeControlObservability(env, upstream, envelope);
29236
+ const responseBytes = await readBoundedBody(upstream, MAX_CONTROL_BYTES);
29237
+ if (responseBytes === null) {
29238
+ return Response.json({ error: "invalid_response" }, { status: 502 });
29239
+ }
29240
+ const responseBody = parseJson2(new TextDecoder().decode(responseBytes));
29241
+ if (responseBody === void 0) {
29242
+ return Response.json({ error: "invalid_response" }, { status: 502 });
29243
+ }
29244
+ const response = controlResponseSchema.safeParse(responseBody);
29245
+ if (!upstream.ok || !response.success) {
29246
+ return Response.json({ error: "invalid_response" }, { status: 502 });
29247
+ }
29248
+ return Response.json(response.data);
29249
+ }, "handleControl");
29250
+ var appControlRoutes = new Hono2().post(CONTROL_PATH, (c) => {
29251
+ return handleControl(c.req.raw, c.env);
29252
+ });
29253
+
29254
+ // ../../libs/worker-access-token/src/endpoints.ts
29255
+ var APP_ENDPOINT_PREFIX = "/app/";
29256
+ var MAX_CUSTOM_ENDPOINT_LENGTH = 128;
29257
+ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
29258
+ var HTTP_METHOD_BIT = { GET: 1, POST: 2, PUT: 4, PATCH: 8, DELETE: 16 };
29259
+ var ALL_METHODS_MASK = HTTP_METHODS.reduce((mask, method) => mask | HTTP_METHOD_BIT[method], 0);
29260
+ var ALLOWED_CHARS = /^[A-Za-z0-9._~/-]+$/;
29261
+ var matchesAppEndpoint = /* @__PURE__ */ __name((endpoints, path, method) => {
29262
+ const bit = HTTP_METHODS.includes(method) ? HTTP_METHOD_BIT[method] : 0;
29263
+ return endpoints.some((endpoint) => {
29264
+ if ((endpoint.methods & bit) === 0) {
29265
+ return false;
29266
+ }
29267
+ return endpoint.path.endsWith("/*") ? path.startsWith(endpoint.path.slice(0, -1)) : path === endpoint.path;
29268
+ });
29269
+ }, "matchesAppEndpoint");
29270
+ var validateAppEndpoint = /* @__PURE__ */ __name((raw2) => {
29271
+ if (!Number.isInteger(raw2.methods) || raw2.methods <= 0 || raw2.methods > ALL_METHODS_MASK) {
29272
+ return { ok: false, reason: "Choose at least one HTTP method." };
29273
+ }
29274
+ const path = raw2.path.trim();
29275
+ if (!path.startsWith(APP_ENDPOINT_PREFIX)) {
29276
+ return { ok: false, reason: "Custom endpoints must start with /app/." };
29277
+ }
29278
+ if (path.length > MAX_CUSTOM_ENDPOINT_LENGTH) {
29279
+ return { ok: false, reason: `Custom endpoint must be ${MAX_CUSTOM_ENDPOINT_LENGTH} characters or fewer.` };
29280
+ }
29281
+ const isWildcard = path.endsWith("/*");
29282
+ if (path.includes("*") && (!isWildcard || path.indexOf("*") !== path.length - 1)) {
29283
+ return { ok: false, reason: "A wildcard is only allowed as a trailing /* (e.g. /app/reports/*)." };
29284
+ }
29285
+ if (!isWildcard && path.endsWith("/")) {
29286
+ return { ok: false, reason: "Custom endpoint must not end with a slash." };
29287
+ }
29288
+ const body = isWildcard ? path.slice(0, -1) : path;
29289
+ if (!ALLOWED_CHARS.test(body)) {
29290
+ return { ok: false, reason: "Custom endpoint contains invalid characters." };
29291
+ }
29292
+ if (body.includes("..")) {
29293
+ return { ok: false, reason: 'Custom endpoint must not contain "..".' };
29294
+ }
29295
+ return { ok: true, value: { methods: raw2.methods, path } };
29296
+ }, "validateAppEndpoint");
29297
+
29298
+ // ../../libs/worker-access-token/src/scopes.ts
29299
+ var SCOPE = {
29300
+ track: 1,
29301
+ identify: 2,
29302
+ alias: 4,
29303
+ consentWrite: 8,
29304
+ propertiesRead: 16,
29305
+ consentRead: 32,
29306
+ sessionRead: 64,
29307
+ idGraphInspect: 128,
29308
+ capture: 256
29309
+ };
29310
+ var ALL_SCOPE_MASK = Object.values(SCOPE).reduce((mask, bit) => mask | bit, 0);
29311
+ var WORKER_ACCESS_TOKEN_SCOPES = [
29312
+ { key: "track", bit: SCOPE.track, label: "Track events", description: "POST /v1/track \u2014 record events a user performed." },
29313
+ { key: "identify", bit: SCOPE.identify, label: "Identify users", description: "POST /v1/identify \u2014 attach identity traits to a user." },
29314
+ { key: "alias", bit: SCOPE.alias, label: "Link identifiers", description: "POST /v1/alias \u2014 link an identifier of your own to a user." },
29315
+ {
29316
+ key: "consentWrite",
29317
+ bit: SCOPE.consentWrite,
29318
+ label: "Save consent",
29319
+ description: "POST /v1/consent \u2014 record a user\u2019s consent choices."
29320
+ },
29321
+ {
29322
+ key: "propertiesRead",
29323
+ bit: SCOPE.propertiesRead,
29324
+ label: "Read properties",
29325
+ description: "GET /v1/properties \u2014 read the properties stored for a user."
29326
+ },
29327
+ {
29328
+ key: "consentRead",
29329
+ bit: SCOPE.consentRead,
29330
+ label: "Read consent",
29331
+ description: "GET /v1/consent \u2014 read the consent stored for a user."
29332
+ },
29333
+ {
29334
+ key: "sessionRead",
29335
+ bit: SCOPE.sessionRead,
29336
+ label: "Read session ids",
29337
+ description: "GET /v1/session, GET /v1/person \u2014 read the anonymous and person ids for a user, and the sessions linked to a person."
29338
+ },
29339
+ {
29340
+ key: "capture",
29341
+ bit: SCOPE.capture,
29342
+ label: "Write captures",
29343
+ description: "POST /v1/capture \u2014 record click ids and vendor ids an installed app declares."
29344
+ }
29345
+ ];
29346
+
29347
+ // ../../libs/worker-access-token/src/env.ts
29348
+ var validMask = /* @__PURE__ */ __name((mask) => {
29349
+ return typeof mask === "number" && Number.isSafeInteger(mask) && mask >= 0 && mask <= ALL_SCOPE_MASK;
29350
+ }, "validMask");
29351
+ var validApp = /* @__PURE__ */ __name((app2) => {
29352
+ return app2 === void 0 || typeof app2 === "string" && app2.length > 0 && app2.length <= 30;
29353
+ }, "validApp");
29354
+ var decodeEndpoints = /* @__PURE__ */ __name((value) => {
29355
+ if (value === void 0) {
29356
+ return { endpoints: [], dropped: 0 };
29357
+ }
29358
+ if (!Array.isArray(value)) {
29359
+ return { endpoints: [], dropped: 1 };
29387
29360
  }
29388
- const list = [];
29361
+ const endpoints = [];
29389
29362
  let dropped = 0;
29390
- for (const token of record2.list) {
29391
- if (typeof token === "string" && isSelectableCountryToken(token)) {
29392
- list.push(token);
29393
- } else {
29363
+ for (const encoded of value) {
29364
+ if (!Array.isArray(encoded) || encoded.length !== 2 || typeof encoded[0] !== "number" || typeof encoded[1] !== "string") {
29365
+ dropped += 1;
29366
+ continue;
29367
+ }
29368
+ const result = validateAppEndpoint({ methods: encoded[0], path: encoded[1] });
29369
+ if (!result.ok) {
29394
29370
  dropped += 1;
29371
+ continue;
29395
29372
  }
29373
+ endpoints.push(Object.freeze(result.value));
29396
29374
  }
29397
- if (dropped > 0) {
29398
- problems.push(`dropped ${dropped} invalid country token(s)`);
29375
+ return { endpoints: Object.freeze(endpoints), dropped };
29376
+ }, "decodeEndpoints");
29377
+ var parseTokenEntry = /* @__PURE__ */ __name((value) => {
29378
+ if (typeof value === "number") {
29379
+ return { entry: validMask(value) && value > 0 ? Object.freeze({ mask: value, app: null, endpoints: null }) : null, dropped: 0 };
29399
29380
  }
29400
- return list.length === 0 ? null : { mode: record2.mode, list };
29401
- }, "normalizeCountries");
29402
- var parseBotProtection = /* @__PURE__ */ __name((raw2) => {
29381
+ if (typeof value !== "object" || value === null) {
29382
+ return { entry: null, dropped: 0 };
29383
+ }
29384
+ const entry = value;
29385
+ if (!validMask(entry.m) || !validApp(entry.a)) {
29386
+ return { entry: null, dropped: 0 };
29387
+ }
29388
+ const decoded = decodeEndpoints(entry.e);
29389
+ if (entry.m === 0 && decoded.endpoints.length === 0) {
29390
+ return { entry: null, dropped: decoded.dropped };
29391
+ }
29392
+ return {
29393
+ entry: Object.freeze({
29394
+ mask: entry.m,
29395
+ app: typeof entry.a === "string" ? entry.a : null,
29396
+ endpoints: decoded.endpoints.length === 0 ? null : decoded.endpoints
29397
+ }),
29398
+ dropped: decoded.dropped
29399
+ };
29400
+ }, "parseTokenEntry");
29401
+ var parseWorkerAccessTokensEnv = /* @__PURE__ */ __name((raw2) => {
29403
29402
  if (raw2.trim() === "") {
29404
- return { config: NO_PROTECTION };
29403
+ return { tokens: /* @__PURE__ */ new Map() };
29405
29404
  }
29406
- let value;
29405
+ let parsed;
29407
29406
  try {
29408
- value = JSON.parse(raw2);
29407
+ parsed = JSON.parse(raw2);
29409
29408
  } catch {
29410
- return { config: NO_PROTECTION, error: "BOT_PROTECTION is not valid JSON" };
29409
+ return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS is not valid JSON" };
29411
29410
  }
29412
- if (typeof value !== "object" || value === null) {
29413
- return { config: NO_PROTECTION, error: "BOT_PROTECTION is not an object" };
29411
+ if (typeof parsed !== "object" || parsed === null || parsed.v !== 2) {
29412
+ return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS has an unsupported version" };
29414
29413
  }
29415
- const record2 = value;
29416
- const problems = [];
29417
- const config2 = {
29418
- minBotScore: normalizeScore(record2.minBotScore, problems),
29419
- countries: normalizeCountries(record2.countries, problems)
29414
+ const table = parsed.t;
29415
+ if (typeof table !== "object" || table === null) {
29416
+ return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS is missing its token table" };
29417
+ }
29418
+ const tokens = /* @__PURE__ */ new Map();
29419
+ let rejected = 0;
29420
+ for (const [hash2, value] of Object.entries(table)) {
29421
+ const parsedEntry = parseTokenEntry(value);
29422
+ rejected += parsedEntry.dropped;
29423
+ if (parsedEntry.entry === null) {
29424
+ rejected += 1;
29425
+ continue;
29426
+ }
29427
+ tokens.set(hash2, parsedEntry.entry);
29428
+ }
29429
+ if (rejected > 0) {
29430
+ return { tokens, error: `WORKER_ACCESS_TOKENS dropped ${rejected} invalid entr${rejected === 1 ? "y" : "ies"}` };
29431
+ }
29432
+ return { tokens };
29433
+ }, "parseWorkerAccessTokensEnv");
29434
+
29435
+ // ../../libs/worker-access-token/src/hash.ts
29436
+ var hashWorkerAccessToken = /* @__PURE__ */ __name(async (raw2) => {
29437
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(raw2));
29438
+ let binary = "";
29439
+ for (const byte of new Uint8Array(digest)) {
29440
+ binary += String.fromCharCode(byte);
29441
+ }
29442
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
29443
+ }, "hashWorkerAccessToken");
29444
+
29445
+ // src/common/auth.ts
29446
+ var cachedRaw4;
29447
+ var cachedTokens = /* @__PURE__ */ new Map();
29448
+ var tokensFor = /* @__PURE__ */ __name((raw2) => {
29449
+ if (raw2 === cachedRaw4) {
29450
+ return cachedTokens;
29451
+ }
29452
+ const parsed = parseWorkerAccessTokensEnv(raw2);
29453
+ if (parsed.error !== void 0) {
29454
+ addSpanEvent("WORKER_ACCESS_TOKENS is misconfigured; rejecting affected tokens until fixed", {
29455
+ level: "error",
29456
+ attrs: { error: String(parsed.error) }
29457
+ });
29458
+ }
29459
+ cachedRaw4 = raw2;
29460
+ cachedTokens = parsed.tokens;
29461
+ return cachedTokens;
29462
+ }, "tokensFor");
29463
+ var readBearerToken = /* @__PURE__ */ __name((request) => {
29464
+ const header = request.headers.get("Authorization");
29465
+ if (header === null) {
29466
+ return null;
29467
+ }
29468
+ const prefix = "Bearer ";
29469
+ if (!header.startsWith(prefix)) {
29470
+ return null;
29471
+ }
29472
+ const token = header.slice(prefix.length).trim();
29473
+ return token.length > 0 ? token : null;
29474
+ }, "readBearerToken");
29475
+ var workerAccessTokenEntry = /* @__PURE__ */ __name(async (env, token) => {
29476
+ const raw2 = env.WORKER_ACCESS_TOKENS ?? "";
29477
+ return tokensFor(raw2).get(await hashWorkerAccessToken(token));
29478
+ }, "workerAccessTokenEntry");
29479
+ var requireScope = /* @__PURE__ */ __name((bit) => {
29480
+ return async (c, next) => {
29481
+ const token = readBearerToken(c.req.raw);
29482
+ if (token === null) {
29483
+ return c.json({ error: "unauthenticated" }, 401);
29484
+ }
29485
+ const entry = await workerAccessTokenEntry(c.env, token);
29486
+ if (entry === void 0) {
29487
+ return c.json({ error: "unauthenticated" }, 401);
29488
+ }
29489
+ if ((entry.mask & bit) === 0) {
29490
+ return c.json({ error: "forbidden" }, 403);
29491
+ }
29492
+ c.set("actionSource", entry.app === null ? "api" : "app");
29493
+ return next();
29420
29494
  };
29421
- return problems.length > 0 ? { config: config2, error: `BOT_PROTECTION ${problems.join("; ")}` } : { config: config2 };
29422
- }, "parseBotProtection");
29423
- var isRequestBlocked = /* @__PURE__ */ __name((config2, signal) => {
29424
- if (config2.minBotScore !== null && signal.botScore !== void 0 && signal.botScore < config2.minBotScore) {
29425
- return true;
29495
+ }, "requireScope");
29496
+
29497
+ // src/helpers/header-json.ts
29498
+ var headerJson = /* @__PURE__ */ __name((value) => {
29499
+ return JSON.stringify(value).replace(/[\u0080-\uffff]/g, (char) => {
29500
+ return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
29501
+ });
29502
+ }, "headerJson");
29503
+
29504
+ // src/apps/stream-deadlines.ts
29505
+ var streamWithDeadlines = /* @__PURE__ */ __name((source, { idleMs, totalMs }) => {
29506
+ if (source === null) {
29507
+ return null;
29426
29508
  }
29427
- return isCountryBlocked(signal.country, signal.isEU ?? false, config2.countries);
29428
- }, "isRequestBlocked");
29509
+ const reader = source.getReader();
29510
+ let target;
29511
+ let totalTimer;
29512
+ let idleTimer = null;
29513
+ let stopped = false;
29514
+ const clearIdleTimer = /* @__PURE__ */ __name(() => {
29515
+ if (idleTimer !== null) {
29516
+ clearTimeout(idleTimer);
29517
+ idleTimer = null;
29518
+ }
29519
+ }, "clearIdleTimer");
29520
+ const clearTimers = /* @__PURE__ */ __name(() => {
29521
+ clearTimeout(totalTimer);
29522
+ clearIdleTimer();
29523
+ }, "clearTimers");
29524
+ const stop = /* @__PURE__ */ __name((message) => {
29525
+ stopped = true;
29526
+ clearTimers();
29527
+ const error61 = new DOMException(message, "TimeoutError");
29528
+ target.error(error61);
29529
+ void reader.cancel(error61).catch(() => void 0);
29530
+ }, "stop");
29531
+ return new ReadableStream({
29532
+ start: /* @__PURE__ */ __name((controller) => {
29533
+ target = controller;
29534
+ totalTimer = setTimeout(() => {
29535
+ stop("The app endpoint stream exceeded its maximum duration.");
29536
+ }, totalMs);
29537
+ }, "start"),
29538
+ pull: /* @__PURE__ */ __name(async (controller) => {
29539
+ idleTimer = setTimeout(() => {
29540
+ stop("The app endpoint stream stopped producing data.");
29541
+ }, idleMs);
29542
+ let result;
29543
+ try {
29544
+ result = await reader.read();
29545
+ } catch (error61) {
29546
+ stopped = true;
29547
+ clearTimers();
29548
+ throw error61;
29549
+ }
29550
+ clearIdleTimer();
29551
+ if (stopped) {
29552
+ return;
29553
+ }
29554
+ if (result.done) {
29555
+ stopped = true;
29556
+ clearTimers();
29557
+ controller.close();
29558
+ return;
29559
+ }
29560
+ controller.enqueue(result.value);
29561
+ }, "pull"),
29562
+ cancel: /* @__PURE__ */ __name(async (reason) => {
29563
+ stopped = true;
29564
+ clearTimers();
29565
+ await reader.cancel(reason);
29566
+ }, "cancel")
29567
+ });
29568
+ }, "streamWithDeadlines");
29429
29569
 
29430
29570
  // src/helpers/bot-check.ts
29431
29571
  var botRejected = /* @__PURE__ */ __name((env, geo) => {
@@ -29524,7 +29664,8 @@ var withCors = /* @__PURE__ */ __name((response, request, method) => {
29524
29664
  return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
29525
29665
  }, "withCors");
29526
29666
  var authorize = /* @__PURE__ */ __name(async (c, { slug, app: app2 }) => {
29527
- if (await botRejected(c.env, geoFromRequest(c.req.raw))) {
29667
+ const geo = geoFromRequest(c.req.raw);
29668
+ if (await botRejected(c.env, geo)) {
29528
29669
  return refuse(403, "traffic_rejected");
29529
29670
  }
29530
29671
  const anonId = getCookie(c, COOKIE_NAME2);
@@ -29533,8 +29674,8 @@ var authorize = /* @__PURE__ */ __name(async (c, { slug, app: app2 }) => {
29533
29674
  return refuse(401, "visitor_required");
29534
29675
  }
29535
29676
  const installId = c.req.header(VISITOR_INSTALL_HEADER);
29536
- const install = app2.installs.find((candidate) => {
29537
- return candidate.active && candidate.id === installId;
29677
+ const install = geoRunnableInstalls(slug, app2, geoLocation(geo)).runs.find((candidate) => {
29678
+ return candidate.id === installId;
29538
29679
  });
29539
29680
  if (install === void 0) {
29540
29681
  return refuse(403, "forbidden");
@@ -31464,34 +31605,46 @@ var gatePasses = /* @__PURE__ */ __name((entry, trigger, consent2, enforced) =>
31464
31605
  return consentAllows(consent2, entry.slug, entry.app.consent.category, enforced);
31465
31606
  }, "gatePasses");
31466
31607
  var eligibleApps = /* @__PURE__ */ __name((registry3, trigger, consent2, enforced) => {
31608
+ const location = visitorLocation(trigger);
31467
31609
  const targets = [];
31610
+ const geoSkipped = [];
31468
31611
  for (const slug of registry3.order) {
31469
31612
  const app2 = registry3.apps[slug];
31470
31613
  if (app2 === void 0 || !wantsTrigger(app2, trigger) || !gatePasses({ slug, app: app2 }, trigger, consent2, enforced)) {
31471
31614
  continue;
31472
31615
  }
31473
- const installs = app2.installs.filter((install) => install.active).map(({ id, ord, label }) => ({ id, ord, label }));
31616
+ const { runs, skipped } = geoRunnableInstalls(slug, app2, location);
31617
+ geoSkipped.push(...skipped);
31618
+ const installs = runs.map(({ id, ord, label }) => ({ id, ord, label }));
31474
31619
  if (installs.length === 0) {
31475
31620
  continue;
31476
31621
  }
31477
31622
  targets.push({ slug, app: app2, installs });
31478
31623
  }
31479
- return targets;
31624
+ return { targets, geoSkipped };
31480
31625
  }, "eligibleApps");
31481
31626
 
31482
31627
  // src/apps/fan-out.ts
31483
31628
  var MAX_APPS_PER_DOMAIN = 50;
31484
31629
  var fanOutToApps = /* @__PURE__ */ __name(async (env, { trigger, snapshot, trace }, { deliverEffects }) => {
31485
31630
  const { registry: registry3, error: error61 } = siteRegistry();
31486
- const targets = eligibleApps(registry3, trigger, snapshot.consent, consentEnforced(env));
31487
- if (error61 === void 0 && targets.length === 0) {
31631
+ const { targets, geoSkipped } = eligibleApps(registry3, trigger, snapshot.consent, consentEnforced(env));
31632
+ if (error61 === void 0 && targets.length === 0 && geoSkipped.length === 0) {
31488
31633
  return;
31489
31634
  }
31490
- await runInvocation({ name: "app.fan_out", trace, attrs: { "fan_out.kind": trigger.kind, "fan_out.apps": targets.length } }, async () => {
31635
+ const attrs = {
31636
+ "fan_out.kind": trigger.kind,
31637
+ "fan_out.apps": targets.length,
31638
+ ...geoSkipped.length === 0 ? {} : { "fan_out.geo_skipped": geoSkipped.join(",") }
31639
+ };
31640
+ await runInvocation({ name: "app.fan_out", trace, attrs }, async () => {
31491
31641
  if (error61 !== void 0) {
31492
31642
  addSpanEvent("apps registry is malformed; apps are off", { level: "error", attrs: { error: error61 } });
31493
31643
  return;
31494
31644
  }
31645
+ if (targets.length === 0) {
31646
+ return;
31647
+ }
31495
31648
  const context = invocationContext(env);
31496
31649
  if (context === void 0) {
31497
31650
  addSpanEvent("apps skipped: APPS_KEY_MASTER or QINTO_DOMAIN_ID is missing", { level: "warn" });
@@ -31557,20 +31710,22 @@ var TransformChainError = class extends Error {
31557
31710
  this.name = "TransformChainError";
31558
31711
  }
31559
31712
  };
31560
- var chainEntries = /* @__PURE__ */ __name((registry3, type) => {
31713
+ var chainEntries = /* @__PURE__ */ __name((registry3, action) => {
31714
+ const location = visitorLocation({ kind: "event", action });
31561
31715
  const entries = [];
31716
+ const geoSkipped = [];
31562
31717
  for (const slug of registry3.order) {
31563
31718
  const app2 = registry3.apps[slug];
31564
- if (app2 === void 0 || !app2.hooks.transform.includes(type)) {
31719
+ if (app2 === void 0 || !app2.hooks.transform.includes(action.type)) {
31565
31720
  continue;
31566
31721
  }
31567
- for (const install of app2.installs) {
31568
- if (install.active) {
31569
- entries.push({ slug, app: app2, install: { id: install.id, ord: install.ord, label: install.label } });
31570
- }
31722
+ const { runs, skipped } = geoRunnableInstalls(slug, app2, location);
31723
+ geoSkipped.push(...skipped);
31724
+ for (const install of runs) {
31725
+ entries.push({ slug, app: app2, install: { id: install.id, ord: install.ord, label: install.label } });
31571
31726
  }
31572
31727
  }
31573
- return entries;
31728
+ return { entries, geoSkipped };
31574
31729
  }, "chainEntries");
31575
31730
  var trackPayloadSchema = external_exports.object({
31576
31731
  event: external_exports.string().min(1),
@@ -31692,7 +31847,8 @@ var buildEffectsRelease = /* @__PURE__ */ __name(({ effects, action, deliveries
31692
31847
  ...deliveries,
31693
31848
  geo: action.geo,
31694
31849
  host: action.host,
31695
- context: action.context
31850
+ context: action.context,
31851
+ ...startedOnSocket({ kind: "event", action }) ? { fromSocket: true } : {}
31696
31852
  });
31697
31853
  }
31698
31854
  };
@@ -31702,15 +31858,22 @@ var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, d
31702
31858
  if (error61 !== void 0) {
31703
31859
  throw new TransformChainError(`the apps registry is malformed: ${error61}`);
31704
31860
  }
31705
- const entries = chainEntries(registry3, action.type);
31861
+ const { entries, geoSkipped } = chainEntries(registry3, action);
31862
+ const attrs = {
31863
+ "transform.entries": entries.length,
31864
+ ...geoSkipped.length === 0 ? {} : { "transform.geo_skipped": geoSkipped.join(",") }
31865
+ };
31706
31866
  if (entries.length === 0) {
31867
+ if (geoSkipped.length > 0) {
31868
+ await traced("apps.transform_chain", { attrs }, () => Promise.resolve());
31869
+ }
31707
31870
  return { dropped: false, action, effects: [] };
31708
31871
  }
31709
31872
  const context = invocationContext(env);
31710
31873
  if (context === void 0) {
31711
31874
  throw new TransformChainError("APPS_KEY_MASTER or QINTO_DOMAIN_ID is missing");
31712
31875
  }
31713
- return traced("apps.transform_chain", { attrs: { "transform.entries": entries.length } }, async () => {
31876
+ return traced("apps.transform_chain", { attrs }, async () => {
31714
31877
  const collected = [];
31715
31878
  let budget = { totalDeadline: Date.now() + TRANSFORM_CHAIN_BUDGET_MS, appDeadline: 0 };
31716
31879
  let current = action;
@@ -31955,14 +32118,14 @@ var allCaptures = /* @__PURE__ */ __name((sql, now, cache) => {
31955
32118
  }, "allCaptures");
31956
32119
 
31957
32120
  // src/apps/capture.ts
31958
- var appWriteAllowed = /* @__PURE__ */ __name((registry3, consent2, slug, enforced) => {
32121
+ var appWriteAllowed = /* @__PURE__ */ __name((registry3, slug, { consent: consent2, enforced, location }) => {
31959
32122
  const app2 = registry3.apps[slug];
31960
32123
  if (app2 === void 0) {
31961
32124
  addSpanEvent("capture dropped: the app is not in the registry", { level: "warn", attrs: { app: slug } });
31962
32125
  return false;
31963
32126
  }
31964
- if (!app2.installs.some((install) => install.active)) {
31965
- addSpanEvent("capture dropped: no active install", { level: "warn", attrs: { app: slug } });
32127
+ if (geoRunnableInstalls(slug, app2, location).runs.length === 0) {
32128
+ addSpanEvent("capture dropped: no install runs for this visitor", { level: "warn", attrs: { app: slug } });
31966
32129
  return false;
31967
32130
  }
31968
32131
  if (!consentAllows(consent2, slug, app2.consent.category, enforced)) {
@@ -31983,11 +32146,11 @@ var declaration = /* @__PURE__ */ __name((app2, key) => {
31983
32146
  ttlMs: declared.expiresInSeconds === void 0 ? null : declared.expiresInSeconds * 1e3
31984
32147
  };
31985
32148
  }, "declaration");
31986
- var acceptedCaptures = /* @__PURE__ */ __name((registry3, consent2, apps, enforced) => {
32149
+ var acceptedCaptures = /* @__PURE__ */ __name((registry3, apps, gate) => {
31987
32150
  const accepted = [];
31988
32151
  for (const [slug, values] of Object.entries(apps)) {
31989
32152
  const app2 = registry3.apps[slug];
31990
- if (app2 === void 0 || !appWriteAllowed(registry3, consent2, slug, enforced)) {
32153
+ if (app2 === void 0 || !appWriteAllowed(registry3, slug, gate)) {
31991
32154
  continue;
31992
32155
  }
31993
32156
  for (const [key, value] of Object.entries(values)) {
@@ -32789,14 +32952,29 @@ var appPushFrame = /* @__PURE__ */ __name(({ push, context, consent: consent2 })
32789
32952
  topic,
32790
32953
  data: data ?? null
32791
32954
  };
32792
- const serialized = JSON.stringify(frame);
32793
32955
  const overhead = scope === "site" ? PUSH_SITE_FRAME_OVERHEAD_BYTES : PUSH_FRAME_OVERHEAD_BYTES;
32794
- if (new TextEncoder().encode(serialized).length > MAX_PUSH_BYTES + overhead) {
32956
+ if (new TextEncoder().encode(JSON.stringify(frame)).length > MAX_PUSH_BYTES + overhead) {
32795
32957
  addSpanEvent("app push dropped over the size cap", { level: "warn", attrs: { app: slug, topic } });
32796
32958
  return null;
32797
32959
  }
32798
- return serialized;
32960
+ return frame;
32799
32961
  }, "appPushFrame");
32962
+ var connectionPushFrame = /* @__PURE__ */ __name((frame, registry3, location) => {
32963
+ const runningApps = /* @__PURE__ */ new Set();
32964
+ const runningInstalls = /* @__PURE__ */ new Set();
32965
+ for (const [slug, app2] of Object.entries(registry3.apps)) {
32966
+ for (const install of geoRunnableInstalls(slug, app2, location).runs) {
32967
+ runningApps.add(slug);
32968
+ runningInstalls.add(install.id);
32969
+ }
32970
+ }
32971
+ if (frame.to !== void 0) {
32972
+ const to = frame.to.filter((slug) => runningApps.has(slug));
32973
+ return to.length === 0 ? null : JSON.stringify({ ...frame, to });
32974
+ }
32975
+ const reaches = frame.install === void 0 ? runningApps.has(frame.app) : runningInstalls.has(frame.install);
32976
+ return reaches ? JSON.stringify(frame) : null;
32977
+ }, "connectionPushFrame");
32800
32978
  var sharedContextFrame = /* @__PURE__ */ __name((context, consent2, stored) => {
32801
32979
  const readable = readableSharedContext(context.registry, consent2, context.consentEnforced, stored);
32802
32980
  return JSON.stringify({ t: "sharedContext", context: sharedContextWire(readable), ...consent2 });
@@ -32994,6 +33172,10 @@ var getSessionFrame = external_exports.object({
32994
33172
  t: external_exports.literal("getSession"),
32995
33173
  ref: external_exports.string().min(1).optional()
32996
33174
  }).strict();
33175
+ var getGeoInstallsFrame = external_exports.object({
33176
+ t: external_exports.literal("getGeoInstalls"),
33177
+ ref: external_exports.string().min(1).optional()
33178
+ }).strict();
32997
33179
  var clientFrame = external_exports.discriminatedUnion("t", [
32998
33180
  identifyFrame,
32999
33181
  trackFrame,
@@ -33001,7 +33183,8 @@ var clientFrame = external_exports.discriminatedUnion("t", [
33001
33183
  consentFrame,
33002
33184
  captureFrame,
33003
33185
  getConsentFrame,
33004
- getSessionFrame
33186
+ getSessionFrame,
33187
+ getGeoInstallsFrame
33005
33188
  ]);
33006
33189
  var pluckRef = /* @__PURE__ */ __name((parsed) => {
33007
33190
  if (typeof parsed !== "object" || parsed === null) {
@@ -33223,12 +33406,28 @@ var AnonDO = class extends DurableObject2 {
33223
33406
  return null;
33224
33407
  }
33225
33408
  if (frame.t === "capture") {
33226
- const { accepted } = await this.capture({ apps: frame.apps }, site);
33409
+ const { accepted } = await this.capture({ apps: frame.apps, location: socketLocation(attachment.cf) }, site);
33227
33410
  this.respond(ws, { t: "captureAck", ref, anonId: userId, accepted });
33228
33411
  return null;
33229
33412
  }
33413
+ if (frame.t === "getGeoInstalls") {
33414
+ await this.answerGeoInstalls(ws, ref, attachment, site);
33415
+ return null;
33416
+ }
33230
33417
  return frame;
33231
33418
  }
33419
+ // The browser holds the page's first events until this answers, so a failure answers at once.
33420
+ async answerGeoInstalls(ws, ref, attachment, site) {
33421
+ let registry3;
33422
+ try {
33423
+ registry3 = (await this.sites.resolve(site, Date.now())).registry;
33424
+ } catch (error61) {
33425
+ recordError(error61, { "site.id": site.domainId });
33426
+ this.respond(ws, { t: "error", ref, message: "geo filter read failed" });
33427
+ return;
33428
+ }
33429
+ this.respond(ws, { t: "geoInstalls", ref, installs: geoAllowedInstalls(registry3, attachment.cf) });
33430
+ }
33232
33431
  admitFrame(ws) {
33233
33432
  let bucket = this.frameBuckets.get(ws);
33234
33433
  if (bucket === void 0) {
@@ -33353,7 +33552,7 @@ var AnonDO = class extends DurableObject2 {
33353
33552
  addSpanEvent("capture dropped: no visitor state", { level: "warn" });
33354
33553
  return { accepted: {} };
33355
33554
  }
33356
- return this.runCaptures(input2.apps, site);
33555
+ return this.runCaptures(input2.apps, site, input2.location);
33357
33556
  });
33358
33557
  });
33359
33558
  }
@@ -33367,7 +33566,7 @@ var AnonDO = class extends DurableObject2 {
33367
33566
  this.cleanupAlarm = await seedCleanupAlarm(this.ctx.storage, now, lastActivityAt(this.kv) ?? now, this.cleanupAlarm);
33368
33567
  seedMeta(this.kv, input2.anonId, now);
33369
33568
  touchActivity(this.kv, now);
33370
- return this.runCaptures(input2.apps, site);
33569
+ return this.runCaptures(input2.apps, site, null);
33371
33570
  });
33372
33571
  });
33373
33572
  }
@@ -33381,7 +33580,11 @@ var AnonDO = class extends DurableObject2 {
33381
33580
  return;
33382
33581
  }
33383
33582
  const context = await this.sites.resolve(site, Date.now());
33384
- if (!appWriteAllowed(context.registry, consentState2(this.kv), input2.slug, context.consentEnforced)) {
33583
+ if (!appWriteAllowed(context.registry, input2.slug, {
33584
+ consent: consentState2(this.kv),
33585
+ enforced: context.consentEnforced,
33586
+ location: null
33587
+ })) {
33385
33588
  return;
33386
33589
  }
33387
33590
  this.storeCaptures(
@@ -33392,9 +33595,9 @@ var AnonDO = class extends DurableObject2 {
33392
33595
  });
33393
33596
  });
33394
33597
  }
33395
- async runCaptures(apps, site) {
33598
+ async runCaptures(apps, site, location) {
33396
33599
  const context = await this.sites.resolve(site, Date.now());
33397
- const writes = acceptedCaptures(context.registry, consentState2(this.kv), apps, context.consentEnforced);
33600
+ const writes = acceptedCaptures(context.registry, apps, { consent: consentState2(this.kv), enforced: context.consentEnforced, location });
33398
33601
  return { accepted: this.storeCaptures(writes) };
33399
33602
  }
33400
33603
  storeCaptures(writes) {
@@ -33423,7 +33626,7 @@ var AnonDO = class extends DurableObject2 {
33423
33626
  }
33424
33627
  const context = await this.sites.resolve(site, Date.now());
33425
33628
  const { registry: registry3 } = context;
33426
- if (!appWriteAllowed(registry3, consentState2(this.kv), input2.slug, context.consentEnforced)) {
33629
+ if (!appWriteAllowed(registry3, input2.slug, { consent: consentState2(this.kv), enforced: context.consentEnforced, location: null })) {
33427
33630
  return [];
33428
33631
  }
33429
33632
  const granted = registry3.apps[input2.slug]?.data.includes(SHARED_CONTEXT_CAPABILITY) === true;
@@ -33557,12 +33760,16 @@ var AnonDO = class extends DurableObject2 {
33557
33760
  // Only the sockets opened on the push's own site: a linked sibling's browser holds no install of
33558
33761
  // the app that produced it.
33559
33762
  deliverAppPush(push, context) {
33560
- const serialized = appPushFrame({ push, context, consent: consentState2(this.kv) });
33561
- if (serialized === null) {
33763
+ const frame = appPushFrame({ push, context, consent: consentState2(this.kv) });
33764
+ if (frame === null) {
33562
33765
  return;
33563
33766
  }
33564
33767
  for (const socket of this.ctx.getWebSockets(context.domainId)) {
33565
- sendFrame(socket, serialized);
33768
+ const { cf } = socket.deserializeAttachment();
33769
+ const serialized = connectionPushFrame(frame, context.registry, socketLocation(cf));
33770
+ if (serialized !== null) {
33771
+ sendFrame(socket, serialized);
33772
+ }
33566
33773
  }
33567
33774
  }
33568
33775
  // A getConsent can arrive before the session is seeded, so the visitor is named only when there
@@ -33715,7 +33922,8 @@ var AnonDO = class extends DurableObject2 {
33715
33922
  }
33716
33923
  appFanOut(trigger, anonId, context) {
33717
33924
  const { registry: registry3, registryError, consentEnforced: consentEnforced2 } = context;
33718
- if (registryError === void 0 && eligibleApps(registry3, trigger, consentState2(this.kv), consentEnforced2).length === 0) {
33925
+ const { targets, geoSkipped } = eligibleApps(registry3, trigger, consentState2(this.kv), consentEnforced2);
33926
+ if (registryError === void 0 && targets.length === 0 && geoSkipped.length === 0) {
33719
33927
  return;
33720
33928
  }
33721
33929
  const job = { trigger, snapshot: this.appsSnapshot(anonId, context), trace: currentTraceContext() };