@blotout/qinto-cli 0.1.17 → 0.1.18
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 +14 -6
- package/dist/index.js +3214 -2950
- package/dist/index.js.map +1 -1
- package/dist/worker-artifact/app-runtime/index.js +100 -50
- package/dist/worker-artifact/main/index.js +839 -538
- package/dist/worker-artifact/version.json +5 -5
- package/package.json +1 -1
|
@@ -743,7 +743,6 @@ var cors = /* @__PURE__ */ __name((options) => {
|
|
|
743
743
|
}, "cors");
|
|
744
744
|
|
|
745
745
|
// src/apps/broker.ts
|
|
746
|
-
import { timingSafeEqual } from "node:crypto";
|
|
747
746
|
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
748
747
|
|
|
749
748
|
// ../../libs/app-deploy/src/control.ts
|
|
@@ -778,12 +777,18 @@ var verifyControlRequestSignature = /* @__PURE__ */ __name(async (key, timestamp
|
|
|
778
777
|
}, "verifyControlRequestSignature");
|
|
779
778
|
|
|
780
779
|
// ../../libs/app-deploy/src/keys.ts
|
|
781
|
-
var
|
|
780
|
+
var hmacHex = /* @__PURE__ */ __name(async (masterBase64, message) => {
|
|
782
781
|
const master = Uint8Array.from(atob(masterBase64), (char) => char.charCodeAt(0));
|
|
783
782
|
const key = await crypto.subtle.importKey("raw", master, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
784
|
-
const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(
|
|
783
|
+
const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message));
|
|
785
784
|
return [...new Uint8Array(signature)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
785
|
+
}, "hmacHex");
|
|
786
|
+
var deriveAppInternalKey = /* @__PURE__ */ __name(async (masterBase64, domainId, slug) => {
|
|
787
|
+
return hmacHex(masterBase64, `${domainId}|${slug}`);
|
|
786
788
|
}, "deriveAppInternalKey");
|
|
789
|
+
var deriveVisitorSignature = /* @__PURE__ */ __name(async (masterBase64, domainId, anonId) => {
|
|
790
|
+
return hmacHex(masterBase64, `${domainId}#visitor#${anonId}`);
|
|
791
|
+
}, "deriveVisitorSignature");
|
|
787
792
|
|
|
788
793
|
// ../../libs/app-manifest/src/cron.ts
|
|
789
794
|
var MAX_CRON_LENGTH = 128;
|
|
@@ -20355,6 +20360,10 @@ var uniqueOrIssue = /* @__PURE__ */ __name((ctx, values, path, label) => {
|
|
|
20355
20360
|
seen.add(value);
|
|
20356
20361
|
}
|
|
20357
20362
|
}, "uniqueOrIssue");
|
|
20363
|
+
var PLATFORM_ZONES = ["blotout.io", "qinto.tech"];
|
|
20364
|
+
var isPlatformHost = /* @__PURE__ */ __name((host) => {
|
|
20365
|
+
return PLATFORM_ZONES.some((zone) => host === zone || host.endsWith(`.${zone}`));
|
|
20366
|
+
}, "isPlatformHost");
|
|
20358
20367
|
|
|
20359
20368
|
// ../../libs/app-manifest/src/oauth.ts
|
|
20360
20369
|
var OAUTH_TOKEN_METHODS = ["POST", "GET"];
|
|
@@ -20384,8 +20393,7 @@ var RESERVED_AUTHORIZE_PARAMS = /* @__PURE__ */ new Set([
|
|
|
20384
20393
|
var PRIVATE_HOST = /^(localhost|\[?::1\]?|.+\.local|.+\.internal|10\..+|127\..+|169\.254\..+|192\.168\..+|172\.(1[6-9]|2\d|3[01])\..+|\d+\.\d+\.\d+\.\d+)$/;
|
|
20385
20394
|
var vendorUrl = /* @__PURE__ */ __name((field) => {
|
|
20386
20395
|
return httpsUrl(field).refine((value) => {
|
|
20387
|
-
|
|
20388
|
-
return host !== "qinto.io" && !host.endsWith(".qinto.io") && host !== "qinto.tech" && !host.endsWith(".qinto.tech");
|
|
20396
|
+
return !isPlatformHost(new URL(value).hostname);
|
|
20389
20397
|
}, `${field} must be the vendor's own endpoint, not a Qinto platform URL`).refine((value) => {
|
|
20390
20398
|
return !PRIVATE_HOST.test(new URL(value).hostname);
|
|
20391
20399
|
}, `${field} must be a public host`).refine((value) => {
|
|
@@ -20845,8 +20853,7 @@ var PLATFORM_APP_SCOPE_VARIABLE_IDENTIFIER_PREFIX = "appvar-";
|
|
|
20845
20853
|
var STORAGE_LINK_BINDING_PREFIX = "QINTO_LINK_";
|
|
20846
20854
|
var legalUrl = /* @__PURE__ */ __name((field) => {
|
|
20847
20855
|
return httpsUrl(field).refine((value) => {
|
|
20848
|
-
|
|
20849
|
-
return host !== "qinto.io" && !host.endsWith(".qinto.io");
|
|
20856
|
+
return !isPlatformHost(new URL(value).hostname);
|
|
20850
20857
|
}, `${field} must be the developer's own document, not a Qinto platform URL`);
|
|
20851
20858
|
}, "legalUrl");
|
|
20852
20859
|
var bindingField = external_exports.string().min(1).max(255).regex(BINDING_NAME);
|
|
@@ -21019,7 +21026,7 @@ var captureSchema = external_exports.object({
|
|
|
21019
21026
|
var endpointSchema = external_exports.object({
|
|
21020
21027
|
path: external_exports.string().min(1).max(255).regex(ENDPOINT_PATH_PATTERN, "Endpoint paths are absolute; each segment is a literal or a :name parameter"),
|
|
21021
21028
|
methods: external_exports.array(external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE"])).min(1).max(5),
|
|
21022
|
-
auth: external_exports.enum(["public", "token"]),
|
|
21029
|
+
auth: external_exports.enum(["public", "token", "visitor"]),
|
|
21023
21030
|
mode: external_exports.enum(["webhook", "api"])
|
|
21024
21031
|
}).strict();
|
|
21025
21032
|
var eventsSchema = external_exports.object({
|
|
@@ -21245,6 +21252,22 @@ var validatePipelines = /* @__PURE__ */ __name((manifest, ctx) => {
|
|
|
21245
21252
|
}
|
|
21246
21253
|
}
|
|
21247
21254
|
}, "validatePipelines");
|
|
21255
|
+
var validateVisitorEndpoint = /* @__PURE__ */ __name((manifest, ctx, endpoint, index) => {
|
|
21256
|
+
if (endpoint.mode !== "api") {
|
|
21257
|
+
ctx.addIssue({
|
|
21258
|
+
code: "custom",
|
|
21259
|
+
path: ["endpoints", index, "mode"],
|
|
21260
|
+
message: "A visitor endpoint answers one visitor, so its mode must be api"
|
|
21261
|
+
});
|
|
21262
|
+
}
|
|
21263
|
+
if (manifest.browser?.entry === void 0) {
|
|
21264
|
+
ctx.addIssue({
|
|
21265
|
+
code: "custom",
|
|
21266
|
+
path: ["endpoints", index, "auth"],
|
|
21267
|
+
message: "A visitor endpoint is called by the app's own browser code, so it needs a browser entry"
|
|
21268
|
+
});
|
|
21269
|
+
}
|
|
21270
|
+
}, "validateVisitorEndpoint");
|
|
21248
21271
|
var validateEndpoints = /* @__PURE__ */ __name((manifest, ctx) => {
|
|
21249
21272
|
const shapes = /* @__PURE__ */ new Map();
|
|
21250
21273
|
for (const [index, endpoint] of manifest.endpoints.entries()) {
|
|
@@ -21270,7 +21293,9 @@ var validateEndpoints = /* @__PURE__ */ __name((manifest, ctx) => {
|
|
|
21270
21293
|
message: `Endpoint path ${endpoint.path} names the parameter ${repeated} twice`
|
|
21271
21294
|
});
|
|
21272
21295
|
}
|
|
21273
|
-
if (endpoint.
|
|
21296
|
+
if (endpoint.auth === "visitor") {
|
|
21297
|
+
validateVisitorEndpoint(manifest, ctx, endpoint, index);
|
|
21298
|
+
} else if (endpoint.mode === "api" && manifest.install.multiple) {
|
|
21274
21299
|
ctx.addIssue({
|
|
21275
21300
|
code: "custom",
|
|
21276
21301
|
path: ["endpoints", index, "mode"],
|
|
@@ -21313,29 +21338,29 @@ var validateCapture = /* @__PURE__ */ __name((manifest, ctx) => {
|
|
|
21313
21338
|
if (eventResults.length > 0 && browser.entry === void 0) {
|
|
21314
21339
|
ctx.addIssue({ code: "custom", path: ["browser", "eventResults"], message: "eventResults needs a browser entry" });
|
|
21315
21340
|
}
|
|
21316
|
-
const
|
|
21317
|
-
if (
|
|
21341
|
+
const capture2 = browser.capture;
|
|
21342
|
+
if (capture2 === void 0) {
|
|
21318
21343
|
return;
|
|
21319
21344
|
}
|
|
21320
|
-
if (
|
|
21345
|
+
if (capture2.queryParams.length === 0 && capture2.cookies.length === 0) {
|
|
21321
21346
|
ctx.addIssue({ code: "custom", path: ["browser", "capture"], message: "capture must name at least one query param or cookie" });
|
|
21322
21347
|
}
|
|
21323
21348
|
uniqueOrIssue(
|
|
21324
21349
|
ctx,
|
|
21325
|
-
|
|
21350
|
+
capture2.queryParams.map((entry) => entry.name),
|
|
21326
21351
|
["browser", "capture", "queryParams"],
|
|
21327
21352
|
"query param"
|
|
21328
21353
|
);
|
|
21329
21354
|
uniqueOrIssue(
|
|
21330
21355
|
ctx,
|
|
21331
|
-
|
|
21356
|
+
capture2.cookies.map((entry) => entry.name),
|
|
21332
21357
|
["browser", "capture", "cookies"],
|
|
21333
21358
|
"cookie"
|
|
21334
21359
|
);
|
|
21335
|
-
validateCaptureNames(ctx,
|
|
21336
|
-
validateCaptureNames(ctx,
|
|
21337
|
-
const params = new Set(
|
|
21338
|
-
for (const [index, entry] of
|
|
21360
|
+
validateCaptureNames(ctx, capture2.queryParams, ["browser", "capture", "queryParams"], false);
|
|
21361
|
+
validateCaptureNames(ctx, capture2.cookies, ["browser", "capture", "cookies"], true);
|
|
21362
|
+
const params = new Set(capture2.queryParams.map((entry) => entry.name));
|
|
21363
|
+
for (const [index, entry] of capture2.cookies.entries()) {
|
|
21339
21364
|
if (params.has(entry.name)) {
|
|
21340
21365
|
ctx.addIssue({
|
|
21341
21366
|
code: "custom",
|
|
@@ -21433,7 +21458,7 @@ var storageLinkBindingName = /* @__PURE__ */ __name((domainId) => {
|
|
|
21433
21458
|
var MAX_APPS_Q_BYTES = 4 * 1024 * 1024;
|
|
21434
21459
|
|
|
21435
21460
|
// ../../libs/app-envelope/src/envelope.ts
|
|
21436
|
-
var ENVELOPE_VERSION =
|
|
21461
|
+
var ENVELOPE_VERSION = 3;
|
|
21437
21462
|
var ENVELOPE_ERROR_CODES = ["invalid_arguments"];
|
|
21438
21463
|
var MAX_INSTALLS_PER_APP = 15;
|
|
21439
21464
|
var MAX_EMITS_PER_INSTALL = 10;
|
|
@@ -21489,12 +21514,12 @@ var actionContextSchema = external_exports.discriminatedUnion("source", [
|
|
|
21489
21514
|
var envelopeRequestSchema = external_exports.object({
|
|
21490
21515
|
v: external_exports.literal(ENVELOPE_VERSION),
|
|
21491
21516
|
kind: external_exports.enum(["event", "connect", "transform", "rpc"]),
|
|
21492
|
-
site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
|
|
21493
21517
|
app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
|
|
21494
21518
|
installs: external_exports.array(installSchema).min(1).max(MAX_INSTALLS_PER_APP),
|
|
21495
21519
|
user: external_exports.object({
|
|
21496
21520
|
anonId: external_exports.string().min(1).max(64),
|
|
21497
21521
|
globalId: external_exports.string().min(1).max(64).nullable(),
|
|
21522
|
+
createdAt: external_exports.number().int().optional(),
|
|
21498
21523
|
properties: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
21499
21524
|
geo: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
21500
21525
|
}).strict().optional(),
|
|
@@ -21579,7 +21604,7 @@ var validateEnvelopeResponse = /* @__PURE__ */ __name((raw2, installCount) => {
|
|
|
21579
21604
|
}, "validateEnvelopeResponse");
|
|
21580
21605
|
|
|
21581
21606
|
// ../../libs/app-envelope/src/control.ts
|
|
21582
|
-
var CONTROL_ENVELOPE_VERSION =
|
|
21607
|
+
var CONTROL_ENVELOPE_VERSION = 2;
|
|
21583
21608
|
var MAX_CONTROL_BYTES = 256 * 1024;
|
|
21584
21609
|
var MAX_MIGRATION_CHECKPOINT_BYTES = 16 * 1024;
|
|
21585
21610
|
var MAX_TOOL_ARGUMENTS_BYTES = 32 * 1024;
|
|
@@ -21688,7 +21713,6 @@ var toolSchema2 = external_exports.object({
|
|
|
21688
21713
|
var controlEnvelopeSchema = external_exports.object({
|
|
21689
21714
|
v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
|
|
21690
21715
|
kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule", "tool"]),
|
|
21691
|
-
site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
|
|
21692
21716
|
app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
|
|
21693
21717
|
install: external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict().optional(),
|
|
21694
21718
|
lifecycle: lifecycleEventSchema.optional(),
|
|
@@ -21762,6 +21786,7 @@ var controlResponseSchema = external_exports.object({
|
|
|
21762
21786
|
var ENDPOINT_CONTEXT_HEADER = "x-qinto-endpoint-context";
|
|
21763
21787
|
var ENDPOINT_OUTCOMES_HEADER = "x-qinto-endpoint-outcomes";
|
|
21764
21788
|
var MAX_ENDPOINT_BODY_BYTES = 1024 * 1024;
|
|
21789
|
+
var MAX_VISITOR_ENDPOINT_BODY_BYTES = 2 * 1024 * 1024;
|
|
21765
21790
|
var MAX_ENDPOINT_OUTCOMES_BYTES = 64 * 1024;
|
|
21766
21791
|
var MAX_ENDPOINT_PATH_LENGTH = 16 * 1024;
|
|
21767
21792
|
var installSchema3 = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict();
|
|
@@ -21777,12 +21802,14 @@ var endpointContextSchema = external_exports.object({
|
|
|
21777
21802
|
method: external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
21778
21803
|
mode: external_exports.enum(["webhook", "api"]),
|
|
21779
21804
|
version: external_exports.string().min(1).max(32),
|
|
21780
|
-
site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
|
|
21781
21805
|
geo: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
21782
|
-
installs: external_exports.array(installSchema3).min(1).max(15)
|
|
21806
|
+
installs: external_exports.array(installSchema3).min(1).max(15),
|
|
21807
|
+
user: external_exports.object({ anonId: external_exports.string().min(1).max(64) }).strict().optional()
|
|
21783
21808
|
}).strict().refine((context) => {
|
|
21784
21809
|
return context.mode === "webhook" || context.installs.length === 1;
|
|
21785
|
-
}, "An api endpoint carries exactly one install")
|
|
21810
|
+
}, "An api endpoint carries exactly one install").refine((context) => {
|
|
21811
|
+
return context.user === void 0 || context.mode === "api";
|
|
21812
|
+
}, "Only an api endpoint carries a visitor");
|
|
21786
21813
|
var endpointOutcomesSchema = external_exports.object({
|
|
21787
21814
|
outcomes: external_exports.array(
|
|
21788
21815
|
external_exports.object({
|
|
@@ -25098,14 +25125,14 @@ var OpenAPIGenerator = class {
|
|
|
25098
25125
|
this.sortDefinitions();
|
|
25099
25126
|
}
|
|
25100
25127
|
generateDocumentData() {
|
|
25101
|
-
this.definitions.forEach((
|
|
25128
|
+
this.definitions.forEach((definition11) => this.generateSingle(definition11));
|
|
25102
25129
|
return {
|
|
25103
25130
|
components: this.buildComponents(),
|
|
25104
25131
|
paths: this.pathRefs
|
|
25105
25132
|
};
|
|
25106
25133
|
}
|
|
25107
25134
|
generateComponents() {
|
|
25108
|
-
this.definitions.forEach((
|
|
25135
|
+
this.definitions.forEach((definition11) => this.generateSingle(definition11));
|
|
25109
25136
|
return {
|
|
25110
25137
|
components: this.buildComponents()
|
|
25111
25138
|
};
|
|
@@ -25156,23 +25183,23 @@ var OpenAPIGenerator = class {
|
|
|
25156
25183
|
return leftIndex - rightIndex;
|
|
25157
25184
|
});
|
|
25158
25185
|
}
|
|
25159
|
-
generateSingle(
|
|
25160
|
-
if (!("type" in
|
|
25161
|
-
this.generateSchemaWithRef(
|
|
25186
|
+
generateSingle(definition11) {
|
|
25187
|
+
if (!("type" in definition11)) {
|
|
25188
|
+
this.generateSchemaWithRef(definition11);
|
|
25162
25189
|
return;
|
|
25163
25190
|
}
|
|
25164
|
-
switch (
|
|
25191
|
+
switch (definition11.type) {
|
|
25165
25192
|
case "parameter":
|
|
25166
|
-
this.generateParameterDefinition(
|
|
25193
|
+
this.generateParameterDefinition(definition11.schema);
|
|
25167
25194
|
return;
|
|
25168
25195
|
case "schema":
|
|
25169
|
-
this.generateSchemaWithRef(
|
|
25196
|
+
this.generateSchemaWithRef(definition11.schema);
|
|
25170
25197
|
return;
|
|
25171
25198
|
case "route":
|
|
25172
|
-
this.generateSingleRoute(
|
|
25199
|
+
this.generateSingleRoute(definition11.route);
|
|
25173
25200
|
return;
|
|
25174
25201
|
case "component":
|
|
25175
|
-
this.rawComponents.push(
|
|
25202
|
+
this.rawComponents.push(definition11);
|
|
25176
25203
|
return;
|
|
25177
25204
|
}
|
|
25178
25205
|
}
|
|
@@ -25617,8 +25644,8 @@ var OpenApiGeneratorV31Specifics = class {
|
|
|
25617
25644
|
}));
|
|
25618
25645
|
}
|
|
25619
25646
|
};
|
|
25620
|
-
function isWebhookDefinition$1(
|
|
25621
|
-
return "type" in
|
|
25647
|
+
function isWebhookDefinition$1(definition11) {
|
|
25648
|
+
return "type" in definition11 && definition11.type === "webhook";
|
|
25622
25649
|
}
|
|
25623
25650
|
__name(isWebhookDefinition$1, "isWebhookDefinition$1");
|
|
25624
25651
|
var OpenApiGeneratorV31 = class {
|
|
@@ -25641,7 +25668,7 @@ var OpenApiGeneratorV31 = class {
|
|
|
25641
25668
|
}
|
|
25642
25669
|
generateWebhooks() {
|
|
25643
25670
|
this.webhookRefs = {};
|
|
25644
|
-
this.definitions.filter(isWebhookDefinition$1).forEach((
|
|
25671
|
+
this.definitions.filter(isWebhookDefinition$1).forEach((definition11) => this.generateSingleWebhook(definition11.webhook));
|
|
25645
25672
|
}
|
|
25646
25673
|
generateSingleWebhook(route) {
|
|
25647
25674
|
const routeDoc = this.generator.generatePath(route);
|
|
@@ -26234,7 +26261,7 @@ var isGlobalId = /* @__PURE__ */ __name((id) => {
|
|
|
26234
26261
|
|
|
26235
26262
|
// src/common/validation.ts
|
|
26236
26263
|
var identifierField2 = external_exports.string().trim().min(1).refine((value) => !isGlobalId(value), { message: "gid_\u2026 ids are internal; pass an anon_\u2026 id or your own identifier" }).openapi({
|
|
26237
|
-
description: "Who the request applies to: a canonical Qinto id (`anon_\u2026`), or any identifier from your own systems (email, phone, custom id). Internal `gid_\u2026` ids are rejected.",
|
|
26264
|
+
description: "Who the request applies to: a canonical Qinto id (`anon_\u2026`, or a `tag_user_id` adopted from EdgeTag), or any identifier from your own systems (email, phone, custom id). Internal `gid_\u2026` ids are rejected.",
|
|
26238
26265
|
example: "crm-user-8412"
|
|
26239
26266
|
});
|
|
26240
26267
|
var consentCategoriesField = external_exports.object({
|
|
@@ -26264,6 +26291,12 @@ var consentAppsField = external_exports.record(
|
|
|
26264
26291
|
external_exports.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9_-]*$/),
|
|
26265
26292
|
external_exports.boolean()
|
|
26266
26293
|
);
|
|
26294
|
+
var captureAppsField = external_exports.record(external_exports.string().max(30), external_exports.record(external_exports.string().max(64), external_exports.string().max(512))).refine((apps) => {
|
|
26295
|
+
return Object.keys(apps).length <= 50 && Object.values(apps).every((values) => Object.keys(values).length <= 32);
|
|
26296
|
+
}, "capture carries more apps or keys than a site can have").openapi({
|
|
26297
|
+
description: "Values to record, keyed by app slug and then by capture name. A name the app does not declare is dropped.",
|
|
26298
|
+
example: { facebook: { fbclid: "IwAR0abc" }, "google-ads": { gclid: "Cj0KCQ" } }
|
|
26299
|
+
});
|
|
26267
26300
|
var serverActionContextField = external_exports.object({
|
|
26268
26301
|
occurredAt: external_exports.number().int().nonnegative().safe().optional().openapi({
|
|
26269
26302
|
description: "When the visitor action occurred, as Unix milliseconds; defaults to Qinto receipt time."
|
|
@@ -26293,6 +26326,14 @@ var acceptedResponse = external_exports.object({
|
|
|
26293
26326
|
}).openapi("AcceptedAction", { description: "Confirmation that the action was accepted." });
|
|
26294
26327
|
var respond = /* @__PURE__ */ __name(((c, schema, data, status) => c.json(schema.parse(data), status ?? 200)), "respond");
|
|
26295
26328
|
|
|
26329
|
+
// src/helpers/constant-time.ts
|
|
26330
|
+
import { timingSafeEqual } from "node:crypto";
|
|
26331
|
+
var constantTimeEqual = /* @__PURE__ */ __name((expected, presented) => {
|
|
26332
|
+
const expectedBytes = new TextEncoder().encode(expected);
|
|
26333
|
+
const presentedBytes = new TextEncoder().encode(presented);
|
|
26334
|
+
return expectedBytes.length === presentedBytes.length && timingSafeEqual(expectedBytes, presentedBytes);
|
|
26335
|
+
}, "constantTimeEqual");
|
|
26336
|
+
|
|
26296
26337
|
// src/apps/consent-gate.ts
|
|
26297
26338
|
var parseEnforced = /* @__PURE__ */ __name((raw2) => {
|
|
26298
26339
|
let parsed;
|
|
@@ -26692,19 +26733,22 @@ var claimPair = /* @__PURE__ */ __name(async (env, identifier, globalId, anonId)
|
|
|
26692
26733
|
}, "claimPair");
|
|
26693
26734
|
|
|
26694
26735
|
// src/id-graph/user.ts
|
|
26736
|
+
var EDGETAG_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-[0-9]{13}$/;
|
|
26737
|
+
var isEdgeTagId = /* @__PURE__ */ __name((id) => {
|
|
26738
|
+
return EDGETAG_ID.test(id);
|
|
26739
|
+
}, "isEdgeTagId");
|
|
26695
26740
|
var isCanonicalId = /* @__PURE__ */ __name((id) => {
|
|
26696
|
-
return id.startsWith("anon_");
|
|
26741
|
+
return id.startsWith("anon_") || isEdgeTagId(id);
|
|
26697
26742
|
}, "isCanonicalId");
|
|
26698
26743
|
var generateUserId = /* @__PURE__ */ __name(() => {
|
|
26699
26744
|
return `anon_${crypto.randomUUID()}`;
|
|
26700
26745
|
}, "generateUserId");
|
|
26701
|
-
var restoreUser = /* @__PURE__ */ __name(async (env, cookie) => {
|
|
26702
|
-
const minted = generateUserId();
|
|
26746
|
+
var restoreUser = /* @__PURE__ */ __name(async (env, cookie, candidate) => {
|
|
26703
26747
|
if (cookie === void 0) {
|
|
26704
|
-
return
|
|
26748
|
+
return candidate;
|
|
26705
26749
|
}
|
|
26706
|
-
const assignment = await claimFallbackAnon(env, cookie,
|
|
26707
|
-
setSpanAttributes({ "qinto.fallback_restored": assignment.anonId !==
|
|
26750
|
+
const assignment = await claimFallbackAnon(env, cookie, candidate);
|
|
26751
|
+
setSpanAttributes({ "qinto.fallback_restored": assignment.anonId !== candidate });
|
|
26708
26752
|
return assignment.anonId;
|
|
26709
26753
|
}, "restoreUser");
|
|
26710
26754
|
var resolveUser = /* @__PURE__ */ __name(async (env, id) => {
|
|
@@ -27497,11 +27541,11 @@ var APP_HOOK_TIMEOUT_MS = 25e3;
|
|
|
27497
27541
|
var MAX_EMIT_DEPTH = 2;
|
|
27498
27542
|
var HOOK_URL = "https://app/__qinto/hook";
|
|
27499
27543
|
var invocationContext = /* @__PURE__ */ __name((env) => {
|
|
27500
|
-
const { APPS_KEY_MASTER: master, QINTO_DOMAIN_ID: domainId
|
|
27544
|
+
const { APPS_KEY_MASTER: master, QINTO_DOMAIN_ID: domainId } = env;
|
|
27501
27545
|
if (master === void 0 || domainId === void 0) {
|
|
27502
27546
|
return void 0;
|
|
27503
27547
|
}
|
|
27504
|
-
return { master, domainId,
|
|
27548
|
+
return { master, domainId, consentEnforced: consentEnforced(env) };
|
|
27505
27549
|
}, "invocationContext");
|
|
27506
27550
|
var postHookEnvelope = /* @__PURE__ */ __name(async (env, { binding, key, envelope, timeoutMs }) => {
|
|
27507
27551
|
const service = env[binding];
|
|
@@ -27567,12 +27611,12 @@ var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context)
|
|
|
27567
27611
|
return {
|
|
27568
27612
|
v: ENVELOPE_VERSION,
|
|
27569
27613
|
kind: trigger.kind,
|
|
27570
|
-
site: { origin: context.origin },
|
|
27571
27614
|
app: { slug: target.slug, version: target.app.version },
|
|
27572
27615
|
installs: target.installs,
|
|
27573
27616
|
user: {
|
|
27574
27617
|
anonId: snapshot.anonId,
|
|
27575
27618
|
globalId: snapshot.globalId,
|
|
27619
|
+
...snapshot.createdAt === null ? {} : { createdAt: snapshot.createdAt },
|
|
27576
27620
|
// Custom properties intentionally win on key collisions with PII, matching the profile read path.
|
|
27577
27621
|
properties: {
|
|
27578
27622
|
...buckets.includes("pii") ? snapshot.pii : {},
|
|
@@ -27784,12 +27828,12 @@ var invokeApp = /* @__PURE__ */ __name(async (env, input2) => {
|
|
|
27784
27828
|
|
|
27785
27829
|
// src/apps/rate-limit.ts
|
|
27786
27830
|
var limiterAbsenceLogged = false;
|
|
27787
|
-
var rateLimited = /* @__PURE__ */ __name(async (env, key) => {
|
|
27788
|
-
const limiter = env
|
|
27831
|
+
var rateLimited = /* @__PURE__ */ __name(async (env, key, binding = "BROKER_RATE_LIMITER") => {
|
|
27832
|
+
const limiter = env[binding];
|
|
27789
27833
|
if (limiter === void 0) {
|
|
27790
27834
|
if (!limiterAbsenceLogged) {
|
|
27791
27835
|
limiterAbsenceLogged = true;
|
|
27792
|
-
addSpanEvent("
|
|
27836
|
+
addSpanEvent("rate limiter binding is absent; limits are off", { level: "warn", attrs: { binding } });
|
|
27793
27837
|
}
|
|
27794
27838
|
return false;
|
|
27795
27839
|
}
|
|
@@ -27887,11 +27931,6 @@ var parseJson = /* @__PURE__ */ __name((raw2) => {
|
|
|
27887
27931
|
return null;
|
|
27888
27932
|
}
|
|
27889
27933
|
}, "parseJson");
|
|
27890
|
-
var constantTimeEqual = /* @__PURE__ */ __name((expected, presented) => {
|
|
27891
|
-
const expectedBytes = new TextEncoder().encode(expected);
|
|
27892
|
-
const presentedBytes = new TextEncoder().encode(presented);
|
|
27893
|
-
return expectedBytes.length === presentedBytes.length && timingSafeEqual(expectedBytes, presentedBytes);
|
|
27894
|
-
}, "constantTimeEqual");
|
|
27895
27934
|
var factsCursor = external_exports.object({
|
|
27896
27935
|
userId: external_exports.string().min(1).max(64),
|
|
27897
27936
|
kinds: external_exports.array(external_exports.enum(APP_FACT_KINDS)).min(1).max(APP_FACT_KINDS.length),
|
|
@@ -28022,7 +28061,6 @@ var runRelayRpc = /* @__PURE__ */ __name(async (env, { context, body, callee, in
|
|
|
28022
28061
|
envelope: {
|
|
28023
28062
|
v: ENVELOPE_VERSION,
|
|
28024
28063
|
kind: "rpc",
|
|
28025
|
-
site: { origin: context.origin },
|
|
28026
28064
|
app: { slug: body.callee, version: callee.version },
|
|
28027
28065
|
installs: [install],
|
|
28028
28066
|
// No bucket projection for rpc: the callee sees only the subject's id, never its profile.
|
|
@@ -28526,7 +28564,8 @@ var SCOPE = {
|
|
|
28526
28564
|
propertiesRead: 16,
|
|
28527
28565
|
consentRead: 32,
|
|
28528
28566
|
sessionRead: 64,
|
|
28529
|
-
idGraphInspect: 128
|
|
28567
|
+
idGraphInspect: 128,
|
|
28568
|
+
capture: 256
|
|
28530
28569
|
};
|
|
28531
28570
|
var ALL_SCOPE_MASK = Object.values(SCOPE).reduce((mask, bit) => mask | bit, 0);
|
|
28532
28571
|
var WORKER_ACCESS_TOKEN_SCOPES = [
|
|
@@ -28556,6 +28595,12 @@ var WORKER_ACCESS_TOKEN_SCOPES = [
|
|
|
28556
28595
|
bit: SCOPE.sessionRead,
|
|
28557
28596
|
label: "Read session ids",
|
|
28558
28597
|
description: "GET /v1/session, GET /v1/person \u2014 read the anonymous and person ids for a user, and the sessions linked to a person."
|
|
28598
|
+
},
|
|
28599
|
+
{
|
|
28600
|
+
key: "capture",
|
|
28601
|
+
bit: SCOPE.capture,
|
|
28602
|
+
label: "Write captures",
|
|
28603
|
+
description: "POST /v1/capture \u2014 record click ids and vendor ids an installed app declares."
|
|
28559
28604
|
}
|
|
28560
28605
|
];
|
|
28561
28606
|
|
|
@@ -28781,18 +28826,498 @@ var streamWithDeadlines = /* @__PURE__ */ __name((source, { idleMs, totalMs }) =
|
|
|
28781
28826
|
});
|
|
28782
28827
|
}, "streamWithDeadlines");
|
|
28783
28828
|
|
|
28829
|
+
// ../../libs/bot-protection/src/countries.ts
|
|
28830
|
+
var EU_TOKEN = "EU";
|
|
28831
|
+
var COUNTRY_CODES = [
|
|
28832
|
+
"AD",
|
|
28833
|
+
"AE",
|
|
28834
|
+
"AF",
|
|
28835
|
+
"AG",
|
|
28836
|
+
"AI",
|
|
28837
|
+
"AL",
|
|
28838
|
+
"AM",
|
|
28839
|
+
"AO",
|
|
28840
|
+
"AQ",
|
|
28841
|
+
"AR",
|
|
28842
|
+
"AS",
|
|
28843
|
+
"AT",
|
|
28844
|
+
"AU",
|
|
28845
|
+
"AW",
|
|
28846
|
+
"AX",
|
|
28847
|
+
"AZ",
|
|
28848
|
+
"BA",
|
|
28849
|
+
"BB",
|
|
28850
|
+
"BD",
|
|
28851
|
+
"BE",
|
|
28852
|
+
"BF",
|
|
28853
|
+
"BG",
|
|
28854
|
+
"BH",
|
|
28855
|
+
"BI",
|
|
28856
|
+
"BJ",
|
|
28857
|
+
"BL",
|
|
28858
|
+
"BM",
|
|
28859
|
+
"BN",
|
|
28860
|
+
"BO",
|
|
28861
|
+
"BQ",
|
|
28862
|
+
"BR",
|
|
28863
|
+
"BS",
|
|
28864
|
+
"BT",
|
|
28865
|
+
"BV",
|
|
28866
|
+
"BW",
|
|
28867
|
+
"BY",
|
|
28868
|
+
"BZ",
|
|
28869
|
+
"CA",
|
|
28870
|
+
"CC",
|
|
28871
|
+
"CD",
|
|
28872
|
+
"CF",
|
|
28873
|
+
"CG",
|
|
28874
|
+
"CH",
|
|
28875
|
+
"CI",
|
|
28876
|
+
"CK",
|
|
28877
|
+
"CL",
|
|
28878
|
+
"CM",
|
|
28879
|
+
"CN",
|
|
28880
|
+
"CO",
|
|
28881
|
+
"CR",
|
|
28882
|
+
"CU",
|
|
28883
|
+
"CV",
|
|
28884
|
+
"CW",
|
|
28885
|
+
"CX",
|
|
28886
|
+
"CY",
|
|
28887
|
+
"CZ",
|
|
28888
|
+
"DE",
|
|
28889
|
+
"DJ",
|
|
28890
|
+
"DK",
|
|
28891
|
+
"DM",
|
|
28892
|
+
"DO",
|
|
28893
|
+
"DZ",
|
|
28894
|
+
"EC",
|
|
28895
|
+
"EE",
|
|
28896
|
+
"EG",
|
|
28897
|
+
"EH",
|
|
28898
|
+
"ER",
|
|
28899
|
+
"ES",
|
|
28900
|
+
"ET",
|
|
28901
|
+
"FI",
|
|
28902
|
+
"FJ",
|
|
28903
|
+
"FK",
|
|
28904
|
+
"FM",
|
|
28905
|
+
"FO",
|
|
28906
|
+
"FR",
|
|
28907
|
+
"GA",
|
|
28908
|
+
"GB",
|
|
28909
|
+
"GD",
|
|
28910
|
+
"GE",
|
|
28911
|
+
"GF",
|
|
28912
|
+
"GG",
|
|
28913
|
+
"GH",
|
|
28914
|
+
"GI",
|
|
28915
|
+
"GL",
|
|
28916
|
+
"GM",
|
|
28917
|
+
"GN",
|
|
28918
|
+
"GP",
|
|
28919
|
+
"GQ",
|
|
28920
|
+
"GR",
|
|
28921
|
+
"GS",
|
|
28922
|
+
"GT",
|
|
28923
|
+
"GU",
|
|
28924
|
+
"GW",
|
|
28925
|
+
"GY",
|
|
28926
|
+
"HK",
|
|
28927
|
+
"HM",
|
|
28928
|
+
"HN",
|
|
28929
|
+
"HR",
|
|
28930
|
+
"HT",
|
|
28931
|
+
"HU",
|
|
28932
|
+
"ID",
|
|
28933
|
+
"IE",
|
|
28934
|
+
"IL",
|
|
28935
|
+
"IM",
|
|
28936
|
+
"IN",
|
|
28937
|
+
"IO",
|
|
28938
|
+
"IQ",
|
|
28939
|
+
"IR",
|
|
28940
|
+
"IS",
|
|
28941
|
+
"IT",
|
|
28942
|
+
"JE",
|
|
28943
|
+
"JM",
|
|
28944
|
+
"JO",
|
|
28945
|
+
"JP",
|
|
28946
|
+
"KE",
|
|
28947
|
+
"KG",
|
|
28948
|
+
"KH",
|
|
28949
|
+
"KI",
|
|
28950
|
+
"KM",
|
|
28951
|
+
"KN",
|
|
28952
|
+
"KP",
|
|
28953
|
+
"KR",
|
|
28954
|
+
"KW",
|
|
28955
|
+
"KY",
|
|
28956
|
+
"KZ",
|
|
28957
|
+
"LA",
|
|
28958
|
+
"LB",
|
|
28959
|
+
"LC",
|
|
28960
|
+
"LI",
|
|
28961
|
+
"LK",
|
|
28962
|
+
"LR",
|
|
28963
|
+
"LS",
|
|
28964
|
+
"LT",
|
|
28965
|
+
"LU",
|
|
28966
|
+
"LV",
|
|
28967
|
+
"LY",
|
|
28968
|
+
"MA",
|
|
28969
|
+
"MC",
|
|
28970
|
+
"MD",
|
|
28971
|
+
"ME",
|
|
28972
|
+
"MF",
|
|
28973
|
+
"MG",
|
|
28974
|
+
"MH",
|
|
28975
|
+
"MK",
|
|
28976
|
+
"ML",
|
|
28977
|
+
"MM",
|
|
28978
|
+
"MN",
|
|
28979
|
+
"MO",
|
|
28980
|
+
"MP",
|
|
28981
|
+
"MQ",
|
|
28982
|
+
"MR",
|
|
28983
|
+
"MS",
|
|
28984
|
+
"MT",
|
|
28985
|
+
"MU",
|
|
28986
|
+
"MV",
|
|
28987
|
+
"MW",
|
|
28988
|
+
"MX",
|
|
28989
|
+
"MY",
|
|
28990
|
+
"MZ",
|
|
28991
|
+
"NA",
|
|
28992
|
+
"NC",
|
|
28993
|
+
"NE",
|
|
28994
|
+
"NF",
|
|
28995
|
+
"NG",
|
|
28996
|
+
"NI",
|
|
28997
|
+
"NL",
|
|
28998
|
+
"NO",
|
|
28999
|
+
"NP",
|
|
29000
|
+
"NR",
|
|
29001
|
+
"NU",
|
|
29002
|
+
"NZ",
|
|
29003
|
+
"OM",
|
|
29004
|
+
"PA",
|
|
29005
|
+
"PE",
|
|
29006
|
+
"PF",
|
|
29007
|
+
"PG",
|
|
29008
|
+
"PH",
|
|
29009
|
+
"PK",
|
|
29010
|
+
"PL",
|
|
29011
|
+
"PM",
|
|
29012
|
+
"PN",
|
|
29013
|
+
"PR",
|
|
29014
|
+
"PS",
|
|
29015
|
+
"PT",
|
|
29016
|
+
"PW",
|
|
29017
|
+
"PY",
|
|
29018
|
+
"QA",
|
|
29019
|
+
"RE",
|
|
29020
|
+
"RO",
|
|
29021
|
+
"RS",
|
|
29022
|
+
"RU",
|
|
29023
|
+
"RW",
|
|
29024
|
+
"SA",
|
|
29025
|
+
"SB",
|
|
29026
|
+
"SC",
|
|
29027
|
+
"SD",
|
|
29028
|
+
"SE",
|
|
29029
|
+
"SG",
|
|
29030
|
+
"SH",
|
|
29031
|
+
"SI",
|
|
29032
|
+
"SJ",
|
|
29033
|
+
"SK",
|
|
29034
|
+
"SL",
|
|
29035
|
+
"SM",
|
|
29036
|
+
"SN",
|
|
29037
|
+
"SO",
|
|
29038
|
+
"SR",
|
|
29039
|
+
"SS",
|
|
29040
|
+
"ST",
|
|
29041
|
+
"SV",
|
|
29042
|
+
"SX",
|
|
29043
|
+
"SY",
|
|
29044
|
+
"SZ",
|
|
29045
|
+
"TC",
|
|
29046
|
+
"TD",
|
|
29047
|
+
"TF",
|
|
29048
|
+
"TG",
|
|
29049
|
+
"TH",
|
|
29050
|
+
"TJ",
|
|
29051
|
+
"TK",
|
|
29052
|
+
"TL",
|
|
29053
|
+
"TM",
|
|
29054
|
+
"TN",
|
|
29055
|
+
"TO",
|
|
29056
|
+
"TR",
|
|
29057
|
+
"TT",
|
|
29058
|
+
"TV",
|
|
29059
|
+
"TW",
|
|
29060
|
+
"TZ",
|
|
29061
|
+
"UA",
|
|
29062
|
+
"UG",
|
|
29063
|
+
"UM",
|
|
29064
|
+
"US",
|
|
29065
|
+
"UY",
|
|
29066
|
+
"UZ",
|
|
29067
|
+
"VA",
|
|
29068
|
+
"VC",
|
|
29069
|
+
"VE",
|
|
29070
|
+
"VG",
|
|
29071
|
+
"VI",
|
|
29072
|
+
"VN",
|
|
29073
|
+
"VU",
|
|
29074
|
+
"WF",
|
|
29075
|
+
"WS",
|
|
29076
|
+
"YE",
|
|
29077
|
+
"YT",
|
|
29078
|
+
"ZA",
|
|
29079
|
+
"ZM",
|
|
29080
|
+
"ZW"
|
|
29081
|
+
];
|
|
29082
|
+
var SELECTABLE_TOKENS = /* @__PURE__ */ new Set([EU_TOKEN, ...COUNTRY_CODES]);
|
|
29083
|
+
var isSelectableCountryToken = /* @__PURE__ */ __name((token) => {
|
|
29084
|
+
return SELECTABLE_TOKENS.has(token);
|
|
29085
|
+
}, "isSelectableCountryToken");
|
|
29086
|
+
var matchesRule = /* @__PURE__ */ __name((country, isEU, list) => {
|
|
29087
|
+
if (country !== void 0 && list.includes(country)) {
|
|
29088
|
+
return true;
|
|
29089
|
+
}
|
|
29090
|
+
return isEU && list.includes(EU_TOKEN);
|
|
29091
|
+
}, "matchesRule");
|
|
29092
|
+
var isCountryBlocked = /* @__PURE__ */ __name((country, isEU, rule) => {
|
|
29093
|
+
if (rule === null || rule.list.length === 0) {
|
|
29094
|
+
return false;
|
|
29095
|
+
}
|
|
29096
|
+
const matched = matchesRule(country, isEU, rule.list);
|
|
29097
|
+
return rule.mode === "block" ? matched : !matched;
|
|
29098
|
+
}, "isCountryBlocked");
|
|
29099
|
+
|
|
29100
|
+
// ../../libs/bot-protection/src/score.ts
|
|
29101
|
+
var BOT_SCORE_MIN = 1;
|
|
29102
|
+
var BOT_SCORE_MAX = 99;
|
|
29103
|
+
|
|
29104
|
+
// ../../libs/bot-protection/src/config.ts
|
|
29105
|
+
var NO_PROTECTION = { minBotScore: null, countries: null };
|
|
29106
|
+
var normalizeScore = /* @__PURE__ */ __name((value, problems) => {
|
|
29107
|
+
if (value === void 0 || value === null) {
|
|
29108
|
+
return null;
|
|
29109
|
+
}
|
|
29110
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= BOT_SCORE_MIN && value <= BOT_SCORE_MAX) {
|
|
29111
|
+
return value;
|
|
29112
|
+
}
|
|
29113
|
+
problems.push("has an out-of-range minBotScore");
|
|
29114
|
+
return null;
|
|
29115
|
+
}, "normalizeScore");
|
|
29116
|
+
var normalizeCountries = /* @__PURE__ */ __name((value, problems) => {
|
|
29117
|
+
if (value === void 0 || value === null) {
|
|
29118
|
+
return null;
|
|
29119
|
+
}
|
|
29120
|
+
const record2 = value;
|
|
29121
|
+
if (record2.mode !== "allow" && record2.mode !== "block" || !Array.isArray(record2.list)) {
|
|
29122
|
+
problems.push("has an invalid countries rule");
|
|
29123
|
+
return null;
|
|
29124
|
+
}
|
|
29125
|
+
const list = [];
|
|
29126
|
+
let dropped = 0;
|
|
29127
|
+
for (const token of record2.list) {
|
|
29128
|
+
if (typeof token === "string" && isSelectableCountryToken(token)) {
|
|
29129
|
+
list.push(token);
|
|
29130
|
+
} else {
|
|
29131
|
+
dropped += 1;
|
|
29132
|
+
}
|
|
29133
|
+
}
|
|
29134
|
+
if (dropped > 0) {
|
|
29135
|
+
problems.push(`dropped ${dropped} invalid country token(s)`);
|
|
29136
|
+
}
|
|
29137
|
+
return list.length === 0 ? null : { mode: record2.mode, list };
|
|
29138
|
+
}, "normalizeCountries");
|
|
29139
|
+
var parseBotProtection = /* @__PURE__ */ __name((raw2) => {
|
|
29140
|
+
if (raw2.trim() === "") {
|
|
29141
|
+
return { config: NO_PROTECTION };
|
|
29142
|
+
}
|
|
29143
|
+
let value;
|
|
29144
|
+
try {
|
|
29145
|
+
value = JSON.parse(raw2);
|
|
29146
|
+
} catch {
|
|
29147
|
+
return { config: NO_PROTECTION, error: "BOT_PROTECTION is not valid JSON" };
|
|
29148
|
+
}
|
|
29149
|
+
if (typeof value !== "object" || value === null) {
|
|
29150
|
+
return { config: NO_PROTECTION, error: "BOT_PROTECTION is not an object" };
|
|
29151
|
+
}
|
|
29152
|
+
const record2 = value;
|
|
29153
|
+
const problems = [];
|
|
29154
|
+
const config2 = {
|
|
29155
|
+
minBotScore: normalizeScore(record2.minBotScore, problems),
|
|
29156
|
+
countries: normalizeCountries(record2.countries, problems)
|
|
29157
|
+
};
|
|
29158
|
+
return problems.length > 0 ? { config: config2, error: `BOT_PROTECTION ${problems.join("; ")}` } : { config: config2 };
|
|
29159
|
+
}, "parseBotProtection");
|
|
29160
|
+
var isRequestBlocked = /* @__PURE__ */ __name((config2, signal) => {
|
|
29161
|
+
if (config2.minBotScore !== null && signal.botScore !== void 0 && signal.botScore < config2.minBotScore) {
|
|
29162
|
+
return true;
|
|
29163
|
+
}
|
|
29164
|
+
return isCountryBlocked(signal.country, signal.isEU ?? false, config2.countries);
|
|
29165
|
+
}, "isRequestBlocked");
|
|
29166
|
+
|
|
29167
|
+
// src/helpers/bot-check.ts
|
|
29168
|
+
var botRejected = /* @__PURE__ */ __name((env, geo) => {
|
|
29169
|
+
return traced("bot.check", {}, () => {
|
|
29170
|
+
const { config: protection, error: error61 } = parseBotProtection(env.BOT_PROTECTION);
|
|
29171
|
+
if (error61 !== void 0) {
|
|
29172
|
+
addSpanEvent("BOT_PROTECTION is misconfigured; enforcing it with the invalid parts dropped", {
|
|
29173
|
+
level: "error",
|
|
29174
|
+
attrs: { error: String(error61) }
|
|
29175
|
+
});
|
|
29176
|
+
}
|
|
29177
|
+
const rejected = isRequestBlocked(protection, { botScore: geo.botScore, country: geo.country, isEU: geo.isEUCountry === "yes" });
|
|
29178
|
+
setSpanAttributes({ "bot.blocked": rejected, ...geo.botScore === void 0 ? {} : { "bot.score": geo.botScore } });
|
|
29179
|
+
return rejected;
|
|
29180
|
+
});
|
|
29181
|
+
}, "botRejected");
|
|
29182
|
+
|
|
29183
|
+
// src/helpers/cookie.ts
|
|
29184
|
+
var COOKIE_NAME2 = "q_user_id";
|
|
29185
|
+
var EDGETAG_COOKIE_NAME = "tag_user_id";
|
|
29186
|
+
var COOKIE_MAX_AGE = 400 * 24 * 60 * 60;
|
|
29187
|
+
var SESSION_COOKIE_NAME = "q_session";
|
|
29188
|
+
var SESSION_MAX_AGE = 30 * 60;
|
|
29189
|
+
var SESSION_VALUE = /^(vis_[0-9a-f-]{36})\.(\d{1,15})$/;
|
|
29190
|
+
var FALLBACK_MIN_LENGTH = 4;
|
|
29191
|
+
var fallbackCookie = /* @__PURE__ */ __name((c) => {
|
|
29192
|
+
const name = c.env.FALLBACK_COOKIE;
|
|
29193
|
+
if (name === "") {
|
|
29194
|
+
return void 0;
|
|
29195
|
+
}
|
|
29196
|
+
const value = getCookie(c, name);
|
|
29197
|
+
if (value === void 0 || value.length < FALLBACK_MIN_LENGTH) {
|
|
29198
|
+
return void 0;
|
|
29199
|
+
}
|
|
29200
|
+
return { name, value };
|
|
29201
|
+
}, "fallbackCookie");
|
|
29202
|
+
var edgeTagUserId = /* @__PURE__ */ __name((c) => {
|
|
29203
|
+
const value = getCookie(c, EDGETAG_COOKIE_NAME);
|
|
29204
|
+
return value !== void 0 && isEdgeTagId(value) ? value : void 0;
|
|
29205
|
+
}, "edgeTagUserId");
|
|
29206
|
+
var visitSession = /* @__PURE__ */ __name((c, now) => {
|
|
29207
|
+
const match2 = SESSION_VALUE.exec(getCookie(c, SESSION_COOKIE_NAME) ?? "");
|
|
29208
|
+
if (match2 === null) {
|
|
29209
|
+
return { id: `vis_${crypto.randomUUID()}`, isNew: true, startedAt: now };
|
|
29210
|
+
}
|
|
29211
|
+
return { id: match2[1], isNew: false, startedAt: Number(match2[2]) };
|
|
29212
|
+
}, "visitSession");
|
|
29213
|
+
|
|
29214
|
+
// src/helpers/visitor-signature.ts
|
|
29215
|
+
var VISITOR_SIGNATURE_COOKIE = "q_user_sig";
|
|
29216
|
+
var signVisitor = /* @__PURE__ */ __name(async (env, anonId) => {
|
|
29217
|
+
const context = invocationContext(env);
|
|
29218
|
+
if (context === void 0) {
|
|
29219
|
+
return void 0;
|
|
29220
|
+
}
|
|
29221
|
+
return deriveVisitorSignature(context.master, context.domainId, anonId);
|
|
29222
|
+
}, "signVisitor");
|
|
29223
|
+
var verifyVisitor = /* @__PURE__ */ __name(async (env, anonId, signature) => {
|
|
29224
|
+
const expected = await signVisitor(env, anonId);
|
|
29225
|
+
return expected !== void 0 && constantTimeEqual(expected, signature);
|
|
29226
|
+
}, "verifyVisitor");
|
|
29227
|
+
|
|
29228
|
+
// src/apps/visitor-endpoint.ts
|
|
29229
|
+
var VISITOR_INSTALL_HEADER = "x-qinto-install";
|
|
29230
|
+
var refuse = /* @__PURE__ */ __name((status, error61) => {
|
|
29231
|
+
return Response.json({ error: error61 }, { status });
|
|
29232
|
+
}, "refuse");
|
|
29233
|
+
var originAllowed = /* @__PURE__ */ __name((request, env) => {
|
|
29234
|
+
const origin = request.headers.get("origin");
|
|
29235
|
+
if (origin === null) {
|
|
29236
|
+
return true;
|
|
29237
|
+
}
|
|
29238
|
+
let host;
|
|
29239
|
+
try {
|
|
29240
|
+
host = new URL(origin).host;
|
|
29241
|
+
} catch {
|
|
29242
|
+
return false;
|
|
29243
|
+
}
|
|
29244
|
+
if (host === new URL(request.url).host) {
|
|
29245
|
+
return true;
|
|
29246
|
+
}
|
|
29247
|
+
const root = env.ROOT_DOMAIN;
|
|
29248
|
+
return root !== void 0 && root !== "" && (host === root || host.endsWith(`.${root}`));
|
|
29249
|
+
}, "originAllowed");
|
|
29250
|
+
var withCors = /* @__PURE__ */ __name((response, request, method) => {
|
|
29251
|
+
const origin = request.headers.get("origin");
|
|
29252
|
+
if (origin === null) {
|
|
29253
|
+
return response;
|
|
29254
|
+
}
|
|
29255
|
+
const headers = new Headers(response.headers);
|
|
29256
|
+
headers.set("access-control-allow-origin", origin);
|
|
29257
|
+
headers.set("access-control-allow-credentials", "true");
|
|
29258
|
+
headers.set("access-control-allow-methods", method);
|
|
29259
|
+
headers.set("access-control-allow-headers", `content-type, ${VISITOR_INSTALL_HEADER}`);
|
|
29260
|
+
headers.append("vary", "Origin");
|
|
29261
|
+
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
29262
|
+
}, "withCors");
|
|
29263
|
+
var authorize = /* @__PURE__ */ __name(async (c, { slug, app: app2 }) => {
|
|
29264
|
+
if (await botRejected(c.env, geoFromRequest(c.req.raw))) {
|
|
29265
|
+
return refuse(403, "traffic_rejected");
|
|
29266
|
+
}
|
|
29267
|
+
const anonId = getCookie(c, COOKIE_NAME2);
|
|
29268
|
+
const signature = getCookie(c, VISITOR_SIGNATURE_COOKIE);
|
|
29269
|
+
if (anonId === void 0 || signature === void 0 || !await verifyVisitor(c.env, anonId, signature)) {
|
|
29270
|
+
return refuse(401, "visitor_required");
|
|
29271
|
+
}
|
|
29272
|
+
const installId = c.req.header(VISITOR_INSTALL_HEADER);
|
|
29273
|
+
const install = app2.installs.find((candidate) => {
|
|
29274
|
+
return candidate.active && candidate.id === installId;
|
|
29275
|
+
});
|
|
29276
|
+
if (install === void 0) {
|
|
29277
|
+
return refuse(403, "forbidden");
|
|
29278
|
+
}
|
|
29279
|
+
if (await rateLimited(c.env, `${slug}:${anonId}:visitor`, "VISITOR_RATE_LIMITER")) {
|
|
29280
|
+
return refuse(429, "rate_limited");
|
|
29281
|
+
}
|
|
29282
|
+
if (!await anonStub(c.env, anonId).appAllowed(slug, ownSite(c.env), currentTraceContext())) {
|
|
29283
|
+
return refuse(403, "forbidden");
|
|
29284
|
+
}
|
|
29285
|
+
return { anonId, install };
|
|
29286
|
+
}, "authorize");
|
|
29287
|
+
var serve = /* @__PURE__ */ __name(async (c, request, forward) => {
|
|
29288
|
+
const visitor = await authorize(c, request);
|
|
29289
|
+
if (visitor instanceof Response) {
|
|
29290
|
+
return visitor;
|
|
29291
|
+
}
|
|
29292
|
+
if (request.method === "GET") {
|
|
29293
|
+
return forward(visitor, void 0);
|
|
29294
|
+
}
|
|
29295
|
+
const body = await readBoundedBody(c.req.raw, MAX_VISITOR_ENDPOINT_BODY_BYTES);
|
|
29296
|
+
if (body === null) {
|
|
29297
|
+
return refuse(413, "too_large");
|
|
29298
|
+
}
|
|
29299
|
+
return forward(visitor, body);
|
|
29300
|
+
}, "serve");
|
|
29301
|
+
var handleVisitorEndpoint = /* @__PURE__ */ __name(async (c, request, forward) => {
|
|
29302
|
+
if (!originAllowed(c.req.raw, c.env)) {
|
|
29303
|
+
return refuse(403, "forbidden");
|
|
29304
|
+
}
|
|
29305
|
+
const response = request.preflight ? new Response(null, { status: 204 }) : await serve(c, request, forward);
|
|
29306
|
+
return withCors(response, c.req.raw, request.method);
|
|
29307
|
+
}, "handleVisitorEndpoint");
|
|
29308
|
+
|
|
28784
29309
|
// src/apps/endpoint-proxy.ts
|
|
28785
29310
|
var API_ENDPOINT_START_TIMEOUT_MS = 6e4;
|
|
28786
29311
|
var WEBHOOK_ENDPOINT_TIMEOUT_MS = 6e4;
|
|
28787
29312
|
var API_ENDPOINT_STREAM_IDLE_TIMEOUT_MS = 15e4;
|
|
28788
29313
|
var API_ENDPOINT_STREAM_MAX_DURATION_MS = 3e5;
|
|
28789
29314
|
var DENIED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "x-cf-data"]);
|
|
28790
|
-
var
|
|
29315
|
+
var refuse2 = /* @__PURE__ */ __name((status, error61) => {
|
|
28791
29316
|
return Response.json({ error: error61 }, { status });
|
|
28792
29317
|
}, "refuse");
|
|
28793
29318
|
var upstreamFailed = /* @__PURE__ */ __name((slug, reason) => {
|
|
28794
29319
|
addSpanEvent("app endpoint upstream failed", { level: "warn", attrs: { app: slug, reason } });
|
|
28795
|
-
return
|
|
29320
|
+
return refuse2(502, "upstream");
|
|
28796
29321
|
}, "upstreamFailed");
|
|
28797
29322
|
var isHttpMethod = /* @__PURE__ */ __name((value) => {
|
|
28798
29323
|
return HTTP_METHODS.includes(value);
|
|
@@ -28814,10 +29339,17 @@ var matchEndpoint = /* @__PURE__ */ __name((app2, path, method) => {
|
|
|
28814
29339
|
const declared = serving.find((endpoint) => {
|
|
28815
29340
|
return endpoint.path === matched.route;
|
|
28816
29341
|
});
|
|
28817
|
-
if (
|
|
29342
|
+
if (!["public", "token", "visitor"].includes(declared.auth) || declared.mode !== "webhook" && declared.mode !== "api") {
|
|
28818
29343
|
return void 0;
|
|
28819
29344
|
}
|
|
28820
|
-
return {
|
|
29345
|
+
return {
|
|
29346
|
+
route: declared.path,
|
|
29347
|
+
path,
|
|
29348
|
+
params: matched.params,
|
|
29349
|
+
method,
|
|
29350
|
+
auth: declared.auth,
|
|
29351
|
+
mode: declared.mode
|
|
29352
|
+
};
|
|
28821
29353
|
}, "matchEndpoint");
|
|
28822
29354
|
var authorizeEndpoint = /* @__PURE__ */ __name(async (c, slug, endpoint) => {
|
|
28823
29355
|
if (endpoint.auth === "public") {
|
|
@@ -28825,14 +29357,14 @@ var authorizeEndpoint = /* @__PURE__ */ __name(async (c, slug, endpoint) => {
|
|
|
28825
29357
|
}
|
|
28826
29358
|
const token = readBearerToken(c.req.raw);
|
|
28827
29359
|
if (token === null) {
|
|
28828
|
-
return
|
|
29360
|
+
return refuse2(401, "unauthorized");
|
|
28829
29361
|
}
|
|
28830
29362
|
const entry = await workerAccessTokenEntry(c.env, token);
|
|
28831
29363
|
if (entry === void 0) {
|
|
28832
|
-
return
|
|
29364
|
+
return refuse2(401, "unauthorized");
|
|
28833
29365
|
}
|
|
28834
29366
|
if (!matchesAppEndpoint(entry.endpoints ?? [], `/app/${slug}${endpoint.path}`, endpoint.method)) {
|
|
28835
|
-
return
|
|
29367
|
+
return refuse2(403, "forbidden");
|
|
28836
29368
|
}
|
|
28837
29369
|
return void 0;
|
|
28838
29370
|
}, "authorizeEndpoint");
|
|
@@ -28885,9 +29417,9 @@ var webhookAck = /* @__PURE__ */ __name(async (slug, upstream) => {
|
|
|
28885
29417
|
return Response.json({ ok: true });
|
|
28886
29418
|
}
|
|
28887
29419
|
if (status === 404) {
|
|
28888
|
-
return
|
|
29420
|
+
return refuse2(404, "not_handled");
|
|
28889
29421
|
}
|
|
28890
|
-
return
|
|
29422
|
+
return refuse2(502, "upstream");
|
|
28891
29423
|
}, "webhookAck");
|
|
28892
29424
|
var fetchApiResponse = /* @__PURE__ */ __name(async (service, url2, init) => {
|
|
28893
29425
|
const controller = new AbortController();
|
|
@@ -28901,7 +29433,7 @@ var fetchApiResponse = /* @__PURE__ */ __name(async (service, url2, init) => {
|
|
|
28901
29433
|
}
|
|
28902
29434
|
}, "fetchApiResponse");
|
|
28903
29435
|
var runForwardToApp = /* @__PURE__ */ __name(async (c, input2) => {
|
|
28904
|
-
const { slug, app: app2, endpoint, installs, body, search } = input2;
|
|
29436
|
+
const { slug, app: app2, endpoint, installs, body, search, anonId } = input2;
|
|
28905
29437
|
const context = invocationContext(c.env);
|
|
28906
29438
|
if (context === void 0) {
|
|
28907
29439
|
return upstreamFailed(slug, "The deploy carries no apps key master.");
|
|
@@ -28926,9 +29458,9 @@ var runForwardToApp = /* @__PURE__ */ __name(async (c, input2) => {
|
|
|
28926
29458
|
method: endpoint.method,
|
|
28927
29459
|
mode: endpoint.mode,
|
|
28928
29460
|
version: app2.version,
|
|
28929
|
-
site: { origin: context.origin },
|
|
28930
29461
|
...app2.data.includes("geo") && requestGeo !== void 0 ? { geo: requestGeo } : {},
|
|
28931
|
-
installs: installs.map((install) => ({ id: install.id, ord: install.ord, label: install.label }))
|
|
29462
|
+
installs: installs.map((install) => ({ id: install.id, ord: install.ord, label: install.label })),
|
|
29463
|
+
...anonId === void 0 ? {} : { user: { anonId } }
|
|
28932
29464
|
})
|
|
28933
29465
|
);
|
|
28934
29466
|
let upstream;
|
|
@@ -28967,20 +29499,8 @@ var forwardToApp = /* @__PURE__ */ __name(async (c, input2) => {
|
|
|
28967
29499
|
}
|
|
28968
29500
|
);
|
|
28969
29501
|
}, "forwardToApp");
|
|
28970
|
-
var
|
|
28971
|
-
const app2 =
|
|
28972
|
-
if (app2 === void 0) {
|
|
28973
|
-
return refuse(404, "not_found");
|
|
28974
|
-
}
|
|
28975
|
-
const url2 = new URL(c.req.url);
|
|
28976
|
-
const prefix = `/app/${slug}`;
|
|
28977
|
-
if (url2.pathname !== prefix && !url2.pathname.startsWith(`${prefix}/`)) {
|
|
28978
|
-
return refuse(404, "not_found");
|
|
28979
|
-
}
|
|
28980
|
-
const endpoint = matchEndpoint(app2, url2.pathname.slice(prefix.length) || "/", c.req.method);
|
|
28981
|
-
if (endpoint === void 0) {
|
|
28982
|
-
return refuse(404, "not_found");
|
|
28983
|
-
}
|
|
29502
|
+
var serveEndpoint = /* @__PURE__ */ __name(async (c, input2) => {
|
|
29503
|
+
const { slug, app: app2, endpoint, search } = input2;
|
|
28984
29504
|
const refusal = await authorizeEndpoint(c, slug, endpoint);
|
|
28985
29505
|
if (refusal !== void 0) {
|
|
28986
29506
|
return refusal;
|
|
@@ -28988,21 +29508,44 @@ var handleAppEndpoint = /* @__PURE__ */ __name(async (c, slug) => {
|
|
|
28988
29508
|
const installs = app2.installs.filter((install) => install.active);
|
|
28989
29509
|
const [first] = installs;
|
|
28990
29510
|
if (first === void 0) {
|
|
28991
|
-
return
|
|
29511
|
+
return refuse2(503, "inactive");
|
|
28992
29512
|
}
|
|
28993
29513
|
const key = endpoint.mode === "api" ? `${slug}:${first.id}:endpoint` : `${slug}:${endpoint.route}:endpoint`;
|
|
28994
29514
|
if (await rateLimited(c.env, key)) {
|
|
28995
|
-
return
|
|
29515
|
+
return refuse2(429, "rate_limited");
|
|
28996
29516
|
}
|
|
28997
29517
|
let body;
|
|
28998
29518
|
if (endpoint.method !== "GET") {
|
|
28999
29519
|
const bytes = await readBoundedBody(c.req.raw, MAX_ENDPOINT_BODY_BYTES);
|
|
29000
29520
|
if (bytes === null) {
|
|
29001
|
-
return
|
|
29521
|
+
return refuse2(413, "too_large");
|
|
29002
29522
|
}
|
|
29003
29523
|
body = bytes;
|
|
29004
29524
|
}
|
|
29005
|
-
return forwardToApp(c, { slug, app: app2, endpoint, installs, body, search
|
|
29525
|
+
return forwardToApp(c, { slug, app: app2, endpoint, installs, body, search });
|
|
29526
|
+
}, "serveEndpoint");
|
|
29527
|
+
var handleAppEndpoint = /* @__PURE__ */ __name(async (c, slug) => {
|
|
29528
|
+
const app2 = siteRegistry().registry.apps[slug];
|
|
29529
|
+
if (app2 === void 0) {
|
|
29530
|
+
return refuse2(404, "not_found");
|
|
29531
|
+
}
|
|
29532
|
+
const url2 = new URL(c.req.url);
|
|
29533
|
+
const prefix = `/app/${slug}`;
|
|
29534
|
+
if (url2.pathname !== prefix && !url2.pathname.startsWith(`${prefix}/`)) {
|
|
29535
|
+
return refuse2(404, "not_found");
|
|
29536
|
+
}
|
|
29537
|
+
const preflight2 = c.req.method === "OPTIONS";
|
|
29538
|
+
const method = preflight2 ? c.req.header("access-control-request-method") : c.req.method;
|
|
29539
|
+
const endpoint = matchEndpoint(app2, url2.pathname.slice(prefix.length) || "/", method ?? "");
|
|
29540
|
+
if (endpoint === void 0 || preflight2 && endpoint.auth !== "visitor") {
|
|
29541
|
+
return refuse2(404, "not_found");
|
|
29542
|
+
}
|
|
29543
|
+
if (endpoint.auth === "visitor") {
|
|
29544
|
+
return handleVisitorEndpoint(c, { slug, app: app2, method: endpoint.method, preflight: preflight2 }, (visitor, body) => {
|
|
29545
|
+
return forwardToApp(c, { slug, app: app2, endpoint, installs: [visitor.install], body, search: url2.search, anonId: visitor.anonId });
|
|
29546
|
+
});
|
|
29547
|
+
}
|
|
29548
|
+
return serveEndpoint(c, { slug, app: app2, endpoint, search: url2.search });
|
|
29006
29549
|
}, "handleAppEndpoint");
|
|
29007
29550
|
var appEndpointRoutes = new Hono2();
|
|
29008
29551
|
appEndpointRoutes.all("/app/:slug/*", (c) => {
|
|
@@ -29204,12 +29747,12 @@ var mintAppUiWriteTicket = /* @__PURE__ */ __name(async (input2) => {
|
|
|
29204
29747
|
|
|
29205
29748
|
// src/apps/ui-proxy.ts
|
|
29206
29749
|
var APP_UI_TIMEOUT_MS = 6e4;
|
|
29207
|
-
var
|
|
29750
|
+
var refuse3 = /* @__PURE__ */ __name((origin, status, error61) => {
|
|
29208
29751
|
return Response.json({ error: error61 }, { status, headers: { "access-control-allow-origin": origin } });
|
|
29209
29752
|
}, "refuse");
|
|
29210
29753
|
var upstreamFailed2 = /* @__PURE__ */ __name((origin, slug, reason) => {
|
|
29211
29754
|
addSpanEvent("app ui upstream failed", { level: "warn", attrs: { app: slug, reason } });
|
|
29212
|
-
return
|
|
29755
|
+
return refuse3(origin, 502, "upstream");
|
|
29213
29756
|
}, "upstreamFailed");
|
|
29214
29757
|
var preflight = /* @__PURE__ */ __name((origin) => {
|
|
29215
29758
|
return new Response(null, {
|
|
@@ -29310,30 +29853,30 @@ var forwardToApp2 = /* @__PURE__ */ __name(async (c, input2) => {
|
|
|
29310
29853
|
var authorizeUi = /* @__PURE__ */ __name(async (c, slug, origin) => {
|
|
29311
29854
|
const app2 = siteRegistry().registry.apps[slug];
|
|
29312
29855
|
if (app2 === void 0) {
|
|
29313
|
-
return
|
|
29856
|
+
return refuse3(origin, 404, "not_found");
|
|
29314
29857
|
}
|
|
29315
29858
|
const authorization = c.req.header("authorization");
|
|
29316
29859
|
const token = authorization?.startsWith("Bearer ") === true ? authorization.slice("Bearer ".length) : void 0;
|
|
29317
29860
|
const keyEnv = c.env.APP_UI_KEY;
|
|
29318
29861
|
const keys = keyEnv === void 0 ? null : parseAppUiKeyEnv(keyEnv);
|
|
29319
29862
|
if (token === void 0 || keys === null) {
|
|
29320
|
-
return
|
|
29863
|
+
return refuse3(origin, 401, "unauthorized");
|
|
29321
29864
|
}
|
|
29322
29865
|
const verdict = await verifyAppUiToken({ token, keys, slug, now: /* @__PURE__ */ new Date() });
|
|
29323
29866
|
if (!verdict.ok) {
|
|
29324
|
-
return verdict.reason === "wrong_app" ?
|
|
29867
|
+
return verdict.reason === "wrong_app" ? refuse3(origin, 403, "forbidden") : refuse3(origin, 401, "unauthorized");
|
|
29325
29868
|
}
|
|
29326
29869
|
if (verdict.claims.appVersionId !== app2.versionId) {
|
|
29327
|
-
return
|
|
29870
|
+
return refuse3(origin, 401, "token_stale");
|
|
29328
29871
|
}
|
|
29329
29872
|
const install = app2.installs.find((entry) => (entry.enabled ?? entry.active) && entry.id === verdict.claims.appInstallId);
|
|
29330
29873
|
if (install === void 0) {
|
|
29331
|
-
return
|
|
29874
|
+
return refuse3(origin, 401, "token_stale");
|
|
29332
29875
|
}
|
|
29333
29876
|
const method = c.req.method;
|
|
29334
29877
|
const writing = method !== "GET" && method !== "HEAD";
|
|
29335
29878
|
if (writing && verdict.claims.access !== "write") {
|
|
29336
|
-
return
|
|
29879
|
+
return refuse3(origin, 403, "forbidden");
|
|
29337
29880
|
}
|
|
29338
29881
|
const writeTicket = writing ? await mintAppUiWriteTicket({
|
|
29339
29882
|
kid: verdict.kid,
|
|
@@ -29361,13 +29904,13 @@ var handleAppUi = /* @__PURE__ */ __name(async (c, slug) => {
|
|
|
29361
29904
|
return authorized;
|
|
29362
29905
|
}
|
|
29363
29906
|
if (await rateLimited(c.env, `${slug}:${authorized.install.id}:ui`)) {
|
|
29364
|
-
return
|
|
29907
|
+
return refuse3(origin, 429, "rate_limited");
|
|
29365
29908
|
}
|
|
29366
29909
|
let body;
|
|
29367
29910
|
if (c.req.method !== "GET" && c.req.method !== "HEAD") {
|
|
29368
29911
|
const bytes = await readBoundedBody(c.req.raw, MAX_UI_BODY_BYTES);
|
|
29369
29912
|
if (bytes === null) {
|
|
29370
|
-
return
|
|
29913
|
+
return refuse3(origin, 413, "too_large");
|
|
29371
29914
|
}
|
|
29372
29915
|
body = bytes;
|
|
29373
29916
|
}
|
|
@@ -29433,7 +29976,7 @@ var ERRORS = {
|
|
|
29433
29976
|
},
|
|
29434
29977
|
unknown_user: {
|
|
29435
29978
|
status: 404,
|
|
29436
|
-
description: "The `identifier` is one of your own that this site has never seen, and
|
|
29979
|
+
description: "The `identifier` is one of your own that this site has never seen, and this operation never creates a user from one. Send a canonical `anon_\u2026` id, or bind the identifier first with an identify or alias call."
|
|
29437
29980
|
},
|
|
29438
29981
|
forbidden: {
|
|
29439
29982
|
status: 403,
|
|
@@ -29718,365 +30261,21 @@ idGraphInspect.post("/internal/id-graph/rows", async (c) => {
|
|
|
29718
30261
|
return c.json({ page: await inspectObjectTable(c.env, body) });
|
|
29719
30262
|
});
|
|
29720
30263
|
|
|
29721
|
-
// ../../libs/bot-protection/src/countries.ts
|
|
29722
|
-
var EU_TOKEN = "EU";
|
|
29723
|
-
var COUNTRY_CODES = [
|
|
29724
|
-
"AD",
|
|
29725
|
-
"AE",
|
|
29726
|
-
"AF",
|
|
29727
|
-
"AG",
|
|
29728
|
-
"AI",
|
|
29729
|
-
"AL",
|
|
29730
|
-
"AM",
|
|
29731
|
-
"AO",
|
|
29732
|
-
"AQ",
|
|
29733
|
-
"AR",
|
|
29734
|
-
"AS",
|
|
29735
|
-
"AT",
|
|
29736
|
-
"AU",
|
|
29737
|
-
"AW",
|
|
29738
|
-
"AX",
|
|
29739
|
-
"AZ",
|
|
29740
|
-
"BA",
|
|
29741
|
-
"BB",
|
|
29742
|
-
"BD",
|
|
29743
|
-
"BE",
|
|
29744
|
-
"BF",
|
|
29745
|
-
"BG",
|
|
29746
|
-
"BH",
|
|
29747
|
-
"BI",
|
|
29748
|
-
"BJ",
|
|
29749
|
-
"BL",
|
|
29750
|
-
"BM",
|
|
29751
|
-
"BN",
|
|
29752
|
-
"BO",
|
|
29753
|
-
"BQ",
|
|
29754
|
-
"BR",
|
|
29755
|
-
"BS",
|
|
29756
|
-
"BT",
|
|
29757
|
-
"BV",
|
|
29758
|
-
"BW",
|
|
29759
|
-
"BY",
|
|
29760
|
-
"BZ",
|
|
29761
|
-
"CA",
|
|
29762
|
-
"CC",
|
|
29763
|
-
"CD",
|
|
29764
|
-
"CF",
|
|
29765
|
-
"CG",
|
|
29766
|
-
"CH",
|
|
29767
|
-
"CI",
|
|
29768
|
-
"CK",
|
|
29769
|
-
"CL",
|
|
29770
|
-
"CM",
|
|
29771
|
-
"CN",
|
|
29772
|
-
"CO",
|
|
29773
|
-
"CR",
|
|
29774
|
-
"CU",
|
|
29775
|
-
"CV",
|
|
29776
|
-
"CW",
|
|
29777
|
-
"CX",
|
|
29778
|
-
"CY",
|
|
29779
|
-
"CZ",
|
|
29780
|
-
"DE",
|
|
29781
|
-
"DJ",
|
|
29782
|
-
"DK",
|
|
29783
|
-
"DM",
|
|
29784
|
-
"DO",
|
|
29785
|
-
"DZ",
|
|
29786
|
-
"EC",
|
|
29787
|
-
"EE",
|
|
29788
|
-
"EG",
|
|
29789
|
-
"EH",
|
|
29790
|
-
"ER",
|
|
29791
|
-
"ES",
|
|
29792
|
-
"ET",
|
|
29793
|
-
"FI",
|
|
29794
|
-
"FJ",
|
|
29795
|
-
"FK",
|
|
29796
|
-
"FM",
|
|
29797
|
-
"FO",
|
|
29798
|
-
"FR",
|
|
29799
|
-
"GA",
|
|
29800
|
-
"GB",
|
|
29801
|
-
"GD",
|
|
29802
|
-
"GE",
|
|
29803
|
-
"GF",
|
|
29804
|
-
"GG",
|
|
29805
|
-
"GH",
|
|
29806
|
-
"GI",
|
|
29807
|
-
"GL",
|
|
29808
|
-
"GM",
|
|
29809
|
-
"GN",
|
|
29810
|
-
"GP",
|
|
29811
|
-
"GQ",
|
|
29812
|
-
"GR",
|
|
29813
|
-
"GS",
|
|
29814
|
-
"GT",
|
|
29815
|
-
"GU",
|
|
29816
|
-
"GW",
|
|
29817
|
-
"GY",
|
|
29818
|
-
"HK",
|
|
29819
|
-
"HM",
|
|
29820
|
-
"HN",
|
|
29821
|
-
"HR",
|
|
29822
|
-
"HT",
|
|
29823
|
-
"HU",
|
|
29824
|
-
"ID",
|
|
29825
|
-
"IE",
|
|
29826
|
-
"IL",
|
|
29827
|
-
"IM",
|
|
29828
|
-
"IN",
|
|
29829
|
-
"IO",
|
|
29830
|
-
"IQ",
|
|
29831
|
-
"IR",
|
|
29832
|
-
"IS",
|
|
29833
|
-
"IT",
|
|
29834
|
-
"JE",
|
|
29835
|
-
"JM",
|
|
29836
|
-
"JO",
|
|
29837
|
-
"JP",
|
|
29838
|
-
"KE",
|
|
29839
|
-
"KG",
|
|
29840
|
-
"KH",
|
|
29841
|
-
"KI",
|
|
29842
|
-
"KM",
|
|
29843
|
-
"KN",
|
|
29844
|
-
"KP",
|
|
29845
|
-
"KR",
|
|
29846
|
-
"KW",
|
|
29847
|
-
"KY",
|
|
29848
|
-
"KZ",
|
|
29849
|
-
"LA",
|
|
29850
|
-
"LB",
|
|
29851
|
-
"LC",
|
|
29852
|
-
"LI",
|
|
29853
|
-
"LK",
|
|
29854
|
-
"LR",
|
|
29855
|
-
"LS",
|
|
29856
|
-
"LT",
|
|
29857
|
-
"LU",
|
|
29858
|
-
"LV",
|
|
29859
|
-
"LY",
|
|
29860
|
-
"MA",
|
|
29861
|
-
"MC",
|
|
29862
|
-
"MD",
|
|
29863
|
-
"ME",
|
|
29864
|
-
"MF",
|
|
29865
|
-
"MG",
|
|
29866
|
-
"MH",
|
|
29867
|
-
"MK",
|
|
29868
|
-
"ML",
|
|
29869
|
-
"MM",
|
|
29870
|
-
"MN",
|
|
29871
|
-
"MO",
|
|
29872
|
-
"MP",
|
|
29873
|
-
"MQ",
|
|
29874
|
-
"MR",
|
|
29875
|
-
"MS",
|
|
29876
|
-
"MT",
|
|
29877
|
-
"MU",
|
|
29878
|
-
"MV",
|
|
29879
|
-
"MW",
|
|
29880
|
-
"MX",
|
|
29881
|
-
"MY",
|
|
29882
|
-
"MZ",
|
|
29883
|
-
"NA",
|
|
29884
|
-
"NC",
|
|
29885
|
-
"NE",
|
|
29886
|
-
"NF",
|
|
29887
|
-
"NG",
|
|
29888
|
-
"NI",
|
|
29889
|
-
"NL",
|
|
29890
|
-
"NO",
|
|
29891
|
-
"NP",
|
|
29892
|
-
"NR",
|
|
29893
|
-
"NU",
|
|
29894
|
-
"NZ",
|
|
29895
|
-
"OM",
|
|
29896
|
-
"PA",
|
|
29897
|
-
"PE",
|
|
29898
|
-
"PF",
|
|
29899
|
-
"PG",
|
|
29900
|
-
"PH",
|
|
29901
|
-
"PK",
|
|
29902
|
-
"PL",
|
|
29903
|
-
"PM",
|
|
29904
|
-
"PN",
|
|
29905
|
-
"PR",
|
|
29906
|
-
"PS",
|
|
29907
|
-
"PT",
|
|
29908
|
-
"PW",
|
|
29909
|
-
"PY",
|
|
29910
|
-
"QA",
|
|
29911
|
-
"RE",
|
|
29912
|
-
"RO",
|
|
29913
|
-
"RS",
|
|
29914
|
-
"RU",
|
|
29915
|
-
"RW",
|
|
29916
|
-
"SA",
|
|
29917
|
-
"SB",
|
|
29918
|
-
"SC",
|
|
29919
|
-
"SD",
|
|
29920
|
-
"SE",
|
|
29921
|
-
"SG",
|
|
29922
|
-
"SH",
|
|
29923
|
-
"SI",
|
|
29924
|
-
"SJ",
|
|
29925
|
-
"SK",
|
|
29926
|
-
"SL",
|
|
29927
|
-
"SM",
|
|
29928
|
-
"SN",
|
|
29929
|
-
"SO",
|
|
29930
|
-
"SR",
|
|
29931
|
-
"SS",
|
|
29932
|
-
"ST",
|
|
29933
|
-
"SV",
|
|
29934
|
-
"SX",
|
|
29935
|
-
"SY",
|
|
29936
|
-
"SZ",
|
|
29937
|
-
"TC",
|
|
29938
|
-
"TD",
|
|
29939
|
-
"TF",
|
|
29940
|
-
"TG",
|
|
29941
|
-
"TH",
|
|
29942
|
-
"TJ",
|
|
29943
|
-
"TK",
|
|
29944
|
-
"TL",
|
|
29945
|
-
"TM",
|
|
29946
|
-
"TN",
|
|
29947
|
-
"TO",
|
|
29948
|
-
"TR",
|
|
29949
|
-
"TT",
|
|
29950
|
-
"TV",
|
|
29951
|
-
"TW",
|
|
29952
|
-
"TZ",
|
|
29953
|
-
"UA",
|
|
29954
|
-
"UG",
|
|
29955
|
-
"UM",
|
|
29956
|
-
"US",
|
|
29957
|
-
"UY",
|
|
29958
|
-
"UZ",
|
|
29959
|
-
"VA",
|
|
29960
|
-
"VC",
|
|
29961
|
-
"VE",
|
|
29962
|
-
"VG",
|
|
29963
|
-
"VI",
|
|
29964
|
-
"VN",
|
|
29965
|
-
"VU",
|
|
29966
|
-
"WF",
|
|
29967
|
-
"WS",
|
|
29968
|
-
"YE",
|
|
29969
|
-
"YT",
|
|
29970
|
-
"ZA",
|
|
29971
|
-
"ZM",
|
|
29972
|
-
"ZW"
|
|
29973
|
-
];
|
|
29974
|
-
var SELECTABLE_TOKENS = /* @__PURE__ */ new Set([EU_TOKEN, ...COUNTRY_CODES]);
|
|
29975
|
-
var isSelectableCountryToken = /* @__PURE__ */ __name((token) => {
|
|
29976
|
-
return SELECTABLE_TOKENS.has(token);
|
|
29977
|
-
}, "isSelectableCountryToken");
|
|
29978
|
-
var matchesRule = /* @__PURE__ */ __name((country, isEU, list) => {
|
|
29979
|
-
if (country !== void 0 && list.includes(country)) {
|
|
29980
|
-
return true;
|
|
29981
|
-
}
|
|
29982
|
-
return isEU && list.includes(EU_TOKEN);
|
|
29983
|
-
}, "matchesRule");
|
|
29984
|
-
var isCountryBlocked = /* @__PURE__ */ __name((country, isEU, rule) => {
|
|
29985
|
-
if (rule === null || rule.list.length === 0) {
|
|
29986
|
-
return false;
|
|
29987
|
-
}
|
|
29988
|
-
const matched = matchesRule(country, isEU, rule.list);
|
|
29989
|
-
return rule.mode === "block" ? matched : !matched;
|
|
29990
|
-
}, "isCountryBlocked");
|
|
29991
|
-
|
|
29992
|
-
// ../../libs/bot-protection/src/score.ts
|
|
29993
|
-
var BOT_SCORE_MIN = 1;
|
|
29994
|
-
var BOT_SCORE_MAX = 99;
|
|
29995
|
-
|
|
29996
|
-
// ../../libs/bot-protection/src/config.ts
|
|
29997
|
-
var NO_PROTECTION = { minBotScore: null, countries: null };
|
|
29998
|
-
var normalizeScore = /* @__PURE__ */ __name((value, problems) => {
|
|
29999
|
-
if (value === void 0 || value === null) {
|
|
30000
|
-
return null;
|
|
30001
|
-
}
|
|
30002
|
-
if (typeof value === "number" && Number.isInteger(value) && value >= BOT_SCORE_MIN && value <= BOT_SCORE_MAX) {
|
|
30003
|
-
return value;
|
|
30004
|
-
}
|
|
30005
|
-
problems.push("has an out-of-range minBotScore");
|
|
30006
|
-
return null;
|
|
30007
|
-
}, "normalizeScore");
|
|
30008
|
-
var normalizeCountries = /* @__PURE__ */ __name((value, problems) => {
|
|
30009
|
-
if (value === void 0 || value === null) {
|
|
30010
|
-
return null;
|
|
30011
|
-
}
|
|
30012
|
-
const record2 = value;
|
|
30013
|
-
if (record2.mode !== "allow" && record2.mode !== "block" || !Array.isArray(record2.list)) {
|
|
30014
|
-
problems.push("has an invalid countries rule");
|
|
30015
|
-
return null;
|
|
30016
|
-
}
|
|
30017
|
-
const list = [];
|
|
30018
|
-
let dropped = 0;
|
|
30019
|
-
for (const token of record2.list) {
|
|
30020
|
-
if (typeof token === "string" && isSelectableCountryToken(token)) {
|
|
30021
|
-
list.push(token);
|
|
30022
|
-
} else {
|
|
30023
|
-
dropped += 1;
|
|
30024
|
-
}
|
|
30025
|
-
}
|
|
30026
|
-
if (dropped > 0) {
|
|
30027
|
-
problems.push(`dropped ${dropped} invalid country token(s)`);
|
|
30028
|
-
}
|
|
30029
|
-
return list.length === 0 ? null : { mode: record2.mode, list };
|
|
30030
|
-
}, "normalizeCountries");
|
|
30031
|
-
var parseBotProtection = /* @__PURE__ */ __name((raw2) => {
|
|
30032
|
-
if (raw2.trim() === "") {
|
|
30033
|
-
return { config: NO_PROTECTION };
|
|
30034
|
-
}
|
|
30035
|
-
let value;
|
|
30036
|
-
try {
|
|
30037
|
-
value = JSON.parse(raw2);
|
|
30038
|
-
} catch {
|
|
30039
|
-
return { config: NO_PROTECTION, error: "BOT_PROTECTION is not valid JSON" };
|
|
30040
|
-
}
|
|
30041
|
-
if (typeof value !== "object" || value === null) {
|
|
30042
|
-
return { config: NO_PROTECTION, error: "BOT_PROTECTION is not an object" };
|
|
30043
|
-
}
|
|
30044
|
-
const record2 = value;
|
|
30045
|
-
const problems = [];
|
|
30046
|
-
const config2 = {
|
|
30047
|
-
minBotScore: normalizeScore(record2.minBotScore, problems),
|
|
30048
|
-
countries: normalizeCountries(record2.countries, problems)
|
|
30049
|
-
};
|
|
30050
|
-
return problems.length > 0 ? { config: config2, error: `BOT_PROTECTION ${problems.join("; ")}` } : { config: config2 };
|
|
30051
|
-
}, "parseBotProtection");
|
|
30052
|
-
var isRequestBlocked = /* @__PURE__ */ __name((config2, signal) => {
|
|
30053
|
-
if (config2.minBotScore !== null && signal.botScore !== void 0 && signal.botScore < config2.minBotScore) {
|
|
30054
|
-
return true;
|
|
30055
|
-
}
|
|
30056
|
-
return isCountryBlocked(signal.country, signal.isEU ?? false, config2.countries);
|
|
30057
|
-
}, "isRequestBlocked");
|
|
30058
|
-
|
|
30059
|
-
// src/helpers/cookie.ts
|
|
30060
|
-
var COOKIE_NAME2 = "q_user_id";
|
|
30061
|
-
var COOKIE_MAX_AGE = 400 * 24 * 60 * 60;
|
|
30062
|
-
var FALLBACK_MIN_LENGTH = 4;
|
|
30063
|
-
var fallbackCookie = /* @__PURE__ */ __name((c) => {
|
|
30064
|
-
const name = c.env.FALLBACK_COOKIE;
|
|
30065
|
-
if (name === "") {
|
|
30066
|
-
return void 0;
|
|
30067
|
-
}
|
|
30068
|
-
const value = getCookie(c, name);
|
|
30069
|
-
if (value === void 0 || value.length < FALLBACK_MIN_LENGTH) {
|
|
30070
|
-
return void 0;
|
|
30071
|
-
}
|
|
30072
|
-
return { name, value };
|
|
30073
|
-
}, "fallbackCookie");
|
|
30074
|
-
|
|
30075
30264
|
// src/id-graph/anon/connection.ts
|
|
30076
30265
|
var CONNECTION_CF_HEADER = "x-connection-cf";
|
|
30266
|
+
var SESSION_HEADER = "x-session";
|
|
30077
30267
|
var MAX_HOST_USER_AGENT_LENGTH = 512;
|
|
30078
30268
|
var MAX_HOST_LOCALE_LENGTH = 64;
|
|
30079
|
-
var INTERNAL_HEADERS = /* @__PURE__ */ new Set([
|
|
30269
|
+
var INTERNAL_HEADERS = /* @__PURE__ */ new Set([
|
|
30270
|
+
"x-user-id",
|
|
30271
|
+
"x-new-user",
|
|
30272
|
+
"x-geo",
|
|
30273
|
+
SITE_ID_HEADER,
|
|
30274
|
+
CONNECTION_CF_HEADER,
|
|
30275
|
+
SESSION_HEADER,
|
|
30276
|
+
TRACE_HEADER,
|
|
30277
|
+
CF_DATA_HEADER
|
|
30278
|
+
]);
|
|
30080
30279
|
var REDACTED_HEADERS = /* @__PURE__ */ new Set(["authorization"]);
|
|
30081
30280
|
var encodeConnectionCf = /* @__PURE__ */ __name((cf) => {
|
|
30082
30281
|
if (cf === void 0) {
|
|
@@ -30092,6 +30291,13 @@ var forwardedCf = /* @__PURE__ */ __name((request) => {
|
|
|
30092
30291
|
}
|
|
30093
30292
|
return JSON.parse(raw2);
|
|
30094
30293
|
}, "forwardedCf");
|
|
30294
|
+
var forwardedSession = /* @__PURE__ */ __name((request) => {
|
|
30295
|
+
const raw2 = request.headers.get(SESSION_HEADER);
|
|
30296
|
+
if (raw2 === null) {
|
|
30297
|
+
return void 0;
|
|
30298
|
+
}
|
|
30299
|
+
return JSON.parse(raw2);
|
|
30300
|
+
}, "forwardedSession");
|
|
30095
30301
|
var connectAttrs = /* @__PURE__ */ __name((request, geo) => {
|
|
30096
30302
|
const headers = {};
|
|
30097
30303
|
for (const [name, value] of request.headers) {
|
|
@@ -30128,6 +30334,15 @@ var browserGeoFrom = /* @__PURE__ */ __name((geo) => {
|
|
|
30128
30334
|
var attachmentFieldBytes = /* @__PURE__ */ __name((name, value) => {
|
|
30129
30335
|
return value === void 0 ? 0 : new TextEncoder().encode(`,"${name}":${JSON.stringify(value)}`).length;
|
|
30130
30336
|
}, "attachmentFieldBytes");
|
|
30337
|
+
var boundedHost = /* @__PURE__ */ __name((request) => {
|
|
30338
|
+
const raw2 = hostFromRequest(request);
|
|
30339
|
+
const bounded = {
|
|
30340
|
+
ip: raw2.ip,
|
|
30341
|
+
userAgent: raw2.userAgent !== void 0 && raw2.userAgent.length <= MAX_HOST_USER_AGENT_LENGTH ? raw2.userAgent : void 0,
|
|
30342
|
+
locale: raw2.locale !== void 0 && raw2.locale.length <= MAX_HOST_LOCALE_LENGTH ? raw2.locale : void 0
|
|
30343
|
+
};
|
|
30344
|
+
return bounded.ip === void 0 && bounded.userAgent === void 0 && bounded.locale === void 0 ? void 0 : bounded;
|
|
30345
|
+
}, "boundedHost");
|
|
30131
30346
|
var connectionAttachment = /* @__PURE__ */ __name((request, geo, siteId) => {
|
|
30132
30347
|
const userAgent = request.headers.get("user-agent");
|
|
30133
30348
|
const attrs = {
|
|
@@ -30136,27 +30351,24 @@ var connectionAttachment = /* @__PURE__ */ __name((request, geo, siteId) => {
|
|
|
30136
30351
|
...geo?.city === void 0 ? {} : { "qinto.city": geo.city },
|
|
30137
30352
|
...userAgent === null ? {} : { user_agent: userAgent }
|
|
30138
30353
|
};
|
|
30139
|
-
const
|
|
30140
|
-
const bounded = {
|
|
30141
|
-
ip: raw2.ip,
|
|
30142
|
-
userAgent: raw2.userAgent !== void 0 && raw2.userAgent.length <= MAX_HOST_USER_AGENT_LENGTH ? raw2.userAgent : void 0,
|
|
30143
|
-
locale: raw2.locale !== void 0 && raw2.locale.length <= MAX_HOST_LOCALE_LENGTH ? raw2.locale : void 0
|
|
30144
|
-
};
|
|
30145
|
-
const host = bounded.ip === void 0 && bounded.userAgent === void 0 && bounded.locale === void 0 ? void 0 : bounded;
|
|
30354
|
+
const host = boundedHost(request);
|
|
30146
30355
|
const hostBytes = attachmentFieldBytes("host", host);
|
|
30147
30356
|
const browserGeo = browserGeoFrom(geo);
|
|
30148
30357
|
const browserGeoBytes = attachmentFieldBytes("browserGeo", browserGeo);
|
|
30358
|
+
const session2 = forwardedSession(request);
|
|
30359
|
+
const sessionBytes = attachmentFieldBytes("session", session2);
|
|
30149
30360
|
const siteBytes = attachmentFieldBytes("siteId", siteId);
|
|
30150
30361
|
const cf = forwardedCf(request);
|
|
30151
30362
|
const cfSuffix = cf === void 0 ? "" : `,"cf":${JSON.stringify(cf)}`;
|
|
30152
30363
|
const cfBytes = new TextEncoder().encode(cfSuffix).length;
|
|
30153
30364
|
const emptyAttrsBytes = new TextEncoder().encode(JSON.stringify({ attrs: {} })).length;
|
|
30154
|
-
const fixedBytes = siteBytes + hostBytes + browserGeoBytes;
|
|
30365
|
+
const fixedBytes = siteBytes + sessionBytes + hostBytes + browserGeoBytes;
|
|
30155
30366
|
const includedCf = cfBytes <= MAX_ATTACHMENT_BYTES - emptyAttrsBytes - fixedBytes ? cf : void 0;
|
|
30156
30367
|
const budgeted = budgetAttrs(attrs, MAX_ATTACHMENT_BYTES - fixedBytes - (includedCf === void 0 ? 0 : cfBytes));
|
|
30157
30368
|
return {
|
|
30158
30369
|
...budgeted,
|
|
30159
30370
|
siteId,
|
|
30371
|
+
...session2 === void 0 ? {} : { session: session2 },
|
|
30160
30372
|
...browserGeo === void 0 ? {} : { browserGeo },
|
|
30161
30373
|
...includedCf === void 0 ? {} : { cf: includedCf },
|
|
30162
30374
|
...host === void 0 ? {} : { host }
|
|
@@ -30164,14 +30376,18 @@ var connectionAttachment = /* @__PURE__ */ __name((request, geo, siteId) => {
|
|
|
30164
30376
|
}, "connectionAttachment");
|
|
30165
30377
|
|
|
30166
30378
|
// src/paths/open.ts
|
|
30167
|
-
var withSetCookie = /* @__PURE__ */ __name((res,
|
|
30379
|
+
var withSetCookie = /* @__PURE__ */ __name((res, cookies) => {
|
|
30168
30380
|
if (res.webSocket) {
|
|
30169
30381
|
const headers = new Headers(res.headers);
|
|
30170
|
-
|
|
30382
|
+
for (const cookie of cookies) {
|
|
30383
|
+
headers.append("set-cookie", cookie);
|
|
30384
|
+
}
|
|
30171
30385
|
return new Response(null, { status: res.status, webSocket: res.webSocket, headers });
|
|
30172
30386
|
}
|
|
30173
30387
|
const out = new Response(res.body, res);
|
|
30174
|
-
|
|
30388
|
+
for (const cookie of cookies) {
|
|
30389
|
+
out.headers.append("set-cookie", cookie);
|
|
30390
|
+
}
|
|
30175
30391
|
return out;
|
|
30176
30392
|
}, "withSetCookie");
|
|
30177
30393
|
var open2 = /* @__PURE__ */ __name(async (c) => {
|
|
@@ -30186,35 +30402,27 @@ var open2 = /* @__PURE__ */ __name(async (c) => {
|
|
|
30186
30402
|
});
|
|
30187
30403
|
return enriched;
|
|
30188
30404
|
});
|
|
30189
|
-
|
|
30190
|
-
const { config: protection, error: error61 } = parseBotProtection(c.env.BOT_PROTECTION);
|
|
30191
|
-
if (error61 !== void 0) {
|
|
30192
|
-
addSpanEvent("BOT_PROTECTION is misconfigured; enforcing it with the invalid parts dropped", {
|
|
30193
|
-
level: "error",
|
|
30194
|
-
attrs: { error: String(error61) }
|
|
30195
|
-
});
|
|
30196
|
-
}
|
|
30197
|
-
const rejected = isRequestBlocked(protection, { botScore: geo.botScore, country: geo.country, isEU: geo.isEUCountry === "yes" });
|
|
30198
|
-
setSpanAttributes({ "bot.blocked": rejected, ...geo.botScore === void 0 ? {} : { "bot.score": geo.botScore } });
|
|
30199
|
-
return rejected;
|
|
30200
|
-
});
|
|
30201
|
-
if (blocked) {
|
|
30405
|
+
if (await botRejected(c.env, geo)) {
|
|
30202
30406
|
return c.text("traffic rejected", 403);
|
|
30203
30407
|
}
|
|
30204
30408
|
const existing = getCookie(c, COOKIE_NAME2);
|
|
30205
30409
|
let isNewUser = false;
|
|
30206
30410
|
let userId;
|
|
30207
|
-
if (
|
|
30208
|
-
userId = await restoreUser(c.env, fallbackCookie(c));
|
|
30209
|
-
isNewUser = true;
|
|
30210
|
-
} else {
|
|
30411
|
+
if (existing !== void 0 && isCanonicalId(existing)) {
|
|
30211
30412
|
userId = existing;
|
|
30413
|
+
} else {
|
|
30414
|
+
const adopted = edgeTagUserId(c);
|
|
30415
|
+
setSpanAttributes({ "qinto.edgetag_adopted": adopted !== void 0 });
|
|
30416
|
+
userId = await restoreUser(c.env, fallbackCookie(c), adopted ?? generateUserId());
|
|
30417
|
+
isNewUser = adopted === void 0;
|
|
30212
30418
|
}
|
|
30213
30419
|
const headers = new Headers(c.req.raw.headers);
|
|
30214
30420
|
headers.set("x-user-id", userId);
|
|
30215
30421
|
headers.set("x-new-user", isNewUser.toString());
|
|
30216
30422
|
headers.set(SITE_ID_HEADER, ownSiteId(c.env));
|
|
30217
30423
|
headers.set("x-geo", headerJson(geo));
|
|
30424
|
+
const session2 = visitSession(c, Date.now());
|
|
30425
|
+
headers.set(SESSION_HEADER, JSON.stringify(session2));
|
|
30218
30426
|
headers.delete(CONNECTION_CF_HEADER);
|
|
30219
30427
|
const cf = encodeConnectionCf(c.req.raw.cf);
|
|
30220
30428
|
if (cf !== void 0) {
|
|
@@ -30231,16 +30439,24 @@ var open2 = /* @__PURE__ */ __name(async (c) => {
|
|
|
30231
30439
|
const root = c.env.ROOT_DOMAIN;
|
|
30232
30440
|
const hostname3 = new URL(c.req.url).hostname;
|
|
30233
30441
|
const domain2 = root !== void 0 && root !== "" && (hostname3 === root || hostname3.endsWith(`.${root}`)) ? root : void 0;
|
|
30234
|
-
const
|
|
30442
|
+
const attributes = {
|
|
30235
30443
|
httpOnly: true,
|
|
30236
30444
|
// `wrangler types` narrows COOKIE_SAME_SITE to its dev default; deploys also set none.
|
|
30237
30445
|
sameSite: c.env.COOKIE_SAME_SITE,
|
|
30238
30446
|
secure: true,
|
|
30239
30447
|
path: "/",
|
|
30240
|
-
maxAge: COOKIE_MAX_AGE
|
|
30241
|
-
|
|
30242
|
-
|
|
30243
|
-
|
|
30448
|
+
maxAge: COOKIE_MAX_AGE
|
|
30449
|
+
};
|
|
30450
|
+
const cookies = [
|
|
30451
|
+
generateCookie(COOKIE_NAME2, userId, { ...attributes, ...domain2 === void 0 ? {} : { domain: domain2 } }),
|
|
30452
|
+
// Re-issued on every connect, which is what makes the session expire after 30 idle minutes.
|
|
30453
|
+
generateCookie(SESSION_COOKIE_NAME, `${session2.id}.${session2.startedAt}`, { ...attributes, maxAge: SESSION_MAX_AGE })
|
|
30454
|
+
];
|
|
30455
|
+
const signature = await signVisitor(c.env, userId);
|
|
30456
|
+
if (signature !== void 0) {
|
|
30457
|
+
cookies.push(generateCookie(VISITOR_SIGNATURE_COOKIE, signature, attributes));
|
|
30458
|
+
}
|
|
30459
|
+
return withSetCookie(res, cookies);
|
|
30244
30460
|
}, "open");
|
|
30245
30461
|
|
|
30246
30462
|
// browser-artifact/meta.json
|
|
@@ -30300,7 +30516,7 @@ var appsMetadata = /* @__PURE__ */ __name(() => {
|
|
|
30300
30516
|
const { registry: registry3 } = siteRegistry();
|
|
30301
30517
|
return Object.fromEntries(
|
|
30302
30518
|
Object.entries(registry3.apps).map(([slug, app2]) => {
|
|
30303
|
-
const
|
|
30519
|
+
const capture2 = declaredCapture(app2);
|
|
30304
30520
|
const geo = app2.data.includes("geo") ? { geo: true } : {};
|
|
30305
30521
|
const sharedContext = app2.data.includes(SHARED_CONTEXT_CAPABILITY) ? { sharedContext: true } : {};
|
|
30306
30522
|
return [
|
|
@@ -30308,7 +30524,7 @@ var appsMetadata = /* @__PURE__ */ __name(() => {
|
|
|
30308
30524
|
{
|
|
30309
30525
|
category: app2.consent.category,
|
|
30310
30526
|
eventResults: app2.eventResults,
|
|
30311
|
-
...
|
|
30527
|
+
...capture2 === void 0 ? {} : { capture: capture2 },
|
|
30312
30528
|
...geo,
|
|
30313
30529
|
...sharedContext
|
|
30314
30530
|
}
|
|
@@ -30387,6 +30603,60 @@ var alias = createApiRouter().openapi(definition2, async (c) => {
|
|
|
30387
30603
|
return respond(c, acceptedResponse, { ok: true, id });
|
|
30388
30604
|
});
|
|
30389
30605
|
|
|
30606
|
+
// src/paths/v1/capture.ts
|
|
30607
|
+
var captureBody = external_exports.object({
|
|
30608
|
+
identifier: identifierField2,
|
|
30609
|
+
apps: captureAppsField
|
|
30610
|
+
}).strict().openapi("CaptureRequest", { description: "Click ids and vendor ids to record for the user, keyed by app." });
|
|
30611
|
+
var captureResponse = external_exports.object({
|
|
30612
|
+
ok: external_exports.literal(true).openapi({
|
|
30613
|
+
description: "Always true: the values were accepted, not that every one of them was stored.",
|
|
30614
|
+
example: true
|
|
30615
|
+
}),
|
|
30616
|
+
accepted: external_exports.record(external_exports.string(), external_exports.array(external_exports.string())).openapi({
|
|
30617
|
+
description: "The capture names now stored, per app slug. A name missing here was dropped.",
|
|
30618
|
+
example: { facebook: ["fbclid"] }
|
|
30619
|
+
})
|
|
30620
|
+
}).openapi("CaptureAccepted", { description: "The capture names that are now stored." });
|
|
30621
|
+
var definition3 = createRoute({
|
|
30622
|
+
method: "post",
|
|
30623
|
+
path: "/v1/capture",
|
|
30624
|
+
operationId: "captureValues",
|
|
30625
|
+
tags: ["Identity"],
|
|
30626
|
+
security: [{ QintoWorkerAccessToken: [] }],
|
|
30627
|
+
middleware: [requireScope(SCOPE.capture)],
|
|
30628
|
+
summary: "Record click ids",
|
|
30629
|
+
description: operationDescription({
|
|
30630
|
+
intro: "Records click ids and vendor ids for the user, such as `fbclid` or `gclid`, keyed by the app that reads them.",
|
|
30631
|
+
howItWorks: "A value is stored only when the named app is installed on this site, declares that name, and holds the user\u2019s consent for its category \u2014 `accepted` reports exactly what landed, so compare it against what you sent. Re-sending an unchanged value is a no-op and keeps the original click time, which is what any expiry window runs from. Capturing never creates a user: an identifier of your own that this site has not seen fails with `unknown_user`."
|
|
30632
|
+
}),
|
|
30633
|
+
request: {
|
|
30634
|
+
body: {
|
|
30635
|
+
required: true,
|
|
30636
|
+
description: "The values to record.",
|
|
30637
|
+
content: { "application/json": { schema: captureBody } }
|
|
30638
|
+
}
|
|
30639
|
+
},
|
|
30640
|
+
responses: {
|
|
30641
|
+
200: {
|
|
30642
|
+
description: "The values were accepted.",
|
|
30643
|
+
content: { "application/json": { schema: captureResponse } }
|
|
30644
|
+
},
|
|
30645
|
+
...errorResponses("invalid_body", "unauthenticated", "forbidden", "unknown_user", "internal")
|
|
30646
|
+
}
|
|
30647
|
+
});
|
|
30648
|
+
var capture = createApiRouter().openapi(definition3, async (c) => {
|
|
30649
|
+
const { identifier, apps } = c.req.valid("json");
|
|
30650
|
+
const resolved = await resolveExistingUser(c.env, identifier);
|
|
30651
|
+
if (resolved === null) {
|
|
30652
|
+
return c.json({ error: "unknown_user" }, 404);
|
|
30653
|
+
}
|
|
30654
|
+
const { accepted } = await traced("rpc anon.server_capture", {}, () => {
|
|
30655
|
+
return anonStub(c.env, resolved.id).serverCapture({ anonId: resolved.id, apps }, ownSite(c.env), currentTraceContext());
|
|
30656
|
+
});
|
|
30657
|
+
return respond(c, captureResponse, { ok: true, accepted });
|
|
30658
|
+
});
|
|
30659
|
+
|
|
30390
30660
|
// src/paths/v1/consent.ts
|
|
30391
30661
|
var consentBody = external_exports.object({
|
|
30392
30662
|
identifier: identifierField2,
|
|
@@ -30399,7 +30669,7 @@ var consentBody = external_exports.object({
|
|
|
30399
30669
|
}),
|
|
30400
30670
|
context: serverActionContextField
|
|
30401
30671
|
}).strict().openapi("ConsentRequest", { description: "Consent choices to record for the user." });
|
|
30402
|
-
var
|
|
30672
|
+
var definition4 = createRoute({
|
|
30403
30673
|
method: "post",
|
|
30404
30674
|
path: "/v1/consent",
|
|
30405
30675
|
operationId: "saveUserConsent",
|
|
@@ -30426,7 +30696,7 @@ var definition3 = createRoute({
|
|
|
30426
30696
|
...errorResponses("invalid_body", "unauthenticated", "forbidden", "internal")
|
|
30427
30697
|
}
|
|
30428
30698
|
});
|
|
30429
|
-
var consent = createApiRouter().openapi(
|
|
30699
|
+
var consent = createApiRouter().openapi(definition4, async (c) => {
|
|
30430
30700
|
const { identifier, categories, apps, context } = c.req.valid("json");
|
|
30431
30701
|
const id = await submitAction(
|
|
30432
30702
|
c.env,
|
|
@@ -30460,7 +30730,7 @@ var consentStateResponse = external_exports.object({
|
|
|
30460
30730
|
}).openapi("ConsentState", {
|
|
30461
30731
|
description: "The consent state currently stored for the user; categories never saved read as not granted."
|
|
30462
30732
|
});
|
|
30463
|
-
var
|
|
30733
|
+
var definition5 = createRoute({
|
|
30464
30734
|
method: "get",
|
|
30465
30735
|
path: "/v1/consent",
|
|
30466
30736
|
operationId: "getUserConsent",
|
|
@@ -30481,7 +30751,7 @@ var definition4 = createRoute({
|
|
|
30481
30751
|
...errorResponses("invalid_params", "unauthenticated", "forbidden", "internal")
|
|
30482
30752
|
}
|
|
30483
30753
|
});
|
|
30484
|
-
var consentState = createApiRouter().openapi(
|
|
30754
|
+
var consentState = createApiRouter().openapi(definition5, async (c) => {
|
|
30485
30755
|
const identifier = c.req.valid("query").identifier ?? getCookie(c, COOKIE_NAME2);
|
|
30486
30756
|
if (identifier === void 0 || !isCanonicalId(identifier)) {
|
|
30487
30757
|
return c.json({ error: "invalid_params" }, 400);
|
|
@@ -30501,7 +30771,7 @@ var identifyBody = external_exports.object({
|
|
|
30501
30771
|
}),
|
|
30502
30772
|
context: serverActionContextField
|
|
30503
30773
|
}).openapi("IdentifyRequest", { description: "Identity traits to record for the user." });
|
|
30504
|
-
var
|
|
30774
|
+
var definition6 = createRoute({
|
|
30505
30775
|
method: "post",
|
|
30506
30776
|
path: "/v1/identify",
|
|
30507
30777
|
operationId: "identifyUser",
|
|
@@ -30528,7 +30798,7 @@ var definition5 = createRoute({
|
|
|
30528
30798
|
...errorResponses("invalid_body", "unauthenticated", "forbidden", "internal")
|
|
30529
30799
|
}
|
|
30530
30800
|
});
|
|
30531
|
-
var identify = createApiRouter().openapi(
|
|
30801
|
+
var identify = createApiRouter().openapi(definition6, async (c) => {
|
|
30532
30802
|
const { identifier, traits, context } = c.req.valid("json");
|
|
30533
30803
|
const id = await submitAction(c.env, identifier, { type: "identify", payload: { traits: traits ?? {} }, context }, c.req.raw);
|
|
30534
30804
|
return respond(c, acceptedResponse, { ok: true, id });
|
|
@@ -30538,7 +30808,7 @@ var identify = createApiRouter().openapi(definition5, async (c) => {
|
|
|
30538
30808
|
var personQuery = external_exports.object({
|
|
30539
30809
|
identifier: identifierField2.optional().openapi({
|
|
30540
30810
|
param: { name: "identifier", in: "query" },
|
|
30541
|
-
description: "User to read: any identifier from your own systems (email, phone, custom id), or a canonical Qinto id (`anon_\u2026`). Omit it to read the user from the first-party cookie instead. Internal `gid_\u2026` ids are rejected.",
|
|
30811
|
+
description: "User to read: any identifier from your own systems (email, phone, custom id), or a canonical Qinto id (`anon_\u2026`, or a `tag_user_id` adopted from EdgeTag). Omit it to read the user from the first-party cookie instead. Internal `gid_\u2026` ids are rejected.",
|
|
30542
30812
|
example: "ada@example.com"
|
|
30543
30813
|
}),
|
|
30544
30814
|
cursor: external_exports.string().regex(/^\d+$/).optional().openapi({
|
|
@@ -30561,7 +30831,7 @@ var personResponse = external_exports.object({
|
|
|
30561
30831
|
example: null
|
|
30562
30832
|
})
|
|
30563
30833
|
}).openapi("Person", { description: "The person a user belongs to, and the sessions linked to that person." });
|
|
30564
|
-
var
|
|
30834
|
+
var definition7 = createRoute({
|
|
30565
30835
|
method: "get",
|
|
30566
30836
|
path: "/v1/person",
|
|
30567
30837
|
operationId: "getPerson",
|
|
@@ -30582,7 +30852,7 @@ var definition6 = createRoute({
|
|
|
30582
30852
|
...errorResponses("invalid_params", "unauthenticated", "forbidden", "internal")
|
|
30583
30853
|
}
|
|
30584
30854
|
});
|
|
30585
|
-
var person = createApiRouter().openapi(
|
|
30855
|
+
var person = createApiRouter().openapi(definition7, async (c) => {
|
|
30586
30856
|
const { identifier, cursor } = c.req.valid("query");
|
|
30587
30857
|
const id = identifier ?? getCookie(c, COOKIE_NAME2);
|
|
30588
30858
|
if (id === void 0) {
|
|
@@ -30600,7 +30870,7 @@ var propertiesQuery = external_exports.object({
|
|
|
30600
30870
|
})
|
|
30601
30871
|
});
|
|
30602
30872
|
var propertiesResponse = external_exports.record(external_exports.string(), external_exports.unknown()).openapi("UserProperties", { description: "Every property currently stored for the user, keyed by property name." });
|
|
30603
|
-
var
|
|
30873
|
+
var definition8 = createRoute({
|
|
30604
30874
|
method: "get",
|
|
30605
30875
|
path: "/v1/properties",
|
|
30606
30876
|
operationId: "getUserProperties",
|
|
@@ -30621,7 +30891,7 @@ var definition7 = createRoute({
|
|
|
30621
30891
|
...errorResponses("invalid_params", "unauthenticated", "forbidden", "internal")
|
|
30622
30892
|
}
|
|
30623
30893
|
});
|
|
30624
|
-
var properties = createApiRouter().openapi(
|
|
30894
|
+
var properties = createApiRouter().openapi(definition8, async (c) => {
|
|
30625
30895
|
const identifier = c.req.valid("query").identifier ?? getCookie(c, COOKIE_NAME2);
|
|
30626
30896
|
if (identifier === void 0 || !isCanonicalId(identifier)) {
|
|
30627
30897
|
return c.json({ error: "invalid_params" }, 400);
|
|
@@ -30650,7 +30920,7 @@ var sessionResponse = external_exports.object({
|
|
|
30650
30920
|
example: "gid_01JZWG7Q4S1T8M6Y2E9RBD3KXN"
|
|
30651
30921
|
})
|
|
30652
30922
|
}).openapi("Session", { description: "The anonymous session id and the person id it is linked to." });
|
|
30653
|
-
var
|
|
30923
|
+
var definition9 = createRoute({
|
|
30654
30924
|
method: "get",
|
|
30655
30925
|
path: "/v1/session",
|
|
30656
30926
|
operationId: "getSession",
|
|
@@ -30671,7 +30941,7 @@ var definition8 = createRoute({
|
|
|
30671
30941
|
...errorResponses("invalid_params", "unauthenticated", "forbidden", "internal")
|
|
30672
30942
|
}
|
|
30673
30943
|
});
|
|
30674
|
-
var session = createApiRouter().openapi(
|
|
30944
|
+
var session = createApiRouter().openapi(definition9, async (c) => {
|
|
30675
30945
|
const identifier = c.req.valid("query").identifier ?? getCookie(c, COOKIE_NAME2);
|
|
30676
30946
|
if (identifier === void 0) {
|
|
30677
30947
|
return c.json({ error: "invalid_params" }, 400);
|
|
@@ -30696,7 +30966,7 @@ var trackBody = external_exports.object({
|
|
|
30696
30966
|
}),
|
|
30697
30967
|
context: serverActionContextField
|
|
30698
30968
|
}).strict().openapi("TrackRequest", { description: "An event the user performed, with optional free-form properties." });
|
|
30699
|
-
var
|
|
30969
|
+
var definition10 = createRoute({
|
|
30700
30970
|
method: "post",
|
|
30701
30971
|
path: "/v1/track",
|
|
30702
30972
|
operationId: "trackEvent",
|
|
@@ -30723,7 +30993,7 @@ var definition9 = createRoute({
|
|
|
30723
30993
|
...errorResponses("invalid_body", "unauthenticated", "forbidden", "unknown_user", "internal")
|
|
30724
30994
|
}
|
|
30725
30995
|
});
|
|
30726
|
-
var track = createApiRouter().openapi(
|
|
30996
|
+
var track = createApiRouter().openapi(definition10, async (c) => {
|
|
30727
30997
|
const { identifier, event, eventId, properties: properties2, context } = c.req.valid("json");
|
|
30728
30998
|
try {
|
|
30729
30999
|
const id = await submitAction(
|
|
@@ -30741,6 +31011,12 @@ var track = createApiRouter().openapi(definition9, async (c) => {
|
|
|
30741
31011
|
}
|
|
30742
31012
|
});
|
|
30743
31013
|
|
|
31014
|
+
// src/paths/welcome.ts
|
|
31015
|
+
var WELCOME = "Welcome to Qinto by Blotout. This site is set up.\nFor more information, go to https://docs.blotout.io\n";
|
|
31016
|
+
var welcome = /* @__PURE__ */ __name((c) => {
|
|
31017
|
+
return c.text(WELCOME);
|
|
31018
|
+
}, "welcome");
|
|
31019
|
+
|
|
30744
31020
|
// src/apps/fan-out-entrypoint.ts
|
|
30745
31021
|
import { WorkerEntrypoint as WorkerEntrypoint2 } from "cloudflare:workers";
|
|
30746
31022
|
|
|
@@ -31603,9 +31879,11 @@ var nextRetentionDeadline = /* @__PURE__ */ __name((sql) => {
|
|
|
31603
31879
|
var META_KEY = "meta";
|
|
31604
31880
|
var LAST_ACTIVITY_KEY = "lastActivityAt";
|
|
31605
31881
|
var seedMeta = /* @__PURE__ */ __name((kv, anonId, createdAt) => {
|
|
31606
|
-
if (kv.get(META_KEY)
|
|
31607
|
-
|
|
31882
|
+
if (kv.get(META_KEY) !== void 0) {
|
|
31883
|
+
return false;
|
|
31608
31884
|
}
|
|
31885
|
+
kv.put(META_KEY, { anonId, linkedGlobalId: null, linkedAt: null, createdAt });
|
|
31886
|
+
return true;
|
|
31609
31887
|
}, "seedMeta");
|
|
31610
31888
|
var touchActivity = /* @__PURE__ */ __name((kv, receivedAt) => {
|
|
31611
31889
|
const last = kv.get(LAST_ACTIVITY_KEY);
|
|
@@ -31622,6 +31900,9 @@ var linkedGlobalId = /* @__PURE__ */ __name((kv) => {
|
|
|
31622
31900
|
var storedAnonId = /* @__PURE__ */ __name((kv) => {
|
|
31623
31901
|
return kv.get(META_KEY)?.anonId ?? null;
|
|
31624
31902
|
}, "storedAnonId");
|
|
31903
|
+
var storedMeta = /* @__PURE__ */ __name((kv) => {
|
|
31904
|
+
return kv.get(META_KEY);
|
|
31905
|
+
}, "storedMeta");
|
|
31625
31906
|
var SYNC_DEBT_KEY = "syncDebt";
|
|
31626
31907
|
var syncDebt = /* @__PURE__ */ __name((kv) => {
|
|
31627
31908
|
const stored = kv.get(SYNC_DEBT_KEY);
|
|
@@ -31927,6 +32208,9 @@ var updateGeo = /* @__PURE__ */ __name((storage, geo, observedAt) => {
|
|
|
31927
32208
|
}
|
|
31928
32209
|
return appended;
|
|
31929
32210
|
}, "updateGeo");
|
|
32211
|
+
var hasGeoRecord = /* @__PURE__ */ __name((kv) => {
|
|
32212
|
+
return kv.get(GEO_KEY) !== void 0;
|
|
32213
|
+
}, "hasGeoRecord");
|
|
31930
32214
|
var geoSnapshot = /* @__PURE__ */ __name((kv) => {
|
|
31931
32215
|
const record2 = kv.get(GEO_KEY);
|
|
31932
32216
|
if (record2 === void 0) {
|
|
@@ -32200,8 +32484,7 @@ var MAX_BROWSER_RESULTS_BYTES = 32 * 1024;
|
|
|
32200
32484
|
var MAX_FRAME_BYTES = 64 * 1024;
|
|
32201
32485
|
var browserContextFrame = external_exports.object({
|
|
32202
32486
|
occurredAt: external_exports.number().int().nonnegative().safe(),
|
|
32203
|
-
page: external_exports.object({ url: external_exports.string().min(1).max(4096), referrer: external_exports.string().min(1).max(4096).optional() }).strict()
|
|
32204
|
-
session: external_exports.object({ id: external_exports.string().min(1).max(64), isNew: external_exports.boolean(), startedAt: external_exports.number().int().nonnegative().safe() }).strict()
|
|
32487
|
+
page: external_exports.object({ url: external_exports.string().min(1).max(4096), referrer: external_exports.string().min(1).max(4096).optional() }).strict()
|
|
32205
32488
|
}).strict();
|
|
32206
32489
|
var browserResult = external_exports.unknown().refine((value) => {
|
|
32207
32490
|
return new TextEncoder().encode(JSON.stringify(value)).length <= MAX_BROWSER_RESULT_BYTES;
|
|
@@ -32241,9 +32524,7 @@ var consentFrame = external_exports.object({
|
|
|
32241
32524
|
var captureFrame = external_exports.object({
|
|
32242
32525
|
t: external_exports.literal("capture"),
|
|
32243
32526
|
ref: external_exports.string().min(1).optional(),
|
|
32244
|
-
apps:
|
|
32245
|
-
return Object.keys(apps).length <= 50 && Object.values(apps).every((values) => Object.keys(values).length <= 32);
|
|
32246
|
-
}, "capture carries more apps or keys than a site can have")
|
|
32527
|
+
apps: captureAppsField
|
|
32247
32528
|
}).strict();
|
|
32248
32529
|
var getConsentFrame = external_exports.object({
|
|
32249
32530
|
t: external_exports.literal("getConsent"),
|
|
@@ -32303,7 +32584,7 @@ var frameSpanAttrs = /* @__PURE__ */ __name((frame, userId) => {
|
|
|
32303
32584
|
...userId === null ? {} : { "action.user_id": userId }
|
|
32304
32585
|
};
|
|
32305
32586
|
}, "frameSpanAttrs");
|
|
32306
|
-
var browserActionContext = /* @__PURE__ */ __name((frame, host) => {
|
|
32587
|
+
var browserActionContext = /* @__PURE__ */ __name((frame, host, session2) => {
|
|
32307
32588
|
const context = {
|
|
32308
32589
|
source: "browser",
|
|
32309
32590
|
occurredAt: frame.occurredAt,
|
|
@@ -32316,11 +32597,12 @@ var browserActionContext = /* @__PURE__ */ __name((frame, host) => {
|
|
|
32316
32597
|
provenance: "edge"
|
|
32317
32598
|
}
|
|
32318
32599
|
},
|
|
32319
|
-
session:
|
|
32600
|
+
...session2 === void 0 ? {} : { session: session2 }
|
|
32320
32601
|
};
|
|
32321
32602
|
return context;
|
|
32322
32603
|
}, "browserActionContext");
|
|
32323
|
-
var actionFromFrame = /* @__PURE__ */ __name((frame, userId,
|
|
32604
|
+
var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) => {
|
|
32605
|
+
const { cf: geo, host, session: session2 } = connection;
|
|
32324
32606
|
switch (frame.t) {
|
|
32325
32607
|
case "identify": {
|
|
32326
32608
|
const identifier = frame.id === void 0 ? void 0 : classifyIdentifier(frame.id);
|
|
@@ -32330,7 +32612,7 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, geo, host) => {
|
|
|
32330
32612
|
identifier,
|
|
32331
32613
|
source: "ws",
|
|
32332
32614
|
payload: { traits: frame.traits ?? {} },
|
|
32333
|
-
context: browserActionContext(frame.context, host),
|
|
32615
|
+
context: browserActionContext(frame.context, host, session2),
|
|
32334
32616
|
browserResults: frame.appResults,
|
|
32335
32617
|
geo,
|
|
32336
32618
|
host
|
|
@@ -32342,7 +32624,7 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, geo, host) => {
|
|
|
32342
32624
|
userId,
|
|
32343
32625
|
source: "ws",
|
|
32344
32626
|
payload: { event: frame.event, eventId: frame.eventId, properties: frame.properties ?? {} },
|
|
32345
|
-
context: browserActionContext(frame.context, host),
|
|
32627
|
+
context: browserActionContext(frame.context, host, session2),
|
|
32346
32628
|
browserResults: frame.appResults,
|
|
32347
32629
|
geo,
|
|
32348
32630
|
host
|
|
@@ -32357,7 +32639,7 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, geo, host) => {
|
|
|
32357
32639
|
userId,
|
|
32358
32640
|
source: "ws",
|
|
32359
32641
|
payload: { categories: frame.categories ?? {}, apps: frame.apps ?? {} },
|
|
32360
|
-
context: browserActionContext(frame.context, host),
|
|
32642
|
+
context: browserActionContext(frame.context, host, session2),
|
|
32361
32643
|
geo,
|
|
32362
32644
|
host
|
|
32363
32645
|
});
|
|
@@ -32426,8 +32708,8 @@ var AnonDO = class extends DurableObject2 {
|
|
|
32426
32708
|
this.cleanupAlarm = await seedCleanupAlarm(this.ctx.storage, now, lastActivityAt(this.kv) ?? now, this.cleanupAlarm);
|
|
32427
32709
|
seedMeta(this.kv, userId, now);
|
|
32428
32710
|
touchActivity(this.kv, now);
|
|
32429
|
-
if (geo !== void 0) {
|
|
32430
|
-
this.
|
|
32711
|
+
if (geo !== void 0 && !hasGeoRecord(this.kv)) {
|
|
32712
|
+
this.seedGeoOnConnect(userId, geo, now);
|
|
32431
32713
|
}
|
|
32432
32714
|
const { 0: client, 1: server } = new WebSocketPair();
|
|
32433
32715
|
this.ctx.acceptWebSocket(server, [siteId]);
|
|
@@ -32543,7 +32825,7 @@ var AnonDO = class extends DurableObject2 {
|
|
|
32543
32825
|
if (frame === null) {
|
|
32544
32826
|
return;
|
|
32545
32827
|
}
|
|
32546
|
-
const action = actionFromFrame(frame, userId,
|
|
32828
|
+
const action = actionFromFrame(frame, userId, this.spendSession(ws, frame));
|
|
32547
32829
|
const ref = parsed.ref ?? action.actionId;
|
|
32548
32830
|
setSpanAttributes({ "action.id": action.actionId });
|
|
32549
32831
|
try {
|
|
@@ -32556,6 +32838,15 @@ var AnonDO = class extends DurableObject2 {
|
|
|
32556
32838
|
this.respond(ws, { t: "ack", ref, anonId: userId });
|
|
32557
32839
|
});
|
|
32558
32840
|
}
|
|
32841
|
+
// Re-read, not taken from the message's start: across its awaits a pipelined frame may already have
|
|
32842
|
+
// spent the session's first action.
|
|
32843
|
+
spendSession(ws, frame) {
|
|
32844
|
+
const current = ws.deserializeAttachment();
|
|
32845
|
+
if (current.session?.isNew === true && frame.t !== "alias") {
|
|
32846
|
+
ws.serializeAttachment({ ...current, session: { ...current.session, isNew: false } });
|
|
32847
|
+
}
|
|
32848
|
+
return current;
|
|
32849
|
+
}
|
|
32559
32850
|
respond(ws, frame) {
|
|
32560
32851
|
sendFrame(ws, JSON.stringify(frame));
|
|
32561
32852
|
}
|
|
@@ -32596,9 +32887,21 @@ var AnonDO = class extends DurableObject2 {
|
|
|
32596
32887
|
addSpanEvent("capture dropped: no visitor state", { level: "warn" });
|
|
32597
32888
|
return { accepted: {} };
|
|
32598
32889
|
}
|
|
32599
|
-
|
|
32600
|
-
|
|
32601
|
-
|
|
32890
|
+
return this.runCaptures(input2.apps, site);
|
|
32891
|
+
});
|
|
32892
|
+
});
|
|
32893
|
+
}
|
|
32894
|
+
// The server door. There is no connect to have seeded the visitor, and a migrated visitor can
|
|
32895
|
+
// carry click ids before any identify, so unlike the socket this one creates the profile.
|
|
32896
|
+
serverCapture(input2, site, trace) {
|
|
32897
|
+
return runInvocation({ name: "do.anon.server_capture", trace }, () => {
|
|
32898
|
+
return this.enqueue(async () => {
|
|
32899
|
+
this.revive();
|
|
32900
|
+
const now = Date.now();
|
|
32901
|
+
this.cleanupAlarm = await seedCleanupAlarm(this.ctx.storage, now, lastActivityAt(this.kv) ?? now, this.cleanupAlarm);
|
|
32902
|
+
seedMeta(this.kv, input2.anonId, now);
|
|
32903
|
+
touchActivity(this.kv, now);
|
|
32904
|
+
return this.runCaptures(input2.apps, site);
|
|
32602
32905
|
});
|
|
32603
32906
|
});
|
|
32604
32907
|
}
|
|
@@ -32619,6 +32922,11 @@ var AnonDO = class extends DurableObject2 {
|
|
|
32619
32922
|
});
|
|
32620
32923
|
});
|
|
32621
32924
|
}
|
|
32925
|
+
async runCaptures(apps, site) {
|
|
32926
|
+
const context = await this.sites.resolve(site, Date.now());
|
|
32927
|
+
const writes = acceptedCaptures(context.registry, consentState2(this.kv), apps, context.consentEnforced);
|
|
32928
|
+
return { accepted: this.storeCaptures(writes) };
|
|
32929
|
+
}
|
|
32622
32930
|
storeCaptures(writes) {
|
|
32623
32931
|
if (writes.length === 0) {
|
|
32624
32932
|
return {};
|
|
@@ -32906,9 +33214,11 @@ var AnonDO = class extends DurableObject2 {
|
|
|
32906
33214
|
return { action: outcome.action, releaseEmissions };
|
|
32907
33215
|
}
|
|
32908
33216
|
appsSnapshot(anonId, context) {
|
|
33217
|
+
const meta3 = storedMeta(this.kv);
|
|
32909
33218
|
return {
|
|
32910
33219
|
anonId,
|
|
32911
|
-
globalId: linkedGlobalId
|
|
33220
|
+
globalId: meta3?.linkedGlobalId ?? null,
|
|
33221
|
+
createdAt: meta3?.createdAt ?? null,
|
|
32912
33222
|
pii: userProperties(this.kv),
|
|
32913
33223
|
// A wipe dropped the sql tables, so reading them would throw — and reviving for a transform
|
|
32914
33224
|
// snapshot would strand recreated storage when the chain fails or drops.
|
|
@@ -33270,24 +33580,13 @@ var AnonDO = class extends DurableObject2 {
|
|
|
33270
33580
|
await this.ctx.storage.setAlarm(target);
|
|
33271
33581
|
this.cleanupAlarm = target;
|
|
33272
33582
|
}
|
|
33273
|
-
|
|
33583
|
+
seedGeoOnConnect(anonId, geo, observedAt) {
|
|
33274
33584
|
const run = this.enqueue(async () => {
|
|
33275
33585
|
this.revive();
|
|
33276
|
-
|
|
33277
|
-
if (latest.length === 0) {
|
|
33278
|
-
return;
|
|
33279
|
-
}
|
|
33280
|
-
const globalId = linkedGlobalId(this.kv);
|
|
33281
|
-
if (globalId === null) {
|
|
33282
|
-
await this.retargetCleanupAlarm(Date.now());
|
|
33283
|
-
return;
|
|
33284
|
-
}
|
|
33285
|
-
recordSyncDebt(this.kv, "ride");
|
|
33586
|
+
updateGeo(this.cachedStorage, geo, observedAt);
|
|
33286
33587
|
await this.retargetCleanupAlarm(Date.now());
|
|
33287
|
-
const snapshot = await this.sync({ globalId, anonId, syncedAt: observedAt, identifiers: [], latest });
|
|
33288
|
-
await this.applyReturned(snapshot, globalId);
|
|
33289
33588
|
});
|
|
33290
|
-
this.ctx.waitUntil(run.catch((error61) => console.warn("geo
|
|
33589
|
+
this.ctx.waitUntil(run.catch((error61) => console.warn("geo seed failed", { anonId, error: error61 })));
|
|
33291
33590
|
}
|
|
33292
33591
|
};
|
|
33293
33592
|
|
|
@@ -33605,6 +33904,7 @@ var GlobalDO = class extends DurableObject3 {
|
|
|
33605
33904
|
var app = createApiRouter();
|
|
33606
33905
|
app.use(requestSpan);
|
|
33607
33906
|
app.use(siteStatusGuard);
|
|
33907
|
+
app.get("/", welcome);
|
|
33608
33908
|
app.get("/open", open2);
|
|
33609
33909
|
app.use("/q.js", cors());
|
|
33610
33910
|
app.get("/q.js", qJs);
|
|
@@ -33616,6 +33916,7 @@ app.use("/v1/*", allowCors);
|
|
|
33616
33916
|
app.use("/v1/*", bodyLimit({ maxSize: MAX_V1_BODY_BYTES, onError: /* @__PURE__ */ __name((c) => c.json({ error: "too_large" }, 413), "onError") }));
|
|
33617
33917
|
app.route("/", alias);
|
|
33618
33918
|
app.route("/", identify);
|
|
33919
|
+
app.route("/", capture);
|
|
33619
33920
|
app.route("/", track);
|
|
33620
33921
|
app.route("/", properties);
|
|
33621
33922
|
app.route("/", consent);
|