@blotout/qinto-cli 0.1.30 → 0.1.32

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.
@@ -19607,6 +19607,7 @@ var endpointSchema = external_exports.object({
19607
19607
  var eventsSchema = external_exports.object({
19608
19608
  publishes: external_exports.array(external_exports.string().min(1).max(MAX_EVENT_TOPIC_LENGTH).regex(EVENT_TOPIC)).max(MAX_PUBLISHED_TOPICS).default([])
19609
19609
  }).strict();
19610
+ var dependenciesSchema = external_exports.array(external_exports.object({ app: external_exports.string().regex(APP_SLUG_PATTERN), description: renderableText(1, 200).optional() }).strict()).max(10);
19610
19611
  var rpcSchema = external_exports.object({
19611
19612
  exposes: external_exports.array(external_exports.object({ name: external_exports.string().regex(FUNCTION_NAME).max(64), visibility: external_exports.enum(["public", "private"]) }).strict()).max(32).optional(),
19612
19613
  calls: external_exports.array(external_exports.object({ app: external_exports.string().regex(APP_SLUG_PATTERN), fn: external_exports.string().regex(FUNCTION_NAME).max(64) }).strict()).max(64).optional()
@@ -19650,6 +19651,9 @@ var baseSchema = external_exports.object({
19650
19651
  description: "The app's browser surface: an optional entry for the bundle that ships inside the site's q.js, and the query params and cookies it declares for capture."
19651
19652
  }),
19652
19653
  resources: external_exports.array(resourceSchema).max(32).default([]).meta({ description: "The Cloudflare bindings the app worker is given." }),
19654
+ dependencies: dependenciesSchema.optional().meta({
19655
+ description: "The apps this app needs on the same site. Every app it calls over RPC must be listed."
19656
+ }),
19653
19657
  rpc: rpcSchema.optional().meta({ description: "The functions this app exposes to other apps, and the ones it calls on them." }),
19654
19658
  events: eventsSchema.optional().meta({
19655
19659
  description: "The push topics this app broadcasts to every app on the site. Requires the `sharedContext` data capability; a topic left out reaches only this app\u2019s own browser code."
@@ -19966,9 +19970,30 @@ var validateRpc = (manifest, ctx) => {
19966
19970
  ["rpc", "exposes"],
19967
19971
  "function"
19968
19972
  );
19973
+ const dependencies = new Set((manifest.dependencies ?? []).map((dependency) => dependency.app));
19969
19974
  for (const [index, call] of (manifest.rpc?.calls ?? []).entries()) {
19970
19975
  if (call.app === manifest.slug) {
19971
19976
  ctx.addIssue({ code: "custom", path: ["rpc", "calls", index, "app"], message: "An app cannot declare an RPC call to itself" });
19977
+ } else if (!dependencies.has(call.app)) {
19978
+ ctx.addIssue({
19979
+ code: "custom",
19980
+ path: ["rpc", "calls", index, "app"],
19981
+ message: `${call.app} is called over RPC, so it must be in dependencies. Add { "app": "${call.app}" } to dependencies; if your Qinto CLI rejects that field, upgrade it.`
19982
+ });
19983
+ }
19984
+ }
19985
+ };
19986
+ var validateDependencies = (manifest, ctx) => {
19987
+ const dependencies = manifest.dependencies ?? [];
19988
+ uniqueOrIssue(
19989
+ ctx,
19990
+ dependencies.map((dependency) => dependency.app),
19991
+ ["dependencies"],
19992
+ "dependency"
19993
+ );
19994
+ for (const [index, dependency] of dependencies.entries()) {
19995
+ if (dependency.app === manifest.slug) {
19996
+ ctx.addIssue({ code: "custom", path: ["dependencies", index, "app"], message: "An app cannot depend on itself" });
19972
19997
  }
19973
19998
  }
19974
19999
  };
@@ -20019,6 +20044,7 @@ var appManifestSchema = baseSchema.superRefine((manifest, ctx) => {
20019
20044
  validateEndpoints(manifest, ctx);
20020
20045
  validateCapture(manifest, ctx);
20021
20046
  validateBrowserHooks(manifest, ctx);
20047
+ validateDependencies(manifest, ctx);
20022
20048
  validateRpc(manifest, ctx);
20023
20049
  validateEvents(manifest, ctx);
20024
20050
  validateOauth(manifest, ctx);
@@ -20039,9 +20065,47 @@ var appRpcContractSchema = external_exports.record(external_exports.string().reg
20039
20065
  return new TextEncoder().encode(JSON.stringify(contract)).length <= MAX_RPC_CONTRACT_BYTES;
20040
20066
  }, "The RPC contract exceeds the size cap");
20041
20067
 
20068
+ // ../../../libs/app-envelope/src/geo.ts
20069
+ var cfString = external_exports.string().optional().catch(void 0);
20070
+ var requestGeoSchema = external_exports.object({
20071
+ country: cfString,
20072
+ city: cfString,
20073
+ region: cfString,
20074
+ regionCode: cfString,
20075
+ timezone: cfString,
20076
+ postalCode: cfString,
20077
+ continent: cfString,
20078
+ latitude: cfString,
20079
+ longitude: cfString,
20080
+ isEUCountry: cfString,
20081
+ botManagement: external_exports.object({
20082
+ ja4: cfString,
20083
+ ja3Hash: cfString,
20084
+ score: external_exports.number().optional().catch(void 0),
20085
+ verifiedBot: external_exports.boolean().optional().catch(void 0)
20086
+ }).optional().catch(void 0)
20087
+ });
20088
+ var userGeoSchema = external_exports.object({
20089
+ ip: external_exports.string().optional(),
20090
+ userAgent: external_exports.string().optional(),
20091
+ timezone: external_exports.string().optional(),
20092
+ city: external_exports.string().optional(),
20093
+ postalCode: external_exports.string().optional(),
20094
+ region: external_exports.string().optional(),
20095
+ regionCode: external_exports.string().optional(),
20096
+ country: external_exports.string().optional(),
20097
+ continent: external_exports.string().optional(),
20098
+ isEUCountry: external_exports.string().optional(),
20099
+ latitude: external_exports.string().optional(),
20100
+ longitude: external_exports.string().optional(),
20101
+ ja4: external_exports.string().optional(),
20102
+ ja3: external_exports.string().optional(),
20103
+ botScore: external_exports.string().optional()
20104
+ });
20105
+
20042
20106
  // ../../../libs/app-envelope/src/envelope.ts
20043
20107
  var ENVELOPE_VERSION = 3;
20044
- var ENVELOPE_ERROR_CODES = ["invalid_arguments"];
20108
+ var ENVELOPE_ERROR_CODES = ["invalid_arguments", "result_too_large", "callee_timeout", "rpc_error"];
20045
20109
  var MAX_INSTALLS_PER_APP = 15;
20046
20110
  var MAX_APPS_PER_DOMAIN = 50;
20047
20111
  var MAX_EMITS_PER_INSTALL = 10;
@@ -20055,6 +20119,7 @@ var MAX_SHARED_CONTEXT_ENTRY_BYTES = 4 * 1024;
20055
20119
  var MAX_PUSH_BYTES = 16 * 1024;
20056
20120
  var MAX_RESPONSE_BYTES = 1024 * 1024;
20057
20121
  var MAX_RPC_BYTES = 128 * 1024;
20122
+ var TRAFFIC_CLASSES = ["hot", "cold"];
20058
20123
  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();
20059
20124
  var installRuleSchema = external_exports.object({
20060
20125
  mode: external_exports.enum(["except", "only"]),
@@ -20114,11 +20179,21 @@ var envelopeRequestSchema = external_exports.object({
20114
20179
  anonId: external_exports.string().min(1).max(64),
20115
20180
  globalId: external_exports.string().min(1).max(64).nullable(),
20116
20181
  createdAt: external_exports.number().int().optional(),
20117
- properties: external_exports.record(external_exports.string(), external_exports.unknown()),
20118
- geo: external_exports.record(external_exports.string(), external_exports.string()).optional()
20182
+ properties: external_exports.record(external_exports.string(), external_exports.string()),
20183
+ geo: userGeoSchema.optional()
20184
+ }).strict().optional(),
20185
+ consent: external_exports.object({
20186
+ enabled: external_exports.boolean(),
20187
+ categories: external_exports.object({
20188
+ necessary: external_exports.literal(true),
20189
+ functional: external_exports.boolean(),
20190
+ analytics: external_exports.boolean(),
20191
+ marketing: external_exports.boolean(),
20192
+ salesOfData: external_exports.boolean()
20193
+ }),
20194
+ apps: external_exports.record(external_exports.string(), external_exports.boolean())
20119
20195
  }).strict().optional(),
20120
- consent: external_exports.object({ enabled: external_exports.boolean(), categories: external_exports.record(external_exports.string(), external_exports.boolean()), apps: external_exports.record(external_exports.string(), external_exports.boolean()) }).strict().optional(),
20121
- geo: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
20196
+ geo: requestGeoSchema.optional(),
20122
20197
  // Connect envelopes only: the page the visitor's socket opened from, and the visit session it joined.
20123
20198
  page: pageSchema.optional(),
20124
20199
  session: sessionSchema.optional(),
@@ -20129,7 +20204,15 @@ var envelopeRequestSchema = external_exports.object({
20129
20204
  context: external_exports.object({ private: contextEntrySchema.optional(), shared: external_exports.record(external_exports.string().min(1).max(30), contextEntrySchema).optional() }).strict().optional(),
20130
20205
  // RPC depth is capped at 3 by the broker; hook depth at 2 by the emit path.
20131
20206
  depth: external_exports.number().int().min(0).max(3),
20132
- rpc: external_exports.object({ caller: external_exports.string().min(1).max(30).nullable(), fn: external_exports.string().min(1).max(64), args: external_exports.unknown() }).strict().refine((rpc) => {
20207
+ rpc: external_exports.object({
20208
+ caller: external_exports.string().min(1).max(30).nullable(),
20209
+ fn: external_exports.string().min(1).max(64),
20210
+ args: external_exports.unknown(),
20211
+ // Optional on the wire: a site worker predating traffic classes sends none, which means hot.
20212
+ traffic: external_exports.enum(TRAFFIC_CLASSES).optional(),
20213
+ // Cold calls only: the broker mints it so the callee can query its own catalog.
20214
+ queryTicket: external_exports.string().min(1).max(2048).optional()
20215
+ }).strict().refine((rpc) => {
20133
20216
  return new TextEncoder().encode(JSON.stringify(rpc.args ?? null)).length <= MAX_RPC_BYTES;
20134
20217
  }, "RPC args exceed the size cap").optional(),
20135
20218
  action: external_exports.object({
@@ -20379,7 +20462,7 @@ var endpointContextSchema = external_exports.object({
20379
20462
  method: external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
20380
20463
  mode: external_exports.enum(["webhook", "api"]),
20381
20464
  version: external_exports.string().min(1).max(32),
20382
- geo: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
20465
+ geo: requestGeoSchema.optional(),
20383
20466
  installs: external_exports.array(installSchema3).min(1).max(15),
20384
20467
  user: external_exports.object({ anonId: external_exports.string().min(1).max(64) }).strict().optional()
20385
20468
  }).strict().refine((context) => {
@@ -21212,7 +21295,7 @@ var brokerActive = async (env, input2) => {
21212
21295
  }
21213
21296
  return response.status >= 400 && response.status < 500 ? { kind: "refused" } : { kind: "unknown" };
21214
21297
  };
21215
- var buildAppsClient = (env, envelope) => {
21298
+ var buildAppsClient = (env, envelope, traffic) => {
21216
21299
  const caller = callerSlug(env);
21217
21300
  const subject = envelope.user?.anonId;
21218
21301
  return {
@@ -21229,7 +21312,8 @@ var buildAppsClient = (env, envelope) => {
21229
21312
  fn,
21230
21313
  args,
21231
21314
  ...options?.installId === void 0 ? {} : { installId: options.installId },
21232
- depth: envelope.depth + 1
21315
+ depth: envelope.depth + 1,
21316
+ traffic
21233
21317
  });
21234
21318
  return answer.result;
21235
21319
  },
@@ -21738,10 +21822,14 @@ var buildWorkflowContext = (env, stamp) => {
21738
21822
  const { variables, secrets } = projectInstallValues(parseVariableMaps(env), variableKinds, stamp.ord);
21739
21823
  const { appVariables, appSecrets } = projectAppValues(env, appVariableKinds);
21740
21824
  const install = { id: stamp.installId, label: stamp.installLabel };
21741
- const apps = buildAppsClient(env, {
21742
- ...stamp.anonId === null ? {} : { user: { anonId: stamp.anonId, globalId: null, properties: {} } },
21743
- depth: 0
21744
- });
21825
+ const apps = buildAppsClient(
21826
+ env,
21827
+ {
21828
+ ...stamp.anonId === null ? {} : { user: { anonId: stamp.anonId, globalId: null, properties: {} } },
21829
+ depth: 0
21830
+ },
21831
+ "cold"
21832
+ );
21745
21833
  return {
21746
21834
  env: appEnv(env),
21747
21835
  install,
@@ -21816,7 +21904,8 @@ var buildContext = ({
21816
21904
  pushes,
21817
21905
  captures,
21818
21906
  context,
21819
- currency
21907
+ currency,
21908
+ traffic
21820
21909
  }) => {
21821
21910
  const { variables, secrets } = projectInstallValues(maps, kinds, install.ord);
21822
21911
  const subject = envelope.user?.anonId ?? null;
@@ -21886,7 +21975,7 @@ var buildContext = ({
21886
21975
  observability: buildObservability(install.ord),
21887
21976
  identity: buildIdentity(env),
21888
21977
  currency,
21889
- apps: buildAppsClient(env, envelope)
21978
+ apps: buildAppsClient(env, envelope, traffic)
21890
21979
  };
21891
21980
  };
21892
21981
 
@@ -22007,7 +22096,7 @@ var dispatchSchedule = async (envelope, bundle, env) => {
22007
22096
  observability: buildObservability(install.ord),
22008
22097
  identity: buildIdentity(env),
22009
22098
  currency,
22010
- apps: buildAppsClient(env, { depth: 0 }),
22099
+ apps: buildAppsClient(env, { depth: 0 }, "cold"),
22011
22100
  query: buildQuery({ env, ticket: install.queryTicket, installId: install.id }),
22012
22101
  push: async (userId, message) => {
22013
22102
  await brokerPush(env, {
@@ -22136,7 +22225,7 @@ var buildContext2 = (envelope, env) => {
22136
22225
  observability: buildObservability(install.ord),
22137
22226
  identity: buildIdentity(env),
22138
22227
  currency: buildCurrency(env),
22139
- apps: buildAppsClient(env, { depth: 0 }),
22228
+ apps: buildAppsClient(env, { depth: 0 }, "cold"),
22140
22229
  query: buildQuery({ env, ticket: envelope.tool.queryTicket, installId: install.id }),
22141
22230
  track: buildTrack(env, "tool", install.id),
22142
22231
  fail: (message) => {
@@ -22403,7 +22492,7 @@ var buildContext3 = ({
22403
22492
  geo,
22404
22493
  params,
22405
22494
  install: { id: install.id, label: install.label },
22406
- ...user === void 0 ? {} : { user: { anonId: user.anonId, globalId: null, properties: {} } },
22495
+ ...user === void 0 ? {} : { user: { anonId: user.anonId } },
22407
22496
  startWorkflow: buildStartWorkflow({
22408
22497
  ord: install.ord,
22409
22498
  installId: install.id,
@@ -22418,7 +22507,7 @@ var buildContext3 = ({
22418
22507
  observability: buildObservability(install.ord),
22419
22508
  identity: buildIdentity(env),
22420
22509
  currency,
22421
- apps: buildAppsClient(env, { depth: 0 }),
22510
+ apps: buildAppsClient(env, { depth: 0 }, "hot"),
22422
22511
  push: async (userId, message) => {
22423
22512
  await brokerPush(env, {
22424
22513
  userId,
@@ -22667,6 +22756,17 @@ var checkAgainst = async (schema, value) => {
22667
22756
  };
22668
22757
 
22669
22758
  // src/rpc.ts
22759
+ var MAX_RPC_ERROR_MESSAGE_LENGTH = 1024;
22760
+ var rpcQuery = (env, rpc, installId) => {
22761
+ if (rpc.queryTicket !== void 0) {
22762
+ return buildQuery({ env, ticket: rpc.queryTicket, installId });
22763
+ }
22764
+ const reason = (rpc.traffic ?? "hot") === "hot" ? "catalog queries are not available on hot traffic" : "catalog queries are not available on this site until its next deploy";
22765
+ const refuse = async () => {
22766
+ throw new Error(reason);
22767
+ };
22768
+ return { catalog: refuse, analytics: refuse };
22769
+ };
22670
22770
  var dispatchRpc = async (envelope, bundle, env) => {
22671
22771
  if (envelope.installs.length > 1) {
22672
22772
  return jsonError(400, "rpc envelope carries more than one install");
@@ -22694,14 +22794,29 @@ var dispatchRpc = async (envelope, bundle, env) => {
22694
22794
  const currency = buildCurrency(env);
22695
22795
  const subject = envelope.user?.anonId ?? null;
22696
22796
  const ctx = {
22697
- ...buildContext({ env, envelope, site, install, maps, kinds, appScope, emits, pushes, captures, context, currency }),
22797
+ ...buildContext({
22798
+ env,
22799
+ envelope,
22800
+ site,
22801
+ install,
22802
+ maps,
22803
+ kinds,
22804
+ appScope,
22805
+ emits,
22806
+ pushes,
22807
+ captures,
22808
+ context,
22809
+ currency,
22810
+ traffic: rpc.traffic ?? "hot"
22811
+ }),
22698
22812
  context: fetchedContext(
22699
22813
  env,
22700
22814
  (what) => {
22701
22815
  return requireSubject(subject, what);
22702
22816
  },
22703
22817
  context
22704
- )
22818
+ ),
22819
+ query: rpcQuery(env, rpc, install.id)
22705
22820
  };
22706
22821
  let value;
22707
22822
  try {
@@ -22711,17 +22826,19 @@ var dispatchRpc = async (envelope, bundle, env) => {
22711
22826
  } catch (error61) {
22712
22827
  const message = failureMessage(error61);
22713
22828
  console.warn(`rpc failed for install ${install.id}: ${message}`);
22829
+ if (error61?.__qintoRpcError === true) {
22830
+ return json2(200, { v: ENVELOPE_VERSION, error: { code: "rpc_error", message: message.slice(0, MAX_RPC_ERROR_MESSAGE_LENGTH) } });
22831
+ }
22714
22832
  return json2(200, { v: ENVELOPE_VERSION, error: { message } });
22715
22833
  }
22716
22834
  const checked = await checkAgainst(definition.output, value);
22717
22835
  if (!checked.ok) {
22718
22836
  return jsonError(200, failureMessage(`rpc result does not match the output schema: ${checked.message}`));
22719
22837
  }
22720
- const result = checked.value === void 0 ? null : checked.value;
22721
- if (new TextEncoder().encode(JSON.stringify(result)).length > MAX_RPC_BYTES) {
22722
- return jsonError(200, "rpc result exceeds the size cap");
22723
- }
22724
- const body = { v: ENVELOPE_VERSION, rpc: { result } };
22838
+ const body = {
22839
+ v: ENVELOPE_VERSION,
22840
+ rpc: { result: checked.value === void 0 ? null : checked.value }
22841
+ };
22725
22842
  const contextWrites = context.writes();
22726
22843
  if (emits.length > 0 || pushes.length > 0 || captures.length > 0 || contextWrites.length > 0) {
22727
22844
  body.effects = [{ ord: install.ord, emits, pushes, captures, context: contextWrites }];
@@ -22903,7 +23020,7 @@ var dispatchUi = async ({ request, env, versions }) => {
22903
23020
  observability: buildObservability(context.install.ord),
22904
23021
  identity: buildIdentity(env),
22905
23022
  currency: buildCurrency(env),
22906
- apps: buildAppsClient(env, { depth: 0 })
23023
+ apps: buildAppsClient(env, { depth: 0 }, "cold")
22907
23024
  };
22908
23025
  let response;
22909
23026
  try {
@@ -22974,7 +23091,21 @@ var runInstalls = async (envelope, env, invoke) => {
22974
23091
  const pushes = [];
22975
23092
  const captures = [];
22976
23093
  const context = contextCollector();
22977
- const ctx = buildContext({ env, envelope, site, install, maps, kinds, appScope, emits, pushes, captures, context, currency });
23094
+ const ctx = buildContext({
23095
+ env,
23096
+ envelope,
23097
+ site,
23098
+ install,
23099
+ maps,
23100
+ kinds,
23101
+ appScope,
23102
+ emits,
23103
+ pushes,
23104
+ captures,
23105
+ context,
23106
+ currency,
23107
+ traffic: "hot"
23108
+ });
22978
23109
  try {
22979
23110
  await installSpan(install, () => {
22980
23111
  return invoke(ctx, install);
@@ -23039,7 +23170,8 @@ var runTransform = async (envelope, env, action, handler) => {
23039
23170
  pushes,
23040
23171
  captures,
23041
23172
  context,
23042
- currency: buildCurrency(env)
23173
+ currency: buildCurrency(env),
23174
+ traffic: "hot"
23043
23175
  });
23044
23176
  const ctx = {
23045
23177
  ...withoutApps,
@@ -0,0 +1 @@
1
+ (function(){var e=()=>{},t=t=>{t.config?.autoCapture?.pageLoad===!0&&t.q.push([`track`,[`PageView`],e,e])},n=class extends Error{constructor(e){super(e),this.name=`QintoError`}},r=class extends n{constructor(e){super(e),this.name=`QintoTimeoutError`}},i=(e=Date.now())=>`evt_${globalThis.crypto.randomUUID()}_${e}`,a=(e,t)=>{if(e!==void 0)return e;let n=t?.orderId;return typeof n==`string`&&n!==``?n:i()},o=[`necessary`,`functional`,`analytics`,`marketing`,`salesOfData`],s=[`then`,`ready`,`config`],c=()=>{},l=(e,t)=>(...n)=>{let r=new Promise((r,i)=>{e.q.push([t,n,r,i])});return r.catch(c),r},u=e=>{let t=e;return t._q=1,t.q=[],new Proxy(t,{get:(e,n)=>typeof n!=`string`||s.includes(n)||n in t?Reflect.get(t,n):(t[n]=l(t,n),t[n])})},d=()=>{let e=document.currentScript?.dataset.qintoGlobal;return typeof e==`string`&&e!==``?e:`qinto`},f=e=>typeof e==`object`&&!!e&&e._q===1,p=(e=`qinto`)=>{let t=globalThis;if(e in t){let n=t[e];if(!f(n))return null;let r=Array.isArray(n.q)?n:u(n);return r!==n&&Object.defineProperty(t,e,{value:r,writable:!0,enumerable:!0,configurable:!0}),r}let n=u({});return Object.defineProperty(t,e,{value:n,writable:!0,enumerable:!0,configurable:!0}),n},m=(e,t)=>async(r,i)=>{let a=Reflect.get(e,r);if(typeof a!=`function`)throw new n(`[Qinto] ${t}.${r} is not a method of this tag.`);return a.apply(e,Array.from(i))},ee=e=>()=>Promise.reject(e),h=(e,t)=>{let[n,r,i,a]=t;e(n,r).then(i,a)},g=e=>{let t=[];return t.push=(...t)=>{for(let n of t)h(e,n);return 0},t},_=(e,t,n=`window.qinto`)=>{let r=e.ready;if(r!==void 0)return r;let i=e,a=e=>{let t=i.q;i.q=g(e);for(let n of t)h(e,n)},o=t().then(e=>{a(m(e,n))},e=>{throw a(ee(e)),e});return o.catch(c),i.ready=o,o},v=(e,t,n)=>n===void 0||!o.some(e=>e===n)?!1:n===`necessary`||e!==null&&e.categories[n]===!0&&e.apps[t]!==!1,y=(e,t)=>e.config?.gpc===!0&&navigator.globalPrivacyControl===!0&&(t===`marketing`||t===`salesOfData`),b=(e,t,n)=>{let r=e.config?.apps?.[t]?.category;return y(e,r)?!1:e.config?.consentConfiguration?.enabled!==!0||v(n,t,r)},x=e=>Object.values(e.config?.apps??{}).some(e=>e.category!==`necessary`||e.capture!==void 0),te=(e,t,n,r)=>b(e,t,n)?r===void 0||b(e,t,r):!1,ne=e=>{let{categories:t,apps:n}=e;if(typeof t==`object`&&t&&typeof n==`object`&&n)return{categories:t,apps:n}},re=()=>{},S=(e,t)=>{console.error(`[Qinto] A handler of app "${e.slug}" (install "${e.installId}") failed.`,t)},C=(e,t,n)=>{for(let r of t)try{let t=r(n);t instanceof Promise&&t.catch(t=>{S(e,t)})}catch(t){S(e,t)}},w=(e,t,n,r)=>{e.activated.add(r.id);let i={slug:t,installId:r.id,eventHandlers:[],transformHooks:[],consentHandlers:[],pushHandlers:[],sharedContextHandlers:[]};e.instances.push(i);let a={variables:r.variables,install:{id:r.id,label:r.label},getSession:async()=>{let n=await e.client.getSession();return e.target.config?.apps?.[t]?.geo===!0?n:{...n,ip:null,geo:null}},track:(t,n)=>{e.client.track(t,n).catch(re)},onEvent:e=>{i.eventHandlers.push(e)},onTransform:e=>{if(r.transform!==!0){console.error(`[Qinto] App "${t}" registers a transform hook without declaring browser.hooks: ["transform"].`);return}i.transformHooks.push(e)},onConsent:e=>{i.consentHandlers.push(e)},onPush:e=>{i.pushHandlers.push(e)},fetch:async(n,i)=>{let a=new URL(`${e.client.destination}/app/${t}${n}`);if(!n.startsWith(`/`)||!a.pathname.startsWith(`/app/${t}/`))throw TypeError(`[Qinto] App "${t}" fetched "${n}"; the path must be absolute and stay inside the app.`);let o=new Headers(i?.headers);return o.set(`x-qinto-install`,r.id),fetch(a,{...i,headers:o,credentials:`include`})},onSharedContext:n=>{if(e.target.config?.apps?.[t]?.sharedContext!==!0){console.error(`[Qinto] App "${t}" reads shared context without declaring the sharedContext capability.`);return}i.sharedContextHandlers.push(n),e.sharedContext!==null&&te(e.target,t,e.consent,e.sharedContextConsent)&&C(i,[n],e.sharedContext)}};try{n(a)}catch(e){console.error(`[Qinto] App "${t}" failed to activate install "${r.id}".`,e)}},T=100,E=1024,D=32768,ie=e=>{let t;try{t=JSON.stringify(e)}catch{return}if(!(t===void 0||new TextEncoder().encode(t).length>E))return JSON.parse(t)},ae=e=>{let t={};for(let n of e){if(n.settled!==n.total||n.values.length!==1)continue;let e=ie(n.values[0]);if(e===void 0)continue;let r={...t[n.source.slug]??{},[n.source.installId]:e},i={...t,[n.source.slug]:r};new TextEncoder().encode(JSON.stringify(i)).length<=D&&(t[n.source.slug]=r)}return Object.keys(t).length===0?void 0:t},oe=async(e,t,{declares:n,notify:r,report:i})=>{let a=[],o=[];for(let s of e){if(!n(s)){r(s);continue}let e={source:s,total:s.eventHandlers.length,settled:0,values:[]};a.push(e);for(let n of s.eventHandlers){let r;try{r=n(t)}catch(t){e.settled+=1,i(s,t);continue}o.push(Promise.resolve(r).then(t=>{e.settled+=1,t!==void 0&&e.values.push(t)},t=>{e.settled+=1,i(s,t)}))}}if(o.length>0){let e,t=new Promise(t=>{e=setTimeout(t,T)});await Promise.race([Promise.all(o),t]),clearTimeout(e)}return ae(a)},se=(e,t)=>{if(e.mode===`except`&&t.mode===`except`)return{mode:`except`,installIds:[...new Set([...e.installIds,...t.installIds])]};let[n,r]=e.mode===`only`?[e,t]:[t,e],i=new Set(r.installIds);return{mode:`only`,installIds:n.installIds.filter(e=>i.has(e)===(r.mode===`only`))}},ce=(e,t,n,r)=>{let i;try{i=t(n,r)}catch(t){S(e,t);return}if(i===void 0||i===!1)return i;let a=i?.installs;if((a?.mode===`except`||a?.mode===`only`)&&Array.isArray(a.installIds))return a;S(e,TypeError(`A transform hook returned neither nothing, false, nor { installs }.`))},le=(e,t,n,r)=>{let i=r;for(let r of e)for(let e of r.transformHooks){let a=ce(r,e,t,n);if(a===!1)return!1;a!==void 0&&(i=i===void 0?a:se(i,a))}return i},O=(e,t)=>{let{app:n,install:r,topic:i,to:a}=e;return typeof n!=`string`||typeof i!=`string`||r!==void 0&&typeof r!=`string`||t&&!(Array.isArray(a)&&a.every(e=>typeof e==`string`))},k=(e,t,n,r)=>r===null?e.slug===t&&(n===void 0||n===e.installId):r.has(e.slug),A=e=>`_appInstalls`in e?e._appInstalls:void 0,j=(e,t,n)=>{if(!b(e.target,t,e.consent))return;let r=(A(e.target)?.[t]??[]).filter(t=>!e.activated.has(t.id)&&(t.geoFilter!==!0||e.geoAllowed.has(t.id)));if(r.length===0)return;let i=n.factory;if(i===void 0){try{i=n.loader()}catch(e){i=null,console.error(`[Qinto] App "${t}" failed to load.`,e)}e.apps.set(t,{...n,factory:i})}if(i!==null)for(let n of r)w(e,t,i,n)},M=(e,t)=>{let n=e;n.consent=t,n.sharedContextConsent=void 0;for(let n of e.instances)C(n,n.consentHandlers,t);e.onConsentSettled?.(t);for(let[t,n]of e.apps)j(e,t,n)},N=async e=>{try{M(e,await e.client.getConsent())}catch(e){console.error(`[Qinto] The apps consent read failed.`,e)}},P=async e=>{if(e.geoRequested||!Object.values(A(e.target)??{}).some(e=>e.some(e=>e.geoFilter===!0)))return;let t=e;t.geoRequested=!0;try{t.geoAllowed=new Set(await e.client.getGeoInstalls())}catch(e){console.error(`[Qinto] The geo filter read failed; geo-filtered installs stay off on this page.`,e);return}for(let[t,n]of e.apps)j(e,t,n)},F=async(e,t,n)=>{e.onPageEvent?.(e.consent);let r=new Set(n?.installIds);return oe(e.instances.filter(t=>b(e.target,t.slug,e.consent)&&(n===void 0||r.has(t.installId)===(n.mode===`only`))),t,{declares:n=>e.target.config?.apps?.[n.slug]?.eventResults?.includes(t.type)===!0,notify:e=>{C(e,e.eventHandlers,t)},report:S})},I=e=>{let{client:t}=e;return{track:async(n,r,i)=>{let o=a(i?.eventId,r),s=t.actionContext(),c={type:`track`,event:n,eventId:o,properties:r??{}},l=i?.installs,u=le(e.instances,c,s.page.url,l);if(u===!1)return{eventId:o};let d=await F(e,c,u);return await t.submitTrack({event:n,eventId:o,properties:r,context:s,appResults:d,installs:l}),{eventId:o}},identify:async(n,r)=>{let i=t.actionContext(),a=await F(e,{type:`identify`,event:`identify`,properties:n??{}});return t.submitIdentify({traits:n,id:r,context:i,appResults:a})},alias:t.alias,consent:async n=>{if(await t.submitConsent(n,t.actionContext()),e.onConsentMutation?.(),x(e.target)){let t=e;t.consent=null,await N(e)}},getAnonId:t.getAnonId,getConsent:t.getConsent,getSession:t.getSession,close:t.close}},L=(e,t)=>{let n=new Map,r=null,i=null,a=e=>{let[t,i]=e;if(n.has(t))return;let a={loader:i};n.set(t,a),r?.(t,a)},o=e,s=`_apps`in o?o._apps:void 0,c=Array.isArray(s)?s:[];c.push=(...e)=>{for(let t of e)a(t);return 0},o._apps=c;for(let e of c.splice(0))a(e);return{sharedContext:e=>{let{context:t}=e;if(typeof t!=`object`||!t||Array.isArray(t)){console.error(`[Qinto] The server sent a malformed shared context.`,e);return}let n=i;if(n===null)return;n.sharedContext=t;let r=ne(e),a=r??n.consent;n.sharedContextConsent=r;for(let e of n.instances)b(n.target,e.slug,a)&&C(e,e.sharedContextHandlers,n.sharedContext)},push:e=>{let{app:t,install:n,topic:r,scope:a,to:o}=e,s=a===`site`;if(O(e,s)){console.error(`[Qinto] The server sent a malformed app push.`,e);return}let c=i;if(c===null)return;let l=s?new Set(o):null,u={topic:r,data:e.data,from:t};for(let e of c.instances)k(e,t,n,l)&&b(c.target,e.slug,c.consent)&&C(e,e.pushHandlers,u)},connect:async a=>{let o={target:e,client:a,apps:n,instances:[],activated:new Set,consent:null,geoAllowed:new Set,geoRequested:!1,sharedContext:i?.sharedContext??null,sharedContextConsent:i?.sharedContextConsent,onConsentSettled:t?.onConsentSettled,onConsentMutation:t?.onConsentMutation,onPageEvent:t?.onPageEvent};i=o,r=(e,t)=>{j(o,e,t),o.consent===null&&o.target.config?.consentConfiguration?.enabled===!0&&o.target.config?.apps?.[e]?.category!==`necessary`&&N(o),P(o)};for(let[e,t]of n)j(o,e,t);let s=P(o);return e.config?.consentConfiguration?.enabled===!0&&x(e)?await N(o):o.onConsentSettled?.(o.consent),await s,I(o)}}},R=512,z=864e5,B=e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Object.fromEntries(Object.entries(t).map(([e,t])=>[e,t.value]))])),V=(e,t)=>`qinto:cap:${encodeURIComponent(e)}:${encodeURIComponent(t)}`,H=e=>{try{let t=window.sessionStorage.getItem(e);if(t===null)return null;let n=JSON.parse(t);return typeof n.at!=`number`||typeof n.anonId!=`string`||typeof n.apps!=`object`||n.apps===null||Date.now()-n.at>z?null:{at:n.at,anonId:n.anonId,apps:n.apps}}catch{return null}},U=(e,t)=>{try{window.sessionStorage.setItem(e,JSON.stringify(t))}catch{}},W=e=>{try{window.sessionStorage.removeItem(e)}catch{}},ue=e=>{try{return decodeURIComponent(e)}catch{return e}},de=()=>{let e={};for(let t of document.cookie.split(`;`)){let n=t.indexOf(`=`);n>0&&(e[t.slice(0,n).trim()]=ue(t.slice(n+1)))}return e},fe=(e,t)=>{let n={};for(let r of e.queryParams){let e=t.params.get(r);e!==null&&e.length<=R&&(n[r]={value:e,source:`query`})}for(let r of e.cookies){let e=t.cookies[r];e!==void 0&&e.length<=R&&(n[r]={value:e,source:`cookie`})}return n},pe=(e,t)=>{let n={params:new URLSearchParams(window.location.search),cookies:de()},r={};for(let[i,a]of Object.entries(e.config?.apps??{})){if(a.capture===void 0||y(e,a.category)||!v(t,i,a.category))continue;let o=fe(a.capture,n);Object.keys(o).length>0&&(r[i]=o)}return r},me=(e,t)=>{let n={};for(let[r,i]of Object.entries(e)){let e=t[r]??{},a={};for(let[t,n]of Object.entries(i))(e[t]?.value!==n.value||e[t]?.source!==n.source)&&(a[t]=n);Object.keys(a).length>0&&(n[r]=a)}return n},G=(e,t,n,r)=>{let i=V(r,n),a=H(i),o=a?.apps??{},s=a?.anonId??null,c=a===null,l={},u=0,d=(e,t)=>{s!==null&&s!==e.anonId&&(o={}),s=e.anonId;let n=new Set(Object.entries(e.accepted).flatMap(([e,t])=>t.map(t=>`${e}\u0000${t}`)));for(let[e,r]of Object.entries(t))for(let[t,i]of Object.entries(r))n.has(`${e}\u0000${t}`)&&(o[e]={...o[e],[t]:i});U(i,{at:Date.now(),anonId:s,apps:o})},f=null,p=()=>{u+=1,o={},l={},W(i)},m=n=>{if(f=n,!c)return;let r=me(pe(e,n),{...o,...l});if(Object.keys(r).length===0)return;let i=u;l={...l,...r},t(B(r)).then(e=>{i===u&&d(e,r)}).catch(()=>{}).finally(()=>{l={}})};return{settled:e=>{m(e)},needsAnon:()=>!c,observedAnon:e=>{(e===null||s!==null&&s!==e)&&p(),s=e,c=!0,m(f)},clear:p}},K=Symbol.for(`qinto.connectGate`),q=()=>{},he=e=>{let t=globalThis,n=(t[K]??Promise.resolve()).then(e);return t[K]=n.then(q,q),n},J=class extends n{constructor(e){super(e),this.name=`QintoProtocolError`}},ge=e=>{if(typeof e!=`string`)return null;let t;try{t=JSON.parse(e)}catch{return null}if(typeof t!=`object`||!t)return null;let n=t;return n.t===`ack`||n.t===`error`||n.t===`consentState`||n.t===`session`||n.t===`captureAck`||n.t===`geoInstalls`||n.t===`appPush`||n.t===`sharedContext`?t:null},_e=e=>{if(e.t===`consentState`)return{categories:e.categories,apps:e.apps};if(e.t===`session`)return{anonId:e.anonId,globalId:e.globalId,ip:e.ip,geo:e.geo};if(e.t===`captureAck`)return{anonId:e.anonId,accepted:e.accepted};if(e.t===`geoInstalls`)return e.installs},ve=e=>{let t=new Map,n=e=>{let n=t.get(e);if(n!==void 0)return clearTimeout(n.timer),t.delete(e),n};return{submit:n=>new Promise((i,a)=>{let o={frame:n,sent:e.send(n),resolve:i,reject:a,timer:setTimeout(()=>{t.delete(n.ref),a(new r(`The Qinto request timed out.`))},e.requestTimeoutMs)};t.set(n.ref,o)}),flush:()=>{for(let n of t.values())n.sent||=e.send(n.frame)},deliver:e=>{let t=e.ref===void 0?void 0:n(e.ref);if(t!==void 0){if(e.t===`error`){t.reject(new J(e.message));return}t.resolve(_e(e))}},rejectAll:e=>{let n=[...t.values()];t.clear();for(let t of n)clearTimeout(t.timer),t.reject(e)}}},Y=()=>{let e=document.referrer;return{url:window.location.href,...e===``?{}:{referrer:e}}},ye=e=>new WebSocket(e),be=500,xe=3e4,Se=1e4,X=4096,Ce=8192,we=(e,t,n)=>{let r=n?`${e}?reconnect=1`:e;if(t.url.length>X)return r;let i=new URLSearchParams({url:t.url});t.referrer!==void 0&&t.referrer.length<=X&&i.set(`referrer`,t.referrer),n&&i.set(`reconnect`,`1`);let a=`${e}?${i}`;return a.length<=Ce?a:r},Te=`session not initialized`,Ee=1e3,De=`erased`,Oe=(e,t)=>{let n=Math.min(be*2**e,xe);return Math.round(n/2+n/2*t)},ke=(e,t)=>e.t===`appPush`?(t.onAppPush?.(e),!0):e.t===`sharedContext`&&(t.onSharedContext?.(e),!0),Ae=e=>{let t=`${e.destination.replace(/\/+$/,``)}/open`,n=null,r=null,i=0,a=!1,o=!1,s=null,c=ve({send:e=>n===null||n.readyState!==1?!1:(n.send(JSON.stringify(e)),!0),requestTimeoutMs:e.requestTimeoutMs}),l=t=>{e.onError?.(t)},u=()=>{if(o=!0,document.removeEventListener(`visibilitychange`,p),r!==null&&(clearTimeout(r),r=null),n!==null){let e=n;n=null,e.close()}c.rejectAll(new J(`connection closed`))},d=()=>{document.visibilityState!==`hidden`&&(r=setTimeout(()=>{r=null,f()},Oe(i,Math.random())),i+=1)},f=()=>{let r=ye(we(t,Y(),a));a=!0,n=r;let o;r.onopen=()=>{o=Date.now();let e=s;s=null,c.flush(),e?.resolve()},r.onmessage=t=>{let n=ge(t.data);if(n===null){l(new J(`invalid message`));return}ke(n,e)||(`anonId`in n&&typeof n.anonId==`string`&&e.onAnonId?.(n.anonId),c.deliver(n),n.t===`error`&&n.message===Te&&r.close())},r.onerror=()=>{l(new J(`connection closed`))},r.onclose=e=>{if(n!==r)return;n=null;let t=s;if(t!==null){s=null,u(),t.reject(new J(`connection closed`));return}if(e.code===Ee&&e.reason===De){u();return}o!==void 0&&Date.now()-o>=Se&&(i=0),c.rejectAll(new J(`connection closed`)),d()}},p=()=>{if(document.visibilityState===`hidden`){r!==null&&(clearTimeout(r),r=null);return}n===null&&r===null&&f()};return{connect:()=>new Promise((e,t)=>{s={resolve:e,reject:t},document.addEventListener(`visibilitychange`,p),f()}),submit:e=>o?Promise.reject(new J(`connection closed`)):(n===null&&r===null&&f(),c.submit(e)),close:()=>{u()}}},je=1e4,Me=()=>{let e=0;return()=>(e+=1,`q${e}`)},Ne=(e,t,n)=>{let r=null,i=()=>({occurredAt:Date.now(),page:Y()}),o=async()=>{let n=await e.submit({t:`getSession`,ref:t()});return r=n.anonId,n};return{destination:n.replace(/\/+$/,``),actionContext:i,submitIdentify:async({traits:n,id:r,context:i,appResults:a})=>{await e.submit({t:`identify`,ref:t(),traits:n,id:r,context:i,appResults:a})},submitTrack:async({event:n,eventId:r,properties:i,context:a,appResults:o,installs:s})=>{await e.submit({t:`track`,ref:t(),event:n,eventId:r,properties:i,context:a,appResults:o,installs:s})},submitConsent:async(n,r)=>{await e.submit({t:`consent`,ref:t(),categories:n.categories,apps:n.apps,context:r})},track:async(n,r,o)=>{let s=a(o?.eventId,r);return await e.submit({t:`track`,ref:t(),event:n,eventId:s,properties:r,context:i(),installs:o?.installs}),{eventId:s}},alias:async n=>{await e.submit({t:`alias`,ref:t(),alias:n})},getConsent:async()=>await e.submit({t:`getConsent`,ref:t()}),getGeoInstalls:async()=>await e.submit({t:`getGeoInstalls`,ref:t()}),getSession:o,getAnonId:async()=>(r===null&&await o(),r),close:async()=>{e.close()}}},Pe=async e=>{let t=Ae({destination:e.destination,requestTimeoutMs:e.requestTimeoutMs??je,onError:e.onError,onAppPush:e.onAppPush,onSharedContext:e.onSharedContext,onAnonId:e.onAnonId});await he(()=>t.connect());let n=Me();return{client:Ne(t,n,e.destination),submitCapture:async e=>await t.submit({t:`capture`,ref:n(),apps:e})}},Fe=(e,t)=>{let n=null,r=L(e,{onConsentSettled:e=>{n?.settled(e)},onConsentMutation:()=>{n?.clear()},onPageEvent:e=>{n?.settled(e)}});return{connect:async i=>{let a=await Pe({...i,onAppPush:r.push,onSharedContext:r.sharedContext,onAnonId:e=>{n?.observedAnon(e)}});n=G(e,a.submitCapture,i.destination,t);let o=await r.connect(a.client);return n.needsAnon()&&n.observedAnon((await a.client.getSession()).anonId),o}}},Z=d(),Q=p(Z),$=`window.${Z}`;if(Q!==null&&Q.ready===void 0){let e=Fe(Q,Z);t(Q),_(Q,async()=>{let t=Q.config?.destination;if(typeof t!=`string`||t===``)throw Error(`[Qinto] ${$}.config.destination is missing.`);return e.connect({destination:t})},$)}})();