@blotout/qinto-cli 0.1.6 → 0.1.8
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 +41 -14
- package/dist/index.js +4205 -3469
- package/dist/index.js.map +1 -1
- package/dist/worker-artifact/app-runtime/index.js +129 -32
- package/dist/worker-artifact/apps-ui/index.js +6 -0
- package/dist/worker-artifact/main/index.js +134 -56
- package/dist/worker-artifact/version.json +6 -6
- package/package.json +1 -1
- package/templates/app/full/qinto.app.jsonc +4 -1
|
@@ -14584,7 +14584,7 @@ var envelopeRequestSchema = external_exports.object({
|
|
|
14584
14584
|
globalId: external_exports.string().min(1).max(64).nullable(),
|
|
14585
14585
|
properties: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
14586
14586
|
geo: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
14587
|
-
}).strict(),
|
|
14587
|
+
}).strict().optional(),
|
|
14588
14588
|
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(),
|
|
14589
14589
|
geo: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
14590
14590
|
// Only what this app captured or wrote for this visitor; `updatedAt` is when the current value
|
|
@@ -14608,7 +14608,11 @@ var envelopeRequestSchema = external_exports.object({
|
|
|
14608
14608
|
browser: external_exports.record(external_exports.string().min(1).max(64), external_exports.unknown()).optional(),
|
|
14609
14609
|
origin: external_exports.object({ app: external_exports.string().min(1).max(30), install: external_exports.number().int().min(1), depth: external_exports.number().int().min(0) }).strict().optional()
|
|
14610
14610
|
}).strict().optional()
|
|
14611
|
-
}).strict()
|
|
14611
|
+
}).strict().superRefine((envelope, ctx) => {
|
|
14612
|
+
if (envelope.kind !== "rpc" && envelope.user === void 0) {
|
|
14613
|
+
ctx.addIssue({ code: "custom", path: ["user"], message: `A ${envelope.kind} envelope needs a user` });
|
|
14614
|
+
}
|
|
14615
|
+
});
|
|
14612
14616
|
var envelopeResponseSchema = external_exports.object({
|
|
14613
14617
|
v: external_exports.literal(ENVELOPE_VERSION),
|
|
14614
14618
|
// Transform invocations only: pass the action through, drop it, or replace its payload (§5.4).
|
|
@@ -14688,7 +14692,14 @@ var scheduleSchema = external_exports.object({
|
|
|
14688
14692
|
id: external_exports.string().min(1).max(32),
|
|
14689
14693
|
cron: external_exports.string().min(1).max(MAX_CRON_LENGTH),
|
|
14690
14694
|
scheduledTime: external_exports.number().int().nonnegative().safe(),
|
|
14691
|
-
installs: external_exports.array(
|
|
14695
|
+
installs: external_exports.array(
|
|
14696
|
+
external_exports.object({
|
|
14697
|
+
id: external_exports.string().min(1).max(64),
|
|
14698
|
+
ord: external_exports.number().int().min(1),
|
|
14699
|
+
label: external_exports.string().max(100),
|
|
14700
|
+
catalogQueryTicket: external_exports.string().min(1).max(2048)
|
|
14701
|
+
}).strict()
|
|
14702
|
+
).min(1).max(MAX_INSTALLS_PER_APP)
|
|
14692
14703
|
}).strict();
|
|
14693
14704
|
var controlEnvelopeSchema = external_exports.object({
|
|
14694
14705
|
v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
|
|
@@ -14756,6 +14767,9 @@ var MAX_UI_BODY_BYTES = 1024 * 1024;
|
|
|
14756
14767
|
var appUiWritePath = (domainId, appSlug2) => {
|
|
14757
14768
|
return `/app-ui/sites/${domainId}/apps/${appSlug2}/install-variables`;
|
|
14758
14769
|
};
|
|
14770
|
+
var appUiCatalogQueryPath = (domainId, appSlug2, resourceIdentifier) => {
|
|
14771
|
+
return `/app-ui/sites/${domainId}/apps/${appSlug2}/catalogs/${resourceIdentifier}/query`;
|
|
14772
|
+
};
|
|
14759
14773
|
var uiInstallSchema = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100), enabled: external_exports.boolean() }).strict();
|
|
14760
14774
|
var uiContextSchema = external_exports.object({
|
|
14761
14775
|
version: external_exports.string().min(1).max(32),
|
|
@@ -15361,6 +15375,7 @@ var brokerActive = async (env, input) => {
|
|
|
15361
15375
|
};
|
|
15362
15376
|
var buildAppsClient = (env, envelope) => {
|
|
15363
15377
|
const caller = callerSlug(env);
|
|
15378
|
+
const subject = envelope.user?.anonId;
|
|
15364
15379
|
return {
|
|
15365
15380
|
call: async (callee, fn, args, options) => {
|
|
15366
15381
|
const answer = await askBroker(env, `apps.call ${callee}.${fn}`, {
|
|
@@ -15368,7 +15383,9 @@ var buildAppsClient = (env, envelope) => {
|
|
|
15368
15383
|
kind: "rpc",
|
|
15369
15384
|
slug: caller,
|
|
15370
15385
|
key: env.QINTO_INTERNAL_KEY,
|
|
15371
|
-
|
|
15386
|
+
// An endpoint, schedule or panel calls with no visitor; the broker then reaches the callee
|
|
15387
|
+
// with no user block and its ctx.user is null.
|
|
15388
|
+
...subject === void 0 ? {} : { userId: subject },
|
|
15372
15389
|
callee,
|
|
15373
15390
|
fn,
|
|
15374
15391
|
args,
|
|
@@ -15430,6 +15447,14 @@ var buildObservability = () => {
|
|
|
15430
15447
|
};
|
|
15431
15448
|
};
|
|
15432
15449
|
|
|
15450
|
+
// src/subject.ts
|
|
15451
|
+
var requireSubject = (anonId, what) => {
|
|
15452
|
+
if (anonId === null) {
|
|
15453
|
+
throw new Error(`${what} needs a visitor, and this invocation has none.`);
|
|
15454
|
+
}
|
|
15455
|
+
return anonId;
|
|
15456
|
+
};
|
|
15457
|
+
|
|
15433
15458
|
// src/variables.ts
|
|
15434
15459
|
var parseVariableMaps = (env) => {
|
|
15435
15460
|
const maps = /* @__PURE__ */ new Map();
|
|
@@ -15565,9 +15590,9 @@ var readOnce = (env, subject) => {
|
|
|
15565
15590
|
return pending;
|
|
15566
15591
|
};
|
|
15567
15592
|
};
|
|
15568
|
-
var fetchedContext = (env,
|
|
15593
|
+
var fetchedContext = (env, subject, collector) => {
|
|
15569
15594
|
const read = readOnce(env, () => {
|
|
15570
|
-
return
|
|
15595
|
+
return subject("context.load");
|
|
15571
15596
|
});
|
|
15572
15597
|
return {
|
|
15573
15598
|
private: {
|
|
@@ -15575,6 +15600,7 @@ var fetchedContext = (env, anonId, collector) => {
|
|
|
15575
15600
|
return (await read()).private;
|
|
15576
15601
|
},
|
|
15577
15602
|
set: (patch) => {
|
|
15603
|
+
subject("context.set");
|
|
15578
15604
|
collector.set("private", patch);
|
|
15579
15605
|
}
|
|
15580
15606
|
},
|
|
@@ -15583,6 +15609,7 @@ var fetchedContext = (env, anonId, collector) => {
|
|
|
15583
15609
|
return (await read()).shared;
|
|
15584
15610
|
},
|
|
15585
15611
|
set: (patch) => {
|
|
15612
|
+
subject("context.set");
|
|
15586
15613
|
collector.set("shared", patch);
|
|
15587
15614
|
}
|
|
15588
15615
|
}
|
|
@@ -15654,20 +15681,15 @@ var readStamp = (payload) => {
|
|
|
15654
15681
|
}
|
|
15655
15682
|
return { q: stamp, payload: params?.payload };
|
|
15656
15683
|
};
|
|
15657
|
-
var requireSubject = (anonId, what) => {
|
|
15658
|
-
if (anonId === null) {
|
|
15659
|
-
throw new Error(`${what} needs a visitor, and this workflow was started without one.`);
|
|
15660
|
-
}
|
|
15661
|
-
return anonId;
|
|
15662
|
-
};
|
|
15663
15684
|
var buildWorkflowContext = (env, stamp) => {
|
|
15664
15685
|
const { variableKinds, appVariableKinds } = contextKindsOf(env);
|
|
15665
15686
|
const { variables, secrets } = projectInstallValues(parseVariableMaps(env), variableKinds, stamp.ord);
|
|
15666
15687
|
const { appVariables, appSecrets } = projectAppValues(env, appVariableKinds);
|
|
15667
15688
|
const install = { id: stamp.installId, label: stamp.installLabel };
|
|
15668
|
-
const
|
|
15669
|
-
|
|
15670
|
-
|
|
15689
|
+
const apps = buildAppsClient(env, {
|
|
15690
|
+
...stamp.anonId === null ? {} : { user: { anonId: stamp.anonId, globalId: null, properties: {} } },
|
|
15691
|
+
depth: 0
|
|
15692
|
+
});
|
|
15671
15693
|
return {
|
|
15672
15694
|
env,
|
|
15673
15695
|
install,
|
|
@@ -15690,15 +15712,7 @@ var buildWorkflowContext = (env, stamp) => {
|
|
|
15690
15712
|
broadcast: message.broadcast
|
|
15691
15713
|
});
|
|
15692
15714
|
},
|
|
15693
|
-
apps
|
|
15694
|
-
call: async (slug, fn, args, options) => {
|
|
15695
|
-
return client(requireSubject(stamp.anonId, "apps.call")).call(slug, fn, args, options);
|
|
15696
|
-
},
|
|
15697
|
-
// The broker's installs body carries no subject, so this keeps working without one.
|
|
15698
|
-
installs: async (slug) => {
|
|
15699
|
-
return client(stamp.anonId ?? "").installs(slug);
|
|
15700
|
-
}
|
|
15701
|
-
}
|
|
15715
|
+
apps
|
|
15702
15716
|
};
|
|
15703
15717
|
};
|
|
15704
15718
|
var handlerFor = (bundle, className) => {
|
|
@@ -15747,6 +15761,7 @@ var buildContext = ({
|
|
|
15747
15761
|
context
|
|
15748
15762
|
}) => {
|
|
15749
15763
|
const { variables, secrets } = projectInstallValues(maps, kinds, install.ord);
|
|
15764
|
+
const subject = envelope.user?.anonId ?? null;
|
|
15750
15765
|
return {
|
|
15751
15766
|
env,
|
|
15752
15767
|
site: envelope.site,
|
|
@@ -15755,18 +15770,19 @@ var buildContext = ({
|
|
|
15755
15770
|
ord: install.ord,
|
|
15756
15771
|
installId: install.id,
|
|
15757
15772
|
installLabel: install.label,
|
|
15758
|
-
anonId:
|
|
15773
|
+
anonId: subject
|
|
15759
15774
|
}),
|
|
15760
15775
|
variables,
|
|
15761
15776
|
secrets,
|
|
15762
15777
|
appVariables: appScope.appVariables,
|
|
15763
15778
|
appSecrets: appScope.appSecrets,
|
|
15764
|
-
user: envelope.user,
|
|
15779
|
+
user: envelope.user ?? null,
|
|
15765
15780
|
consent: envelope.consent,
|
|
15766
15781
|
geo: envelope.geo,
|
|
15767
15782
|
// Collect one entry past the per-install cap: the platform rejects an over-limit group whole,
|
|
15768
15783
|
// and the extra entry keeps it visibly over while bounding memory.
|
|
15769
15784
|
emit: (event) => {
|
|
15785
|
+
requireSubject(subject, "emit");
|
|
15770
15786
|
if (event.type !== "track") {
|
|
15771
15787
|
throw new Error("only track events may be emitted");
|
|
15772
15788
|
}
|
|
@@ -15779,6 +15795,7 @@ var buildContext = ({
|
|
|
15779
15795
|
}
|
|
15780
15796
|
},
|
|
15781
15797
|
push: (message) => {
|
|
15798
|
+
requireSubject(subject, "push");
|
|
15782
15799
|
if (pushes.length <= MAX_PUSHES_PER_INSTALL) {
|
|
15783
15800
|
pushes.push(message);
|
|
15784
15801
|
}
|
|
@@ -15786,6 +15803,7 @@ var buildContext = ({
|
|
|
15786
15803
|
attribution: {
|
|
15787
15804
|
values: envelope.attribution ?? {},
|
|
15788
15805
|
set: (key, value) => {
|
|
15806
|
+
requireSubject(subject, "attribution.set");
|
|
15789
15807
|
if (attribution.length <= MAX_ATTRIBUTION_WRITES_PER_INSTALL) {
|
|
15790
15808
|
attribution.push({ key, value });
|
|
15791
15809
|
}
|
|
@@ -15793,19 +15811,21 @@ var buildContext = ({
|
|
|
15793
15811
|
},
|
|
15794
15812
|
facts: {
|
|
15795
15813
|
list: (input) => {
|
|
15796
|
-
return brokerFactsRead(env,
|
|
15814
|
+
return brokerFactsRead(env, requireSubject(subject, "facts.list"), input);
|
|
15797
15815
|
}
|
|
15798
15816
|
},
|
|
15799
15817
|
context: {
|
|
15800
15818
|
private: {
|
|
15801
15819
|
entry: envelope.context?.private ?? null,
|
|
15802
15820
|
set: (patch) => {
|
|
15821
|
+
requireSubject(subject, "context.set");
|
|
15803
15822
|
context.set("private", patch);
|
|
15804
15823
|
}
|
|
15805
15824
|
},
|
|
15806
15825
|
shared: {
|
|
15807
15826
|
byApp: envelope.context?.shared ?? {},
|
|
15808
15827
|
set: (patch) => {
|
|
15828
|
+
requireSubject(subject, "context.set");
|
|
15809
15829
|
context.set("shared", patch);
|
|
15810
15830
|
}
|
|
15811
15831
|
}
|
|
@@ -15837,6 +15857,48 @@ var failureMessage = (error51) => {
|
|
|
15837
15857
|
return message.slice(0, MAX_ERROR_MESSAGE_LENGTH);
|
|
15838
15858
|
};
|
|
15839
15859
|
|
|
15860
|
+
// src/catalog-query.ts
|
|
15861
|
+
var MAX_CATALOG_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
15862
|
+
var parseCatalogResponse = async (response) => {
|
|
15863
|
+
const raw = await readBody(response, MAX_CATALOG_RESPONSE_BYTES);
|
|
15864
|
+
if (raw === null) {
|
|
15865
|
+
throw new Error("The catalog query response exceeded the app runtime limit.");
|
|
15866
|
+
}
|
|
15867
|
+
let body;
|
|
15868
|
+
try {
|
|
15869
|
+
body = JSON.parse(raw);
|
|
15870
|
+
} catch {
|
|
15871
|
+
throw new Error(`The catalog query returned an invalid response (${response.status}).`);
|
|
15872
|
+
}
|
|
15873
|
+
if (!response.ok) {
|
|
15874
|
+
const error51 = body;
|
|
15875
|
+
const reason = typeof error51.message === "string" ? error51.message : typeof error51.error === "string" ? error51.error : `request failed with status ${response.status}`;
|
|
15876
|
+
throw new Error(`Catalog query failed: ${reason}`);
|
|
15877
|
+
}
|
|
15878
|
+
return body;
|
|
15879
|
+
};
|
|
15880
|
+
var buildCatalogQuery = ({ env, ticket, installId }) => {
|
|
15881
|
+
return {
|
|
15882
|
+
catalog: async (resourceIdentifier, sql) => {
|
|
15883
|
+
const platform = env;
|
|
15884
|
+
const context = JSON.parse(platform.APP_CONTEXT);
|
|
15885
|
+
const path = appUiCatalogQueryPath(context.domainId, context.slug, resourceIdentifier);
|
|
15886
|
+
return traced(
|
|
15887
|
+
"qinto.catalog.query",
|
|
15888
|
+
{ attrs: { "app.install_id": installId, "app.catalog.resource": resourceIdentifier } },
|
|
15889
|
+
async () => {
|
|
15890
|
+
const response = await fetch(new URL(path, platform.QINTO_PLATFORM_API), {
|
|
15891
|
+
method: "POST",
|
|
15892
|
+
headers: { authorization: `Bearer ${ticket}`, "content-type": "application/json" },
|
|
15893
|
+
body: JSON.stringify({ sql })
|
|
15894
|
+
});
|
|
15895
|
+
return parseCatalogResponse(response);
|
|
15896
|
+
}
|
|
15897
|
+
);
|
|
15898
|
+
}
|
|
15899
|
+
};
|
|
15900
|
+
};
|
|
15901
|
+
|
|
15840
15902
|
// src/schedule.ts
|
|
15841
15903
|
var dispatchSchedule = async (envelope, bundle, env) => {
|
|
15842
15904
|
const schedule = envelope.schedule;
|
|
@@ -15870,6 +15932,8 @@ var dispatchSchedule = async (envelope, bundle, env) => {
|
|
|
15870
15932
|
appVariables: appScope.appVariables,
|
|
15871
15933
|
appSecrets: appScope.appSecrets,
|
|
15872
15934
|
observability: buildObservability(),
|
|
15935
|
+
apps: buildAppsClient(env, { depth: 0 }),
|
|
15936
|
+
query: buildCatalogQuery({ env, ticket: install.catalogQueryTicket, installId: install.id }),
|
|
15873
15937
|
push: async (userId, message) => {
|
|
15874
15938
|
await brokerPush(env, {
|
|
15875
15939
|
userId,
|
|
@@ -16073,14 +16137,15 @@ var buildContext2 = ({ env, maps, kinds, appScope, install }) => {
|
|
|
16073
16137
|
return {
|
|
16074
16138
|
env,
|
|
16075
16139
|
install: { id: install.id, label: install.label },
|
|
16076
|
-
// An endpoint has no visitor, so a workflow it starts cannot push
|
|
16077
|
-
//
|
|
16140
|
+
// An endpoint has no visitor, so a workflow it starts cannot push. `push` below is the
|
|
16141
|
+
// exception: it names its subject rather than inheriting one.
|
|
16078
16142
|
startWorkflow: buildStartWorkflow({ ord: install.ord, installId: install.id, installLabel: install.label, anonId: null }),
|
|
16079
16143
|
variables,
|
|
16080
16144
|
secrets,
|
|
16081
16145
|
appVariables: appScope.appVariables,
|
|
16082
16146
|
appSecrets: appScope.appSecrets,
|
|
16083
16147
|
observability: buildObservability(),
|
|
16148
|
+
apps: buildAppsClient(env, { depth: 0 }),
|
|
16084
16149
|
push: async (userId, message) => {
|
|
16085
16150
|
await brokerPush(env, {
|
|
16086
16151
|
userId,
|
|
@@ -16361,7 +16426,8 @@ var dispatchUi = async ({ request, env, versions }) => {
|
|
|
16361
16426
|
appVariables,
|
|
16362
16427
|
appSecrets,
|
|
16363
16428
|
viewer: context.viewer,
|
|
16364
|
-
observability: buildObservability()
|
|
16429
|
+
observability: buildObservability(),
|
|
16430
|
+
apps: buildAppsClient(env, { depth: 0 })
|
|
16365
16431
|
};
|
|
16366
16432
|
let response;
|
|
16367
16433
|
try {
|
|
@@ -16382,6 +16448,29 @@ var dispatchUi = async ({ request, env, versions }) => {
|
|
|
16382
16448
|
return response;
|
|
16383
16449
|
};
|
|
16384
16450
|
|
|
16451
|
+
// src/durable-object.ts
|
|
16452
|
+
var VERSION_KEY = "qintoSchemaVersion";
|
|
16453
|
+
var applyDurableObjectMigrations = (storage, migrations) => {
|
|
16454
|
+
const applied = storage.kv.get(VERSION_KEY) ?? 0;
|
|
16455
|
+
for (const [index, migration] of migrations.entries()) {
|
|
16456
|
+
if (index < applied) {
|
|
16457
|
+
continue;
|
|
16458
|
+
}
|
|
16459
|
+
for (const statement of migration.statements) {
|
|
16460
|
+
storage.sql.exec(statement);
|
|
16461
|
+
}
|
|
16462
|
+
storage.kv.put(VERSION_KEY, index + 1);
|
|
16463
|
+
}
|
|
16464
|
+
};
|
|
16465
|
+
var withAppDurableObject = (Base, migrations) => {
|
|
16466
|
+
return class extends Base {
|
|
16467
|
+
constructor(ctx, env) {
|
|
16468
|
+
applyDurableObjectMigrations(ctx.storage, migrations);
|
|
16469
|
+
super(ctx, env);
|
|
16470
|
+
}
|
|
16471
|
+
};
|
|
16472
|
+
};
|
|
16473
|
+
|
|
16385
16474
|
// src/index.ts
|
|
16386
16475
|
installTracedFetch();
|
|
16387
16476
|
var toHandler = (candidate) => {
|
|
@@ -16528,9 +16617,16 @@ var dispatchRpc = async (envelope, bundle, env) => {
|
|
|
16528
16617
|
const maps = parseVariableMaps(env);
|
|
16529
16618
|
const { variableKinds: kinds, appVariableKinds } = contextKindsOf(env);
|
|
16530
16619
|
const appScope = projectAppValues(env, appVariableKinds);
|
|
16620
|
+
const subject = envelope.user?.anonId ?? null;
|
|
16531
16621
|
const ctx = {
|
|
16532
16622
|
...buildContext({ env, envelope, install, maps, kinds, appScope, emits, pushes, attribution, context }),
|
|
16533
|
-
context: fetchedContext(
|
|
16623
|
+
context: fetchedContext(
|
|
16624
|
+
env,
|
|
16625
|
+
(what) => {
|
|
16626
|
+
return requireSubject(subject, what);
|
|
16627
|
+
},
|
|
16628
|
+
context
|
|
16629
|
+
)
|
|
16534
16630
|
};
|
|
16535
16631
|
let value;
|
|
16536
16632
|
try {
|
|
@@ -16639,5 +16735,6 @@ var createRuntime = (input) => {
|
|
|
16639
16735
|
};
|
|
16640
16736
|
export {
|
|
16641
16737
|
createRuntime,
|
|
16642
|
-
createWorkflow
|
|
16738
|
+
createWorkflow,
|
|
16739
|
+
withAppDurableObject
|
|
16643
16740
|
};
|
|
@@ -8,8 +8,10 @@ var withSecurityHeaders = /* @__PURE__ */ __name((response, portalOrigin) => {
|
|
|
8
8
|
response.headers.set("content-security-policy", `frame-ancestors ${portalOrigin}`);
|
|
9
9
|
response.headers.set("x-content-type-options", "nosniff");
|
|
10
10
|
response.headers.set("referrer-policy", "no-referrer");
|
|
11
|
+
response.headers.set("vary", "sec-fetch-dest");
|
|
11
12
|
return response;
|
|
12
13
|
}, "withSecurityHeaders");
|
|
14
|
+
var NAVIGATION_DESTINATIONS = /* @__PURE__ */ new Set(["document", "object", "embed"]);
|
|
13
15
|
var assetKeys = /* @__PURE__ */ __name((hostname, pathname) => {
|
|
14
16
|
const dot = hostname.indexOf(".");
|
|
15
17
|
const slug = dot === -1 ? hostname : hostname.slice(0, dot);
|
|
@@ -27,6 +29,10 @@ var serve = /* @__PURE__ */ __name(async (request, env) => {
|
|
|
27
29
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
28
30
|
return new Response("method not allowed", { status: 405 });
|
|
29
31
|
}
|
|
32
|
+
const destination = request.headers.get("sec-fetch-dest");
|
|
33
|
+
if (destination !== null && NAVIGATION_DESTINATIONS.has(destination)) {
|
|
34
|
+
return new Response("unauthorized", { status: 401 });
|
|
35
|
+
}
|
|
30
36
|
const url = new URL(request.url);
|
|
31
37
|
const keys = assetKeys(url.hostname, url.pathname);
|
|
32
38
|
if (keys === null) {
|