@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.
- package/README.md +4 -3
- package/dist/index.js +1350 -1351
- package/dist/index.js.map +1 -1
- package/dist/worker-artifact/app-runtime/index.js +3 -2
- package/dist/worker-artifact/main/index.js +1734 -1526
- package/dist/worker-artifact/version.json +6 -6
- package/package.json +1 -1
|
@@ -20604,7 +20604,7 @@ var MAX_APP_TOOL_NAME_LENGTH = 32;
|
|
|
20604
20604
|
var APP_TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
20605
20605
|
|
|
20606
20606
|
// ../../libs/app-manifest/src/tools.ts
|
|
20607
|
-
var MAX_APP_TOOLS =
|
|
20607
|
+
var MAX_APP_TOOLS = 20;
|
|
20608
20608
|
var MAX_APP_TOOL_TITLE_LENGTH = 60;
|
|
20609
20609
|
var MIN_APP_TOOL_DESCRIPTION = 20;
|
|
20610
20610
|
var MAX_APP_TOOL_DESCRIPTION = 16 * 1024;
|
|
@@ -20612,7 +20612,7 @@ var MAX_APP_TOOL_PROPERTY_DESCRIPTION = 200;
|
|
|
20612
20612
|
var MAX_APP_TOOL_PROPERTIES = 24;
|
|
20613
20613
|
var MAX_APP_TOOL_SCHEMA_DEPTH = 4;
|
|
20614
20614
|
var MAX_APP_TOOL_ENUM_VALUES = 32;
|
|
20615
|
-
var MAX_MANIFEST_TOOLS_BYTES =
|
|
20615
|
+
var MAX_MANIFEST_TOOLS_BYTES = 375 * 1024;
|
|
20616
20616
|
var TOOL_PROPERTY_NAME = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
20617
20617
|
var RESERVED_TOOL_ARGUMENTS = ["teamId", "siteId", "installId"];
|
|
20618
20618
|
var TOOL_STRING_FORMATS = ["uuid", "date", "date-time", "email", "uri"];
|
|
@@ -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
|
-
|
|
29360
|
-
|
|
29361
|
-
|
|
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 (
|
|
29374
|
-
return
|
|
29133
|
+
if (envelope.tool !== void 0) {
|
|
29134
|
+
return `tools.${envelope.tool.name}`;
|
|
29375
29135
|
}
|
|
29376
|
-
|
|
29377
|
-
|
|
29378
|
-
|
|
29379
|
-
|
|
29380
|
-
if (
|
|
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
|
-
|
|
29384
|
-
|
|
29385
|
-
|
|
29386
|
-
|
|
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
|
|
29361
|
+
const endpoints = [];
|
|
29389
29362
|
let dropped = 0;
|
|
29390
|
-
for (const
|
|
29391
|
-
if (typeof
|
|
29392
|
-
|
|
29393
|
-
|
|
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
|
-
|
|
29398
|
-
|
|
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
|
-
|
|
29401
|
-
|
|
29402
|
-
|
|
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 {
|
|
29403
|
+
return { tokens: /* @__PURE__ */ new Map() };
|
|
29405
29404
|
}
|
|
29406
|
-
let
|
|
29405
|
+
let parsed;
|
|
29407
29406
|
try {
|
|
29408
|
-
|
|
29407
|
+
parsed = JSON.parse(raw2);
|
|
29409
29408
|
} catch {
|
|
29410
|
-
return {
|
|
29409
|
+
return { tokens: /* @__PURE__ */ new Map(), error: "WORKER_ACCESS_TOKENS is not valid JSON" };
|
|
29411
29410
|
}
|
|
29412
|
-
if (typeof
|
|
29413
|
-
return {
|
|
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
|
|
29416
|
-
|
|
29417
|
-
|
|
29418
|
-
|
|
29419
|
-
|
|
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
|
-
|
|
29422
|
-
|
|
29423
|
-
|
|
29424
|
-
|
|
29425
|
-
|
|
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
|
-
|
|
29428
|
-
|
|
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
|
-
|
|
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.
|
|
29537
|
-
return candidate.
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
31568
|
-
|
|
31569
|
-
|
|
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
|
|
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
|
|
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,
|
|
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 (
|
|
31965
|
-
addSpanEvent("capture dropped: no
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
|
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,
|
|
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),
|
|
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),
|
|
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
|
|
33561
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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() };
|