@blotout/qinto-cli 0.1.26 → 0.1.28

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.
@@ -21530,11 +21530,16 @@ var MAX_PUSH_BYTES = 16 * 1024;
21530
21530
  var MAX_RESPONSE_BYTES = 1024 * 1024;
21531
21531
  var MAX_RPC_BYTES = 128 * 1024;
21532
21532
  var installSchema = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict();
21533
- var emitSchema = external_exports.object({ type: external_exports.literal("track"), occurredAt: external_exports.number().int().nonnegative().safe(), payload: external_exports.record(external_exports.string(), external_exports.unknown()) }).strict();
21534
21533
  var installRuleSchema = external_exports.object({
21535
21534
  mode: external_exports.enum(["except", "only"]),
21536
21535
  installIds: external_exports.array(external_exports.string().min(1).max(64)).max(MAX_APPS_PER_DOMAIN * MAX_INSTALLS_PER_APP)
21537
21536
  }).strict();
21537
+ var emitSchema = external_exports.object({
21538
+ type: external_exports.literal("track"),
21539
+ occurredAt: external_exports.number().int().nonnegative().safe(),
21540
+ payload: external_exports.record(external_exports.string(), external_exports.unknown()),
21541
+ installs: installRuleSchema.optional()
21542
+ }).strict();
21538
21543
  var pushSchema = external_exports.object({ topic: external_exports.string().min(1).max(64), data: external_exports.unknown(), broadcast: external_exports.enum(PUSH_BROADCASTS).optional() }).strict().refine((push) => {
21539
21544
  return new TextEncoder().encode(JSON.stringify(push.data ?? null)).length <= MAX_PUSH_BYTES;
21540
21545
  }, "Push data exceeds the per-message size cap");
@@ -26513,7 +26518,14 @@ var consentEnforcedFor = /* @__PURE__ */ __name((raw2) => {
26513
26518
  var consentEnforced = /* @__PURE__ */ __name((env) => {
26514
26519
  return consentEnforcedFor(env.CONSENT_CONFIGURATION ?? "");
26515
26520
  }, "consentEnforced");
26516
- var consentAllows = /* @__PURE__ */ __name((consent2, slug, category, enforced) => {
26521
+ var GPC_DENIED_CATEGORIES = ["marketing", "salesOfData"];
26522
+ var gpcDenies = /* @__PURE__ */ __name((category, gpc) => {
26523
+ return gpc && GPC_DENIED_CATEGORIES.includes(category);
26524
+ }, "gpcDenies");
26525
+ var consentAllows = /* @__PURE__ */ __name((consent2, slug, category, { enforced, gpc }) => {
26526
+ if (gpcDenies(category, gpc)) {
26527
+ return false;
26528
+ }
26517
26529
  if (!enforced) {
26518
26530
  return true;
26519
26531
  }
@@ -27585,7 +27597,7 @@ var trackBody = external_exports.object({
27585
27597
  userId: external_exports.string().min(1).max(64),
27586
27598
  installId: external_exports.string().min(1).max(64),
27587
27599
  installs: installRuleSchema.optional(),
27588
- event: emitSchema
27600
+ event: emitSchema.omit({ installs: true })
27589
27601
  }).strict();
27590
27602
  var handleTrack = /* @__PURE__ */ __name(async (env, body) => {
27591
27603
  const app2 = siteRegistry().registry.apps[body.slug];
@@ -27785,14 +27797,14 @@ var allContext = /* @__PURE__ */ __name((sql, level, cache) => {
27785
27797
 
27786
27798
  // src/apps/context-visibility.ts
27787
27799
  var SHARED_CONTEXT_CAPABILITY = "sharedContext";
27788
- var readableSharedContext = /* @__PURE__ */ __name((registry3, consent2, enforced, stored) => {
27800
+ var readableSharedContext = /* @__PURE__ */ __name((registry3, consent2, policy, stored) => {
27789
27801
  const readable = {};
27790
27802
  for (const [slug, entry] of Object.entries(stored)) {
27791
27803
  const app2 = registry3.apps[slug];
27792
27804
  if (app2 === void 0) {
27793
27805
  continue;
27794
27806
  }
27795
- if (!app2.data.includes(SHARED_CONTEXT_CAPABILITY) || !consentAllows(consent2, slug, app2.consent.category, enforced)) {
27807
+ if (!app2.data.includes(SHARED_CONTEXT_CAPABILITY) || !consentAllows(consent2, slug, app2.consent.category, policy)) {
27796
27808
  continue;
27797
27809
  }
27798
27810
  readable[slug] = entry;
@@ -27804,7 +27816,7 @@ var siteEventReceivers = /* @__PURE__ */ __name((registry3, consent2, enforced)
27804
27816
  return app2.data.includes(SHARED_CONTEXT_CAPABILITY) && app2.installs.some((install) => install.active) && // The receiver's own consent, resolved here rather than left to the page: the tag's
27805
27817
  // `state.consent` does not see a revocation made on another device (apps.ts), so a browser
27806
27818
  // recheck alone would keep notifying an app the visitor has since turned off.
27807
- consentAllows(consent2, slug, app2.consent.category, enforced);
27819
+ consentAllows(consent2, slug, app2.consent.category, { enforced, gpc: false });
27808
27820
  }).map(([slug]) => slug);
27809
27821
  }, "siteEventReceivers");
27810
27822
  var sharedContextWire = /* @__PURE__ */ __name((entries) => {
@@ -28542,7 +28554,8 @@ var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context)
28542
28554
  const buckets = target.app.data;
28543
28555
  const geoAllowed = buckets.includes("geo");
28544
28556
  const currentGeo = trigger.kind === "event" ? trigger.action.geo : trigger.geo;
28545
- const consented = consentAllows(snapshot.consent, target.slug, target.app.consent.category, context.consentEnforced);
28557
+ const gpc = trigger.kind === "event" ? trigger.action.gpc === true : trigger.gpc === true;
28558
+ const consented = consentAllows(snapshot.consent, target.slug, target.app.consent.category, { enforced: context.consentEnforced, gpc });
28546
28559
  const captures = consented ? deliverableCaptures(target, snapshot.captures[target.slug]) : void 0;
28547
28560
  const visitorContext = deliverableContext({ target, consented, ...snapshot.context });
28548
28561
  return {
@@ -28571,36 +28584,38 @@ var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context)
28571
28584
  ...trigger.kind === "event" ? { action: eventAction(target, trigger, snapshot) } : {}
28572
28585
  };
28573
28586
  }, "buildEnvelope");
28574
- var ingestEmit = /* @__PURE__ */ __name(async (ingest2, { slug, ord, depth, emit, anonId, geo, host, context, fromSocket }) => {
28587
+ var emitContext = /* @__PURE__ */ __name((occurredAt, context, host) => ({
28588
+ ...context ?? {},
28589
+ source: "app",
28590
+ occurredAt,
28591
+ ...context !== void 0 || host === void 0 ? {} : {
28592
+ network: {
28593
+ ...host.ip === void 0 ? {} : { ip: host.ip },
28594
+ ...host.userAgent === void 0 ? {} : { userAgent: host.userAgent },
28595
+ ...host.locale === void 0 ? {} : { locale: host.locale },
28596
+ provenance: "edge"
28597
+ }
28598
+ },
28599
+ offline: context === void 0 || context.source === "browser" ? false : context.offline,
28600
+ replay: context === void 0 || context.source === "browser" ? false : context.replay
28601
+ }), "emitContext");
28602
+ var ingestEmit = /* @__PURE__ */ __name(async (ingest2, { slug, ord, depth, emit, anonId, geo, host, context, fromSocket, gpc }) => {
28575
28603
  const parsed = emitPayloadSchema.safeParse(emit.payload);
28576
28604
  if (!parsed.success) {
28577
28605
  addSpanEvent("emit payload rejected", { level: "warn", attrs: { app: slug, ord } });
28578
28606
  return;
28579
28607
  }
28580
28608
  const { event, eventId, properties: properties2 } = parsed.data;
28581
- const inherited = {
28582
- ...context ?? {},
28583
- source: "app",
28584
- occurredAt: emit.occurredAt,
28585
- ...context !== void 0 || host === void 0 ? {} : {
28586
- network: {
28587
- ...host.ip === void 0 ? {} : { ip: host.ip },
28588
- ...host.userAgent === void 0 ? {} : { userAgent: host.userAgent },
28589
- ...host.locale === void 0 ? {} : { locale: host.locale },
28590
- provenance: "edge"
28591
- }
28592
- },
28593
- offline: context === void 0 || context.source === "browser" ? false : context.offline,
28594
- replay: context === void 0 || context.source === "browser" ? false : context.replay
28595
- };
28596
28609
  const action = buildAction({
28597
28610
  type: "track",
28598
28611
  userId: anonId,
28599
28612
  source: "app",
28600
28613
  payload: { event, eventId: emittedEventId(eventId, properties2), properties: properties2 ?? {} },
28601
- context: inherited,
28614
+ context: emitContext(emit.occurredAt, context, host),
28602
28615
  origin: { app: slug, install: ord, depth: depth + 1 },
28616
+ ...emit.installs === void 0 ? {} : { installs: emit.installs },
28603
28617
  fromSocket,
28618
+ gpc,
28604
28619
  geo,
28605
28620
  host
28606
28621
  });
@@ -28645,7 +28660,8 @@ var applyEffects = /* @__PURE__ */ __name(async ({
28645
28660
  geo,
28646
28661
  host,
28647
28662
  context,
28648
- fromSocket
28663
+ fromSocket,
28664
+ gpc
28649
28665
  }) => {
28650
28666
  const captures = groups.flatMap((group) => group.captures);
28651
28667
  if (captures.length > 0) {
@@ -28665,7 +28681,7 @@ var applyEffects = /* @__PURE__ */ __name(async ({
28665
28681
  continue;
28666
28682
  }
28667
28683
  for (const emit of group.emits) {
28668
- await ingestEmit(ingest2, { slug, ord: group.ord, depth, emit, anonId, geo, host, context, fromSocket });
28684
+ await ingestEmit(ingest2, { slug, ord: group.ord, depth, emit, anonId, geo, host, context, fromSocket, gpc });
28669
28685
  }
28670
28686
  }
28671
28687
  }, "applyEffects");
@@ -28727,6 +28743,7 @@ var runInvokeApp = /* @__PURE__ */ __name(async (env, { target, trigger, snapsho
28727
28743
  geo: trigger.kind === "event" ? trigger.action.geo : trigger.geo,
28728
28744
  host: trigger.kind === "event" ? trigger.action.host : trigger.host,
28729
28745
  context: trigger.kind === "event" ? trigger.action.context : void 0,
28746
+ gpc: trigger.kind === "event" ? trigger.action.gpc : trigger.gpc,
28730
28747
  ...startedOnSocket(trigger) ? { fromSocket: true } : {}
28731
28748
  });
28732
28749
  } catch (error61) {
@@ -29810,6 +29827,9 @@ var originAllowed = /* @__PURE__ */ __name((request, env) => {
29810
29827
  if (origin === null) {
29811
29828
  return true;
29812
29829
  }
29830
+ if (JSON.parse(env.ALLOWED_ORIGINS).includes(origin)) {
29831
+ return true;
29832
+ }
29813
29833
  let host;
29814
29834
  try {
29815
29835
  host = new URL(origin).host;
@@ -29852,6 +29872,11 @@ var authorize = /* @__PURE__ */ __name(async (c, { slug, app: app2 }) => {
29852
29872
  if (install === void 0) {
29853
29873
  return refuse(403, "forbidden");
29854
29874
  }
29875
+ const gpc = c.env.GPC === "1" && c.req.header("sec-gpc") === "1";
29876
+ if (gpcDenies(app2.consent.category, gpc)) {
29877
+ addSpanEvent("visitor endpoint refused: Global Privacy Control", { attrs: { app: slug } });
29878
+ return refuse(403, "forbidden");
29879
+ }
29855
29880
  if (await rateLimited(c.env, `${slug}:${anonId}:visitor`, "VISITOR_RATE_LIMITER")) {
29856
29881
  return refuse(429, "rate_limited");
29857
29882
  }
@@ -30626,6 +30651,7 @@ var createApiRouter = /* @__PURE__ */ __name(() => new OpenAPIHono({ defaultHook
30626
30651
  // src/id-graph/anon/connection.ts
30627
30652
  var CONNECTION_CF_HEADER = "x-connection-cf";
30628
30653
  var SESSION_HEADER = "x-session";
30654
+ var GPC_HEADER = "x-gpc";
30629
30655
  var OPEN_PAGE_PARAMS = ["url", "referrer"];
30630
30656
  var MAX_HOST_USER_AGENT_LENGTH = 512;
30631
30657
  var MAX_HOST_LOCALE_LENGTH = 64;
@@ -30636,6 +30662,7 @@ var INTERNAL_HEADERS = /* @__PURE__ */ new Set([
30636
30662
  SITE_ID_HEADER,
30637
30663
  CONNECTION_CF_HEADER,
30638
30664
  SESSION_HEADER,
30665
+ GPC_HEADER,
30639
30666
  TRACE_HEADER,
30640
30667
  CF_DATA_HEADER
30641
30668
  ]);
@@ -30691,7 +30718,8 @@ var connectAttrs = /* @__PURE__ */ __name((request, geo) => {
30691
30718
  "url.full": withoutPageParams(request.url),
30692
30719
  "http.request.headers": JSON.stringify(headers),
30693
30720
  ...cf === void 0 ? {} : { "qinto.cf": JSON.stringify(cf) },
30694
- ...geo === void 0 ? {} : { "qinto.geo": JSON.stringify(loggableGeo(geo)) }
30721
+ ...geo === void 0 ? {} : { "qinto.geo": JSON.stringify(loggableGeo(geo)) },
30722
+ ...request.headers.get(GPC_HEADER) === "1" ? { "qinto.gpc": true } : {}
30695
30723
  };
30696
30724
  }, "connectAttrs");
30697
30725
  var withoutQuery = /* @__PURE__ */ __name((url2) => {
@@ -30737,12 +30765,14 @@ var connectionAttachment = /* @__PURE__ */ __name((request, geo, siteId) => {
30737
30765
  const browserGeoBytes = attachmentFieldBytes("browserGeo", browserGeo);
30738
30766
  const session2 = forwardedSession(request);
30739
30767
  const sessionBytes = attachmentFieldBytes("session", session2);
30768
+ const gpc = request.headers.get(GPC_HEADER) === "1";
30769
+ const gpcBytes = attachmentFieldBytes("gpc", gpc ? true : void 0);
30740
30770
  const siteBytes = attachmentFieldBytes("siteId", siteId);
30741
30771
  const cf = forwardedCf(request);
30742
30772
  const cfSuffix = cf === void 0 ? "" : `,"cf":${JSON.stringify(cf)}`;
30743
30773
  const cfBytes = new TextEncoder().encode(cfSuffix).length;
30744
30774
  const emptyAttrsBytes = new TextEncoder().encode(JSON.stringify({ attrs: {} })).length;
30745
- const fixedBytes = siteBytes + sessionBytes + hostBytes + browserGeoBytes;
30775
+ const fixedBytes = siteBytes + sessionBytes + hostBytes + browserGeoBytes + gpcBytes;
30746
30776
  const includedCf = cfBytes <= MAX_ATTACHMENT_BYTES - emptyAttrsBytes - fixedBytes ? cf : void 0;
30747
30777
  const budgeted = budgetAttrs(attrs, MAX_ATTACHMENT_BYTES - fixedBytes - (includedCf === void 0 ? 0 : cfBytes));
30748
30778
  return {
@@ -30751,7 +30781,8 @@ var connectionAttachment = /* @__PURE__ */ __name((request, geo, siteId) => {
30751
30781
  ...session2 === void 0 ? {} : { session: session2 },
30752
30782
  ...browserGeo === void 0 ? {} : { browserGeo },
30753
30783
  ...includedCf === void 0 ? {} : { cf: includedCf },
30754
- ...host === void 0 ? {} : { host }
30784
+ ...host === void 0 ? {} : { host },
30785
+ ...gpc ? { gpc: true } : {}
30755
30786
  };
30756
30787
  }, "connectionAttachment");
30757
30788
 
@@ -31191,6 +31222,12 @@ var open2 = /* @__PURE__ */ __name(async (c) => {
31191
31222
  if (cf !== void 0) {
31192
31223
  headers.set(CONNECTION_CF_HEADER, cf);
31193
31224
  }
31225
+ headers.delete(GPC_HEADER);
31226
+ const gpc = c.env.GPC === "1" && c.req.header("sec-gpc") === "1";
31227
+ if (gpc) {
31228
+ headers.set(GPC_HEADER, "1");
31229
+ setSpanAttributes({ "qinto.gpc": true });
31230
+ }
31194
31231
  const res = await traced("rpc anon.connect", {}, () => {
31195
31232
  headers.delete(TRACE_HEADER);
31196
31233
  const trace = currentTraceContext();
@@ -31295,12 +31332,12 @@ var appsMetadata = /* @__PURE__ */ __name(() => {
31295
31332
  })
31296
31333
  );
31297
31334
  }, "appsMetadata");
31298
- var configPrelude = /* @__PURE__ */ __name(({ destination, autoCapture, consentConfiguration, apps }) => {
31299
- const config2 = JSON.stringify({ destination, autoCapture, consentConfiguration, apps });
31335
+ var configPrelude = /* @__PURE__ */ __name(({ destination, autoCapture, consentConfiguration, gpc, apps }) => {
31336
+ const config2 = JSON.stringify({ destination, autoCapture, consentConfiguration, gpc, apps });
31300
31337
  return '(function(d){var s=d.currentScript,g=(s&&s.dataset?s.dataset.qintoGlobal:void 0)||"qinto";var w=window,o=g in w?w[g]:null;if(o&&o._q){o.config=' + config2 + ';}else if(g in w){console.error("window."+g+" is already in use");}else{Object.defineProperty(w,g,{value:{_q:1,config:' + config2 + "},writable:true,enumerable:true,configurable:true});}})(document);\n";
31301
31338
  }, "configPrelude");
31302
- var scriptEtag = /* @__PURE__ */ __name(async ({ destination, autoCapture, consentConfiguration, apps }, withTag) => {
31303
- const settings = await digestFor(JSON.stringify({ autoCapture, consentConfiguration, apps }));
31339
+ var scriptEtag = /* @__PURE__ */ __name(async ({ destination, autoCapture, consentConfiguration, gpc, apps }, withTag) => {
31340
+ const settings = await digestFor(JSON.stringify({ autoCapture, consentConfiguration, gpc, apps }));
31304
31341
  const tag = withTag ? meta_default.hash.slice(0, 32) : "sdk";
31305
31342
  return `"${tag}-${appsQHash}-${encodeURIComponent(destination)}-${settings}"`;
31306
31343
  }, "scriptEtag");
@@ -31311,6 +31348,8 @@ var qJs = /* @__PURE__ */ __name(async (c) => {
31311
31348
  destination,
31312
31349
  autoCapture: parseSettings("AUTO_CAPTURE", c.env.AUTO_CAPTURE),
31313
31350
  consentConfiguration: parseSettings("CONSENT_CONFIGURATION", c.env.CONSENT_CONFIGURATION),
31351
+ // `wrangler types` narrows GPC to its dev default; a site not redeployed since GPC shipped has none.
31352
+ gpc: c.env.GPC === "1",
31314
31353
  apps: appsMetadata()
31315
31354
  };
31316
31355
  const etag = await scriptEtag(config2, withTag);
@@ -31791,14 +31830,14 @@ var wantsTrigger = /* @__PURE__ */ __name((app2, trigger) => {
31791
31830
  }
31792
31831
  return app2.hooks.onEvent.includes(trigger.action.type);
31793
31832
  }, "wantsTrigger");
31794
- var gatePasses = /* @__PURE__ */ __name((entry, trigger, consent2, enforced) => {
31833
+ var gatePasses = /* @__PURE__ */ __name((entry, trigger, consent2, policy) => {
31795
31834
  if (trigger.kind === "event" && trigger.action.origin?.app === entry.slug) {
31796
31835
  return false;
31797
31836
  }
31798
31837
  if (trigger.kind === "event" && trigger.action.type === "consent") {
31799
- return true;
31838
+ return !gpcDenies(entry.app.consent.category, policy.gpc);
31800
31839
  }
31801
- return consentAllows(consent2, entry.slug, entry.app.consent.category, enforced);
31840
+ return consentAllows(consent2, entry.slug, entry.app.consent.category, policy);
31802
31841
  }, "gatePasses");
31803
31842
  var eligibleApps = /* @__PURE__ */ __name((registry3, trigger, consent2, enforced) => {
31804
31843
  const location = visitorLocation(trigger);
@@ -31807,9 +31846,17 @@ var eligibleApps = /* @__PURE__ */ __name((registry3, trigger, consent2, enforce
31807
31846
  const keepListed = rule?.mode === "only";
31808
31847
  const targets = [];
31809
31848
  const geoSkipped = [];
31849
+ const gpcSkipped = [];
31850
+ const gpc = trigger.kind === "connect" ? trigger.gpc === true : trigger.action.gpc === true;
31810
31851
  for (const slug of registry3.order) {
31811
31852
  const app2 = registry3.apps[slug];
31812
- if (app2 === void 0 || !wantsTrigger(app2, trigger) || !gatePasses({ slug, app: app2 }, trigger, consent2, enforced)) {
31853
+ if (app2 === void 0 || !wantsTrigger(app2, trigger)) {
31854
+ continue;
31855
+ }
31856
+ if (!gatePasses({ slug, app: app2 }, trigger, consent2, { enforced, gpc })) {
31857
+ if (gpc && gatePasses({ slug, app: app2 }, trigger, consent2, { enforced, gpc: false })) {
31858
+ gpcSkipped.push(slug);
31859
+ }
31813
31860
  continue;
31814
31861
  }
31815
31862
  const { runs, skipped } = geoRunnableInstalls(slug, app2, location);
@@ -31820,20 +31867,21 @@ var eligibleApps = /* @__PURE__ */ __name((registry3, trigger, consent2, enforce
31820
31867
  }
31821
31868
  targets.push({ slug, app: app2, installs });
31822
31869
  }
31823
- return { targets, geoSkipped };
31870
+ return { targets, geoSkipped, gpcSkipped };
31824
31871
  }, "eligibleApps");
31825
31872
 
31826
31873
  // src/apps/fan-out.ts
31827
31874
  var fanOutToApps = /* @__PURE__ */ __name(async (env, { trigger, snapshot, trace }, { deliverEffects }) => {
31828
31875
  const { registry: registry3, error: error61 } = siteRegistry();
31829
- const { targets, geoSkipped } = eligibleApps(registry3, trigger, snapshot.consent, consentEnforced(env));
31830
- if (error61 === void 0 && targets.length === 0 && geoSkipped.length === 0) {
31876
+ const { targets, geoSkipped, gpcSkipped } = eligibleApps(registry3, trigger, snapshot.consent, consentEnforced(env));
31877
+ if (error61 === void 0 && targets.length === 0 && geoSkipped.length === 0 && gpcSkipped.length === 0) {
31831
31878
  return;
31832
31879
  }
31833
31880
  const attrs = {
31834
31881
  "fan_out.kind": trigger.kind,
31835
31882
  "fan_out.apps": targets.length,
31836
- ...geoSkipped.length === 0 ? {} : { "fan_out.geo_skipped": geoSkipped.join(",") }
31883
+ ...geoSkipped.length === 0 ? {} : { "fan_out.geo_skipped": geoSkipped.join(",") },
31884
+ ...gpcSkipped.length === 0 ? {} : { "fan_out.gpc_skipped": gpcSkipped.join(",") }
31837
31885
  };
31838
31886
  await runInvocation({ name: "app.fan_out", trace, attrs }, async () => {
31839
31887
  if (error61 !== void 0) {
@@ -31912,18 +31960,23 @@ var chainEntries = /* @__PURE__ */ __name((registry3, action) => {
31912
31960
  const location = visitorLocation({ kind: "event", action });
31913
31961
  const entries = [];
31914
31962
  const geoSkipped = [];
31963
+ const gpcSkipped = [];
31915
31964
  for (const slug of registry3.order) {
31916
31965
  const app2 = registry3.apps[slug];
31917
31966
  if (app2 === void 0 || !app2.hooks.transform.includes(action.type)) {
31918
31967
  continue;
31919
31968
  }
31969
+ if (gpcDenies(app2.consent.category, action.gpc === true)) {
31970
+ gpcSkipped.push(slug);
31971
+ continue;
31972
+ }
31920
31973
  const { runs, skipped } = geoRunnableInstalls(slug, app2, location);
31921
31974
  geoSkipped.push(...skipped);
31922
31975
  for (const install of runs) {
31923
31976
  entries.push({ slug, app: app2, install: { id: install.id, ord: install.ord, label: install.label } });
31924
31977
  }
31925
31978
  }
31926
- return { entries, geoSkipped };
31979
+ return { entries, geoSkipped, gpcSkipped };
31927
31980
  }, "chainEntries");
31928
31981
  var trackPayloadSchema = external_exports.object({
31929
31982
  event: external_exports.string().min(1),
@@ -32056,6 +32109,7 @@ var buildEffectsRelease = /* @__PURE__ */ __name(({ effects, action, deliveries
32056
32109
  geo: action.geo,
32057
32110
  host: action.host,
32058
32111
  context: action.context,
32112
+ gpc: action.gpc,
32059
32113
  ...startedOnSocket({ kind: "event", action }) ? { fromSocket: true } : {}
32060
32114
  });
32061
32115
  }
@@ -32066,13 +32120,14 @@ var runTransformChain = /* @__PURE__ */ __name(async (env, { action, snapshot, d
32066
32120
  if (error61 !== void 0) {
32067
32121
  throw new TransformChainError(`the apps registry is malformed: ${error61}`);
32068
32122
  }
32069
- const { entries, geoSkipped } = chainEntries(registry3, action);
32123
+ const { entries, geoSkipped, gpcSkipped } = chainEntries(registry3, action);
32070
32124
  const attrs = {
32071
32125
  "transform.entries": entries.length,
32072
- ...geoSkipped.length === 0 ? {} : { "transform.geo_skipped": geoSkipped.join(",") }
32126
+ ...geoSkipped.length === 0 ? {} : { "transform.geo_skipped": geoSkipped.join(",") },
32127
+ ...gpcSkipped.length === 0 ? {} : { "transform.gpc_skipped": gpcSkipped.join(",") }
32073
32128
  };
32074
32129
  if (entries.length === 0) {
32075
- if (geoSkipped.length > 0) {
32130
+ if (geoSkipped.length > 0 || gpcSkipped.length > 0) {
32076
32131
  await traced("apps.transform_chain", { attrs }, () => Promise.resolve());
32077
32132
  }
32078
32133
  return { dropped: false, action, effects: [] };
@@ -32326,7 +32381,7 @@ var allCaptures = /* @__PURE__ */ __name((sql, now, cache) => {
32326
32381
  }, "allCaptures");
32327
32382
 
32328
32383
  // src/apps/capture.ts
32329
- var appWriteAllowed = /* @__PURE__ */ __name((registry3, slug, { consent: consent2, enforced, location }) => {
32384
+ var appWriteAllowed = /* @__PURE__ */ __name((registry3, slug, { consent: consent2, enforced, gpc, location }) => {
32330
32385
  const app2 = registry3.apps[slug];
32331
32386
  if (app2 === void 0) {
32332
32387
  addSpanEvent("capture dropped: the app is not in the registry", { level: "warn", attrs: { app: slug } });
@@ -32336,7 +32391,7 @@ var appWriteAllowed = /* @__PURE__ */ __name((registry3, slug, { consent: consen
32336
32391
  addSpanEvent("capture dropped: no install runs for this visitor", { level: "warn", attrs: { app: slug } });
32337
32392
  return false;
32338
32393
  }
32339
- if (!consentAllows(consent2, slug, app2.consent.category, enforced)) {
32394
+ if (!consentAllows(consent2, slug, app2.consent.category, { enforced, gpc })) {
32340
32395
  addSpanEvent("capture dropped by the consent gate", { attrs: { app: slug } });
32341
32396
  return false;
32342
32397
  }
@@ -33139,7 +33194,7 @@ var appPushFrame = /* @__PURE__ */ __name(({ push, context, consent: consent2 })
33139
33194
  addSpanEvent("app push dropped: the app is not in the registry", { level: "warn", attrs: { app: slug } });
33140
33195
  return null;
33141
33196
  }
33142
- if (!consentAllows(consent2, slug, app2.consent.category, enforced)) {
33197
+ if (!consentAllows(consent2, slug, app2.consent.category, { enforced, gpc: false })) {
33143
33198
  addSpanEvent("app push dropped by the consent gate", { attrs: { app: slug } });
33144
33199
  return null;
33145
33200
  }
@@ -33167,10 +33222,18 @@ var appPushFrame = /* @__PURE__ */ __name(({ push, context, consent: consent2 })
33167
33222
  }
33168
33223
  return frame;
33169
33224
  }, "appPushFrame");
33170
- var connectionPushFrame = /* @__PURE__ */ __name((frame, registry3, location) => {
33225
+ var connectionPushFrame = /* @__PURE__ */ __name((frame, registry3, location, gpc) => {
33226
+ const sender = registry3.apps[frame.app];
33227
+ if (sender !== void 0 && gpcDenies(sender.consent.category, gpc)) {
33228
+ addSpanEvent("app push skipped: Global Privacy Control", { attrs: { app: frame.app } });
33229
+ return null;
33230
+ }
33171
33231
  const runningApps = /* @__PURE__ */ new Set();
33172
33232
  const runningInstalls = /* @__PURE__ */ new Set();
33173
33233
  for (const [slug, app2] of Object.entries(registry3.apps)) {
33234
+ if (gpcDenies(app2.consent.category, gpc)) {
33235
+ continue;
33236
+ }
33174
33237
  for (const install of geoRunnableInstalls(slug, app2, location).runs) {
33175
33238
  runningApps.add(slug);
33176
33239
  runningInstalls.add(install.id);
@@ -33183,8 +33246,8 @@ var connectionPushFrame = /* @__PURE__ */ __name((frame, registry3, location) =>
33183
33246
  const reaches = frame.install === void 0 ? runningApps.has(frame.app) : runningInstalls.has(frame.install);
33184
33247
  return reaches ? JSON.stringify(frame) : null;
33185
33248
  }, "connectionPushFrame");
33186
- var sharedContextFrame = /* @__PURE__ */ __name((context, consent2, stored) => {
33187
- const readable = readableSharedContext(context.registry, consent2, context.consentEnforced, stored);
33249
+ var sharedContextFrame = /* @__PURE__ */ __name((context, consent2, stored, gpc) => {
33250
+ const readable = readableSharedContext(context.registry, consent2, { enforced: context.consentEnforced, gpc }, stored);
33188
33251
  return JSON.stringify({ t: "sharedContext", context: sharedContextWire(readable), ...consent2 });
33189
33252
  }, "sharedContextFrame");
33190
33253
  var socketsBySite = /* @__PURE__ */ __name((ctx) => {
@@ -33454,7 +33517,7 @@ var browserActionContext = /* @__PURE__ */ __name((frame, host, session2) => {
33454
33517
  return context;
33455
33518
  }, "browserActionContext");
33456
33519
  var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) => {
33457
- const { cf: geo, host, session: session2 } = connection;
33520
+ const { cf: geo, host, session: session2, gpc } = connection;
33458
33521
  switch (frame.t) {
33459
33522
  case "identify": {
33460
33523
  const identifier = frame.id === void 0 ? void 0 : classifyIdentifier(frame.id);
@@ -33467,7 +33530,8 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
33467
33530
  context: browserActionContext(frame.context, host, session2),
33468
33531
  browserResults: frame.appResults,
33469
33532
  geo,
33470
- host
33533
+ host,
33534
+ gpc
33471
33535
  });
33472
33536
  }
33473
33537
  case "track": {
@@ -33480,11 +33544,12 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
33480
33544
  browserResults: frame.appResults,
33481
33545
  geo,
33482
33546
  host,
33547
+ gpc,
33483
33548
  ...frame.installs === void 0 ? {} : { installs: frame.installs }
33484
33549
  });
33485
33550
  }
33486
33551
  case "alias": {
33487
- return buildAction({ type: "alias", userId, source: "website", payload: { alias: frame.alias }, geo, host });
33552
+ return buildAction({ type: "alias", userId, source: "website", payload: { alias: frame.alias }, geo, host, gpc });
33488
33553
  }
33489
33554
  case "consent": {
33490
33555
  return buildAction({
@@ -33494,7 +33559,8 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
33494
33559
  payload: { categories: frame.categories ?? {}, apps: frame.apps ?? {} },
33495
33560
  context: browserActionContext(frame.context, host, session2),
33496
33561
  geo,
33497
- host
33562
+ host,
33563
+ gpc
33498
33564
  });
33499
33565
  }
33500
33566
  }
@@ -33575,7 +33641,8 @@ var AnonDO = class extends DurableObject2 {
33575
33641
  kind: "connect",
33576
33642
  ...attachment.cf === void 0 ? {} : { geo: attachment.cf },
33577
33643
  host: attachment.host,
33578
- ...page === void 0 ? {} : { page }
33644
+ ...page === void 0 ? {} : { page },
33645
+ gpc: attachment.gpc
33579
33646
  },
33580
33647
  userId,
33581
33648
  context
@@ -33616,7 +33683,10 @@ var AnonDO = class extends DurableObject2 {
33616
33683
  return null;
33617
33684
  }
33618
33685
  if (frame.t === "capture") {
33619
- const { accepted } = await this.capture({ apps: frame.apps, location: socketLocation(attachment.cf) }, site);
33686
+ const { accepted } = await this.capture(
33687
+ { apps: frame.apps, location: socketLocation(attachment.cf), gpc: attachment.gpc === true },
33688
+ site
33689
+ );
33620
33690
  this.respond(ws, { t: "captureAck", ref, anonId: userId, accepted });
33621
33691
  return null;
33622
33692
  }
@@ -33762,7 +33832,7 @@ var AnonDO = class extends DurableObject2 {
33762
33832
  addSpanEvent("capture dropped: no visitor state", { level: "warn" });
33763
33833
  return { accepted: {} };
33764
33834
  }
33765
- return this.runCaptures(input2.apps, site, input2.location);
33835
+ return this.runCaptures(input2.apps, site, { location: input2.location, gpc: input2.gpc });
33766
33836
  });
33767
33837
  });
33768
33838
  }
@@ -33776,7 +33846,7 @@ var AnonDO = class extends DurableObject2 {
33776
33846
  this.cleanupAlarm = await seedCleanupAlarm(this.ctx.storage, now, lastActivityAt(this.kv) ?? now, this.cleanupAlarm);
33777
33847
  seedMeta(this.kv, input2.anonId, now);
33778
33848
  touchActivity(this.kv, now);
33779
- return this.runCaptures(input2.apps, site, null);
33849
+ return this.runCaptures(input2.apps, site, { location: null, gpc: false });
33780
33850
  });
33781
33851
  });
33782
33852
  }
@@ -33793,6 +33863,7 @@ var AnonDO = class extends DurableObject2 {
33793
33863
  if (!appWriteAllowed(context.registry, input2.slug, {
33794
33864
  consent: consentState2(this.kv),
33795
33865
  enforced: context.consentEnforced,
33866
+ gpc: false,
33796
33867
  location: null
33797
33868
  })) {
33798
33869
  return;
@@ -33805,9 +33876,14 @@ var AnonDO = class extends DurableObject2 {
33805
33876
  });
33806
33877
  });
33807
33878
  }
33808
- async runCaptures(apps, site, location) {
33879
+ async runCaptures(apps, site, { location, gpc }) {
33809
33880
  const context = await this.sites.resolve(site, Date.now());
33810
- const writes = acceptedCaptures(context.registry, apps, { consent: consentState2(this.kv), enforced: context.consentEnforced, location });
33881
+ const writes = acceptedCaptures(context.registry, apps, {
33882
+ consent: consentState2(this.kv),
33883
+ enforced: context.consentEnforced,
33884
+ gpc,
33885
+ location
33886
+ });
33811
33887
  return { accepted: this.storeCaptures(writes) };
33812
33888
  }
33813
33889
  storeCaptures(writes) {
@@ -33836,7 +33912,12 @@ var AnonDO = class extends DurableObject2 {
33836
33912
  }
33837
33913
  const context = await this.sites.resolve(site, Date.now());
33838
33914
  const { registry: registry3 } = context;
33839
- if (!appWriteAllowed(registry3, input2.slug, { consent: consentState2(this.kv), enforced: context.consentEnforced, location: null })) {
33915
+ if (!appWriteAllowed(registry3, input2.slug, {
33916
+ consent: consentState2(this.kv),
33917
+ enforced: context.consentEnforced,
33918
+ gpc: false,
33919
+ location: null
33920
+ })) {
33840
33921
  return [];
33841
33922
  }
33842
33923
  const granted = registry3.apps[input2.slug]?.data.includes(SHARED_CONTEXT_CAPABILITY) === true;
@@ -33883,9 +33964,11 @@ var AnonDO = class extends DurableObject2 {
33883
33964
  // sent even when there is nothing stored: without that baseline a browser app cannot tell an empty
33884
33965
  // context from one that has not arrived yet.
33885
33966
  sendSharedContext(sockets, context) {
33886
- const serialized = sharedContextFrame(context, consentState2(this.kv), allContext(this.sql, "shared", this.contextMirror));
33967
+ const consent2 = consentState2(this.kv);
33968
+ const stored = allContext(this.sql, "shared", this.contextMirror);
33887
33969
  for (const socket of sockets) {
33888
- sendFrame(socket, serialized);
33970
+ const { gpc } = socket.deserializeAttachment();
33971
+ sendFrame(socket, sharedContextFrame(context, consent2, stored, gpc === true));
33889
33972
  }
33890
33973
  }
33891
33974
  // Every open socket, each under its own site's registry and consent policy. A site whose context
@@ -33926,10 +34009,10 @@ var AnonDO = class extends DurableObject2 {
33926
34009
  }
33927
34010
  const consent2 = consentState2(this.kv);
33928
34011
  const enforced = context.consentEnforced;
33929
- if (!consentAllows(consent2, slug, app2.consent.category, enforced)) {
34012
+ if (!consentAllows(consent2, slug, app2.consent.category, { enforced, gpc: false })) {
33930
34013
  return readableContext(void 0, {});
33931
34014
  }
33932
- const shared = app2.data.includes(SHARED_CONTEXT_CAPABILITY) ? readableSharedContext(registry3, consent2, enforced, allContext(this.sql, "shared", this.contextMirror)) : {};
34015
+ const shared = app2.data.includes(SHARED_CONTEXT_CAPABILITY) ? readableSharedContext(registry3, consent2, { enforced, gpc: false }, allContext(this.sql, "shared", this.contextMirror)) : {};
33933
34016
  return readableContext(contextEntry(this.sql, "private", slug, this.contextMirror), shared);
33934
34017
  });
33935
34018
  });
@@ -33944,7 +34027,7 @@ var AnonDO = class extends DurableObject2 {
33944
34027
  if (unused || app2 === void 0 || !app2.data.includes("facts")) {
33945
34028
  return null;
33946
34029
  }
33947
- if (!consentAllows(consentState2(this.kv), slug, app2.consent.category, context.consentEnforced)) {
34030
+ if (!consentAllows(consentState2(this.kv), slug, app2.consent.category, { enforced: context.consentEnforced, gpc: false })) {
33948
34031
  return null;
33949
34032
  }
33950
34033
  if (this.wiped) {
@@ -33963,7 +34046,7 @@ var AnonDO = class extends DurableObject2 {
33963
34046
  if (app2 === void 0) {
33964
34047
  return false;
33965
34048
  }
33966
- return consentAllows(consentState2(this.kv), slug, app2.consent.category, context.consentEnforced);
34049
+ return consentAllows(consentState2(this.kv), slug, app2.consent.category, { enforced: context.consentEnforced, gpc: false });
33967
34050
  });
33968
34051
  });
33969
34052
  }
@@ -33975,8 +34058,8 @@ var AnonDO = class extends DurableObject2 {
33975
34058
  return;
33976
34059
  }
33977
34060
  for (const socket of this.ctx.getWebSockets(context.domainId)) {
33978
- const { cf } = socket.deserializeAttachment();
33979
- const serialized = connectionPushFrame(frame, context.registry, socketLocation(cf));
34061
+ const { cf, gpc } = socket.deserializeAttachment();
34062
+ const serialized = connectionPushFrame(frame, context.registry, socketLocation(cf), gpc === true);
33980
34063
  if (serialized !== null) {
33981
34064
  sendFrame(socket, serialized);
33982
34065
  }
@@ -34122,7 +34205,7 @@ var AnonDO = class extends DurableObject2 {
34122
34205
  shared: readableSharedContext(
34123
34206
  context.registry,
34124
34207
  consentState2(this.kv),
34125
- context.consentEnforced,
34208
+ { enforced: context.consentEnforced, gpc: false },
34126
34209
  allContext(this.sql, "shared", this.contextMirror)
34127
34210
  )
34128
34211
  },
@@ -34138,8 +34221,8 @@ var AnonDO = class extends DurableObject2 {
34138
34221
  }
34139
34222
  appFanOut(trigger, anonId, context) {
34140
34223
  const { registry: registry3, registryError, consentEnforced: consentEnforced2 } = context;
34141
- const { targets, geoSkipped } = eligibleApps(registry3, trigger, consentState2(this.kv), consentEnforced2);
34142
- if (registryError === void 0 && targets.length === 0 && geoSkipped.length === 0) {
34224
+ const { targets, geoSkipped, gpcSkipped } = eligibleApps(registry3, trigger, consentState2(this.kv), consentEnforced2);
34225
+ if (registryError === void 0 && targets.length === 0 && geoSkipped.length === 0 && gpcSkipped.length === 0) {
34143
34226
  return;
34144
34227
  }
34145
34228
  const job = { trigger, snapshot: this.appsSnapshot(anonId, context), trace: currentTraceContext() };
@@ -34752,7 +34835,7 @@ var GlobalDO = class extends DurableObject3 {
34752
34835
  if (await this.discardIfUnused()) {
34753
34836
  return null;
34754
34837
  }
34755
- if (!consentAllows(globalConsentState(this.sql), gate.slug, gate.category, gate.enforced)) {
34838
+ if (!consentAllows(globalConsentState(this.sql), gate.slug, gate.category, { enforced: gate.enforced, gpc: false })) {
34756
34839
  return null;
34757
34840
  }
34758
34841
  return linkedAnonsPage(this.sql, afterRowId, limit);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "main": {
3
- "version": "0.1.9",
4
- "gitSha": "ffbdbe7148d4aba3072a6c3c857e69f0557b0ec5",
5
- "builtAt": "2026-10-01T08:37:00.893Z",
3
+ "version": "0.1.10",
4
+ "gitSha": "d7f0a00c21eb403205af7ecf5ef53a23aa983611",
5
+ "builtAt": "2026-10-02T07:50:13.371Z",
6
6
  "compatibilityDate": "2026-06-23",
7
7
  "compatibilityFlags": [
8
8
  "nodejs_compat"
@@ -39,9 +39,9 @@
39
39
  ]
40
40
  },
41
41
  "app-runtime": {
42
- "version": "0.1.7",
43
- "gitSha": "403986012db0fea5975162b971aca874682f3184",
44
- "builtAt": "2026-10-01T08:37:02.873Z",
42
+ "version": "0.1.8",
43
+ "gitSha": "c97c066674cc86cb667e2e260a6409d9718e8420",
44
+ "builtAt": "2026-10-02T07:50:15.589Z",
45
45
  "compatibilityDate": "2026-06-23",
46
46
  "compatibilityFlags": [
47
47
  "nodejs_compat",