@kehto/paja 0.14.0 → 0.15.0
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 +32 -13
- package/dist/browser-host.js +431 -79
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-JHDQSR5N.js → chunk-DTRRHXYY.js} +27 -1
- package/dist/chunk-DTRRHXYY.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-JHDQSR5N.js.map +0 -1
package/dist/browser-host.js
CHANGED
|
@@ -1329,7 +1329,7 @@ function handleRelayPublish(context, windowId, m) {
|
|
|
1329
1329
|
sendRelayPublishResult(hooks, windowId, id, false, void 0, "invalid event template");
|
|
1330
1330
|
return;
|
|
1331
1331
|
}
|
|
1332
|
-
const signer = hooks.auth.getSigner();
|
|
1332
|
+
const signer = hooks.auth.getSigner(windowId);
|
|
1333
1333
|
const signEvent = signer?.signEvent?.bind(signer);
|
|
1334
1334
|
if (!signEvent) {
|
|
1335
1335
|
sendRelayPublishResult(hooks, windowId, id, false, void 0, "no signer configured");
|
|
@@ -1440,7 +1440,7 @@ function handleRelayPublishEncrypted(context, windowId, msg) {
|
|
|
1440
1440
|
replyPe(false, { error: `unsupported encryption scheme: ${encryption}` });
|
|
1441
1441
|
return;
|
|
1442
1442
|
}
|
|
1443
|
-
const peSigner = hooks.auth.getSigner();
|
|
1443
|
+
const peSigner = hooks.auth.getSigner(windowId);
|
|
1444
1444
|
if (!peSigner) {
|
|
1445
1445
|
replyPe(false, { error: "no signer configured" });
|
|
1446
1446
|
return;
|
|
@@ -1449,12 +1449,9 @@ function handleRelayPublishEncrypted(context, windowId, msg) {
|
|
|
1449
1449
|
replyPe(false, { error: "invalid event template" });
|
|
1450
1450
|
return;
|
|
1451
1451
|
}
|
|
1452
|
-
publishEncrypted(context, windowId, id, recipient, encryption, eventTemplate, replyPe);
|
|
1452
|
+
publishEncrypted(context, windowId, id, recipient, encryption, eventTemplate, peSigner, replyPe);
|
|
1453
1453
|
}
|
|
1454
|
-
function publishEncrypted(context, windowId, id, recipient, encryption, eventTemplate, replyPe) {
|
|
1455
|
-
const { hooks } = context;
|
|
1456
|
-
const peSigner = hooks.auth.getSigner();
|
|
1457
|
-
if (!peSigner) return;
|
|
1454
|
+
function publishEncrypted(context, windowId, id, recipient, encryption, eventTemplate, peSigner, replyPe) {
|
|
1458
1455
|
(async () => {
|
|
1459
1456
|
try {
|
|
1460
1457
|
const plaintext = String(eventTemplate.content ?? "");
|
|
@@ -1697,7 +1694,7 @@ function createIdentityHandler(context) {
|
|
|
1697
1694
|
return;
|
|
1698
1695
|
}
|
|
1699
1696
|
const action = msg.type.slice("identity.".length);
|
|
1700
|
-
const signer = hooks.auth.getSigner();
|
|
1697
|
+
const signer = hooks.auth.getSigner(windowId);
|
|
1701
1698
|
const fallback = createCanonicalDomainResult(msg);
|
|
1702
1699
|
if (!fallback) return;
|
|
1703
1700
|
const sendResult = (payload = {}) => {
|
|
@@ -2835,8 +2832,8 @@ function createAuthAdapter(shellHooks) {
|
|
|
2835
2832
|
getUserPubkey() {
|
|
2836
2833
|
return shellHooks.auth.getUserPubkey();
|
|
2837
2834
|
},
|
|
2838
|
-
getSigner() {
|
|
2839
|
-
return shellHooks.auth.getSigner();
|
|
2835
|
+
getSigner(windowId) {
|
|
2836
|
+
return shellHooks.auth.getSigner(windowId);
|
|
2840
2837
|
}
|
|
2841
2838
|
};
|
|
2842
2839
|
}
|
|
@@ -11087,11 +11084,20 @@ function requestKey(windowId, requestId) {
|
|
|
11087
11084
|
return `${windowId}\0${requestId}`;
|
|
11088
11085
|
}
|
|
11089
11086
|
function assertResourceOptions(options) {
|
|
11090
|
-
if (typeof options?.fetch !== "function"
|
|
11087
|
+
if (typeof options?.fetch !== "function") {
|
|
11091
11088
|
throw new Error(
|
|
11092
|
-
"[RESOURCE-01
|
|
11089
|
+
"[RESOURCE-01] createResourceService requires a runtime-provided fetch resolver."
|
|
11093
11090
|
);
|
|
11094
11091
|
}
|
|
11092
|
+
const hasOriginPolicy = options.isOriginGranted !== void 0 || options.getConnectGrants !== void 0;
|
|
11093
|
+
if (hasOriginPolicy && (typeof options.isOriginGranted !== "function" || typeof options.getConnectGrants !== "function" || typeof options.resolveIdentity !== "function")) {
|
|
11094
|
+
throw new Error(
|
|
11095
|
+
"[RESOURCE-01] optional origin policy requires {isOriginGranted, getConnectGrants, resolveIdentity} together."
|
|
11096
|
+
);
|
|
11097
|
+
}
|
|
11098
|
+
if (options.resolveIdentity !== void 0 && typeof options.resolveIdentity !== "function") {
|
|
11099
|
+
throw new Error("[RESOURCE-01] resolveIdentity must be a function when provided.");
|
|
11100
|
+
}
|
|
11095
11101
|
}
|
|
11096
11102
|
function trackRequest(state, requestId, windowId, controller) {
|
|
11097
11103
|
const key = requestKey(windowId, requestId);
|
|
@@ -11147,7 +11153,7 @@ function normalizeResourceInfo(info) {
|
|
|
11147
11153
|
}
|
|
11148
11154
|
async function resolveResourceInfo(options, windowId) {
|
|
11149
11155
|
const configured = options.resourceInfo ?? DEFAULT_RESOURCE_INFO;
|
|
11150
|
-
const info = typeof configured === "function" ? await configured({ windowId, identity: options.resolveIdentity(windowId) }) : configured;
|
|
11156
|
+
const info = typeof configured === "function" ? await configured({ windowId, identity: options.resolveIdentity?.(windowId) ?? null }) : configured;
|
|
11151
11157
|
return normalizeResourceInfo(info);
|
|
11152
11158
|
}
|
|
11153
11159
|
async function handleInfo(options, windowId, requestId, send) {
|
|
@@ -11181,22 +11187,29 @@ function classifyResourceFailure(error) {
|
|
|
11181
11187
|
message: error instanceof Error ? error.message : String(error)
|
|
11182
11188
|
};
|
|
11183
11189
|
}
|
|
11184
|
-
async function fetchResourceItem(options,
|
|
11190
|
+
async function fetchResourceItem(options, windowId, info, url, signal) {
|
|
11185
11191
|
const parsedUrl = parseResourceUrl(url);
|
|
11186
11192
|
if (!parsedUrl) return resourceInvalidRequest(url, `invalid URL: ${url}`);
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
11193
|
+
if (options.isOriginGranted && options.getConnectGrants) {
|
|
11194
|
+
const identity = options.resolveIdentity?.(windowId) ?? null;
|
|
11195
|
+
if (!identity) {
|
|
11196
|
+
return {
|
|
11197
|
+
ok: false,
|
|
11198
|
+
url,
|
|
11199
|
+
error: "blocked-by-policy",
|
|
11200
|
+
message: "napplet identity not resolvable for runtime origin policy"
|
|
11201
|
+
};
|
|
11202
|
+
}
|
|
11203
|
+
const origin = parsedUrl.origin;
|
|
11204
|
+
const grants = options.getConnectGrants(identity.dTag, identity.aggregateHash);
|
|
11205
|
+
if (!options.isOriginGranted(origin, grants)) {
|
|
11206
|
+
return {
|
|
11207
|
+
ok: false,
|
|
11208
|
+
url,
|
|
11209
|
+
error: "blocked-by-policy",
|
|
11210
|
+
message: `origin ${origin} not granted by runtime policy`
|
|
11211
|
+
};
|
|
11212
|
+
}
|
|
11200
11213
|
}
|
|
11201
11214
|
try {
|
|
11202
11215
|
const response = await options.fetch(url, {
|
|
@@ -11226,16 +11239,11 @@ async function fetchResourceItem(options, identity, info, url, signal) {
|
|
|
11226
11239
|
}
|
|
11227
11240
|
async function handleBytes(options, state, windowId, msg, send) {
|
|
11228
11241
|
const { requestId, url } = msg;
|
|
11229
|
-
const identity = options.resolveIdentity(windowId);
|
|
11230
|
-
if (!identity) {
|
|
11231
|
-
sendResourceError(send, requestId, "napplet identity not resolvable", "blocked-by-policy");
|
|
11232
|
-
return;
|
|
11233
|
-
}
|
|
11234
11242
|
const controller = new AbortController();
|
|
11235
11243
|
trackRequest(state, requestId, windowId, controller);
|
|
11236
11244
|
try {
|
|
11237
11245
|
const info = await resolveResourceInfo(options, windowId);
|
|
11238
|
-
const item = await fetchResourceItem(options,
|
|
11246
|
+
const item = await fetchResourceItem(options, windowId, info, url, controller.signal);
|
|
11239
11247
|
if (controller.signal.aborted) return;
|
|
11240
11248
|
if (!item.ok) {
|
|
11241
11249
|
sendResourceError(send, requestId, item.message, item.error);
|
|
@@ -11262,11 +11270,6 @@ async function handleBytesMany(options, state, windowId, msg, send) {
|
|
|
11262
11270
|
sendBytesManyError(send, requestId, "resource.bytesMany requires a non-empty urls array", "invalid-request");
|
|
11263
11271
|
return;
|
|
11264
11272
|
}
|
|
11265
|
-
const identity = options.resolveIdentity(windowId);
|
|
11266
|
-
if (!identity) {
|
|
11267
|
-
sendBytesManyError(send, requestId, "napplet identity not resolvable", "blocked-by-policy");
|
|
11268
|
-
return;
|
|
11269
|
-
}
|
|
11270
11273
|
let info;
|
|
11271
11274
|
try {
|
|
11272
11275
|
info = await resolveResourceInfo(options, windowId);
|
|
@@ -11284,7 +11287,7 @@ async function handleBytesMany(options, state, windowId, msg, send) {
|
|
|
11284
11287
|
try {
|
|
11285
11288
|
const items = [];
|
|
11286
11289
|
for (const url of urls) {
|
|
11287
|
-
const item = await fetchResourceItem(options,
|
|
11290
|
+
const item = await fetchResourceItem(options, windowId, info, url, controller.signal);
|
|
11288
11291
|
if (controller.signal.aborted) return;
|
|
11289
11292
|
if (item.ok) {
|
|
11290
11293
|
items.push({
|
|
@@ -11343,7 +11346,7 @@ function createResourceService(options) {
|
|
|
11343
11346
|
const descriptor = {
|
|
11344
11347
|
name: "resource",
|
|
11345
11348
|
version: RESOURCE_SERVICE_VERSION,
|
|
11346
|
-
description: "NAP-RESOURCE
|
|
11349
|
+
description: "NAP-RESOURCE service \u2014 runtime-resolved byte transport (RESOURCE-01..06)"
|
|
11347
11350
|
};
|
|
11348
11351
|
const handler = {
|
|
11349
11352
|
descriptor,
|
|
@@ -18186,6 +18189,9 @@ function createPajaResourceFetch(options = {}) {
|
|
|
18186
18189
|
if (url.protocol === "blossom:") {
|
|
18187
18190
|
return fetchBlossomResource(value, init.signal, options);
|
|
18188
18191
|
}
|
|
18192
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
18193
|
+
return fetchNetworkResource(value, init.signal, options.fetch ?? globalThis.fetch);
|
|
18194
|
+
}
|
|
18189
18195
|
if (url.protocol !== "data:") {
|
|
18190
18196
|
throw new ResourceServiceError("unsupported-scheme", `Paja does not enable ${url.protocol}`);
|
|
18191
18197
|
}
|
|
@@ -18205,7 +18211,11 @@ function createPajaResourceFetch(options = {}) {
|
|
|
18205
18211
|
};
|
|
18206
18212
|
}
|
|
18207
18213
|
function pajaResourceInfo(blossomServers = []) {
|
|
18208
|
-
const schemes = [
|
|
18214
|
+
const schemes = [
|
|
18215
|
+
{ scheme: "data", enabled: true },
|
|
18216
|
+
{ scheme: "https", enabled: true },
|
|
18217
|
+
{ scheme: "http", enabled: true }
|
|
18218
|
+
];
|
|
18209
18219
|
if (usableBlossomServers(blossomServers).length > 0) {
|
|
18210
18220
|
schemes.push({ scheme: "blossom", enabled: true });
|
|
18211
18221
|
}
|
|
@@ -18215,6 +18225,35 @@ function pajaResourceInfo(blossomServers = []) {
|
|
|
18215
18225
|
maxUrls: PAJA_RESOURCE_MAX_URLS
|
|
18216
18226
|
};
|
|
18217
18227
|
}
|
|
18228
|
+
async function fetchNetworkResource(value, signal, fetcher) {
|
|
18229
|
+
let response;
|
|
18230
|
+
try {
|
|
18231
|
+
response = await fetcher(value, {
|
|
18232
|
+
method: "GET",
|
|
18233
|
+
signal,
|
|
18234
|
+
credentials: "omit",
|
|
18235
|
+
referrerPolicy: "no-referrer"
|
|
18236
|
+
});
|
|
18237
|
+
} catch (error) {
|
|
18238
|
+
if (signal.aborted || isAbortError(error)) throw error;
|
|
18239
|
+
throw new ResourceServiceError(
|
|
18240
|
+
"network-error",
|
|
18241
|
+
error instanceof Error ? error.message : String(error)
|
|
18242
|
+
);
|
|
18243
|
+
}
|
|
18244
|
+
if (response.status === 404 || response.status === 410) {
|
|
18245
|
+
throw new ResourceServiceError("not-found", "HTTP resource was not found");
|
|
18246
|
+
}
|
|
18247
|
+
if (!response.ok) {
|
|
18248
|
+
throw new ResourceServiceError("network-error", `HTTP resource returned ${response.status}`);
|
|
18249
|
+
}
|
|
18250
|
+
const bytes = await readCappedResponse(response, signal);
|
|
18251
|
+
const mime = sniffSafeResourceMime(bytes);
|
|
18252
|
+
if (!mime) {
|
|
18253
|
+
throw new ResourceServiceError("decode-failed", "resource bytes are not an enabled safe media type");
|
|
18254
|
+
}
|
|
18255
|
+
return new Response(arrayBufferFor(bytes), { headers: { "content-type": mime } });
|
|
18256
|
+
}
|
|
18218
18257
|
async function fetchBlossomResource(value, signal, options) {
|
|
18219
18258
|
const match = BLOSSOM_RESOURCE_PATTERN.exec(value);
|
|
18220
18259
|
if (!match?.[1]) {
|
|
@@ -19421,12 +19460,17 @@ async function createPajaBrowserFsBackend(options) {
|
|
|
19421
19460
|
// src/browser-dev-runtime.ts
|
|
19422
19461
|
var DEV_SIGNER_SECRET_KEY = generateSecretKey();
|
|
19423
19462
|
var PAJA_DEV_SIGNER_PUBKEY = getPublicKey(DEV_SIGNER_SECRET_KEY);
|
|
19424
|
-
function createPajaDevSigner(getSimulation, confirm) {
|
|
19463
|
+
function createPajaDevSigner(getSimulation, confirm, source) {
|
|
19425
19464
|
return {
|
|
19426
19465
|
getPublicKey: () => PAJA_DEV_SIGNER_PUBKEY,
|
|
19427
19466
|
getRelays: () => Object.fromEntries(getPajaRelayUrls(getSimulation()).map((relay) => [relay, { read: true, write: true }])),
|
|
19428
19467
|
async signEvent(event) {
|
|
19429
|
-
|
|
19468
|
+
const signerContext = source ? { ...source, signerPubkey: PAJA_DEV_SIGNER_PUBKEY } : void 0;
|
|
19469
|
+
if (!await confirm({
|
|
19470
|
+
action: "sign",
|
|
19471
|
+
event,
|
|
19472
|
+
...signerContext ? { signerContext } : {}
|
|
19473
|
+
})) {
|
|
19430
19474
|
throw new Error("Paja signing request denied");
|
|
19431
19475
|
}
|
|
19432
19476
|
const template = { ...event };
|
|
@@ -19549,10 +19593,12 @@ function createDevTheme(mode, values) {
|
|
|
19549
19593
|
function getRuntimePubkey(getSimulation, signerProvider) {
|
|
19550
19594
|
return signerProvider?.getPubkey() || (signerProvider?.getMethod() === "dev" ? PAJA_DEV_SIGNER_PUBKEY : "") || getSimulation().identity.pubkey;
|
|
19551
19595
|
}
|
|
19552
|
-
function createRuntimeSigner(getSimulation, confirmRequest, signerProvider) {
|
|
19553
|
-
const signer = signerProvider?.getSigner();
|
|
19596
|
+
function createRuntimeSigner(getSimulation, confirmRequest, signerProvider, source) {
|
|
19597
|
+
const signer = signerProvider?.getSigner(source);
|
|
19554
19598
|
if (!signer) {
|
|
19555
|
-
if (signerProvider?.getMethod() === "dev")
|
|
19599
|
+
if (signerProvider?.getMethod() === "dev") {
|
|
19600
|
+
return createPajaDevSigner(getSimulation, confirmRequest, source);
|
|
19601
|
+
}
|
|
19556
19602
|
const fixedPubkey = getSimulation().identity.pubkey;
|
|
19557
19603
|
if (fixedPubkey) {
|
|
19558
19604
|
return {
|
|
@@ -19596,9 +19642,6 @@ function createDevServices(backend, getSimulation, relayConfig, onThemeService,
|
|
|
19596
19642
|
const services = {
|
|
19597
19643
|
resource: createResourceService({
|
|
19598
19644
|
fetch: createPajaResourceFetch({ getBlossomServers }),
|
|
19599
|
-
isOriginGranted: (origin, grants) => grants.includes(origin),
|
|
19600
|
-
getConnectGrants: () => ["null"],
|
|
19601
|
-
resolveIdentity: (windowId) => getIdentity?.(windowId) ?? null,
|
|
19602
19645
|
resourceInfo: () => pajaResourceInfo(getBlossomServers())
|
|
19603
19646
|
})
|
|
19604
19647
|
};
|
|
@@ -19772,6 +19815,18 @@ function createDevServices(backend, getSimulation, relayConfig, onThemeService,
|
|
|
19772
19815
|
return { services, refreshAvailability };
|
|
19773
19816
|
}
|
|
19774
19817
|
function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadcast, confirmRequest, signerProvider, getIdentity, onEnvironmentChanged, intentHost, userActivation, notifyOptions, configOptions) {
|
|
19818
|
+
const resolveIdentity = (windowId) => getIdentity?.(windowId) ?? {
|
|
19819
|
+
dTag: config.window.dTag,
|
|
19820
|
+
aggregateHash: config.window.aggregateHash
|
|
19821
|
+
};
|
|
19822
|
+
const signerSource = (windowId) => {
|
|
19823
|
+
const napplet = resolveIdentity(windowId);
|
|
19824
|
+
return {
|
|
19825
|
+
windowId,
|
|
19826
|
+
napplet,
|
|
19827
|
+
runtimeScope: config.target?.mode === "iframe-url" ? `target-url:${config.target.url}` : `artifact:${napplet.aggregateHash}`
|
|
19828
|
+
};
|
|
19829
|
+
};
|
|
19775
19830
|
const relayBackend = createPajaRelayBackend(getSimulation, confirmRequest);
|
|
19776
19831
|
const relayConfig = createPajaRelayConfig(getSimulation);
|
|
19777
19832
|
const uploadRuntime = getSimulation().upload.mode === "blossom" ? createPajaUploadRuntime({
|
|
@@ -19781,10 +19836,7 @@ function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadca
|
|
|
19781
19836
|
queryDiscovery: (relayUrls2, filters) => relayBackend.query(relayUrls2, filters),
|
|
19782
19837
|
getRelayUrls: () => relayConfig.getRelayUrls(["discovery", "super"]),
|
|
19783
19838
|
confirmRequest,
|
|
19784
|
-
getNappletIdentity:
|
|
19785
|
-
dTag: config.window.dTag,
|
|
19786
|
-
aggregateHash: config.window.aggregateHash
|
|
19787
|
-
},
|
|
19839
|
+
getNappletIdentity: resolveIdentity,
|
|
19788
19840
|
subscribeSignerChange: signerProvider?.subscribe?.bind(signerProvider)
|
|
19789
19841
|
}) : void 0;
|
|
19790
19842
|
const getBlossomServers = () => [
|
|
@@ -19804,20 +19856,14 @@ function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadca
|
|
|
19804
19856
|
uploadRuntime,
|
|
19805
19857
|
signerProvider,
|
|
19806
19858
|
intentHost,
|
|
19807
|
-
|
|
19808
|
-
dTag: config.window.dTag,
|
|
19809
|
-
aggregateHash: config.window.aggregateHash
|
|
19810
|
-
})),
|
|
19859
|
+
resolveIdentity,
|
|
19811
19860
|
userActivation,
|
|
19812
19861
|
notifyOptions,
|
|
19813
19862
|
configOptions
|
|
19814
19863
|
);
|
|
19815
19864
|
const services = serviceBundle.services;
|
|
19816
19865
|
const ready = createPajaBrowserFsBackend({
|
|
19817
|
-
getIdentity:
|
|
19818
|
-
dTag: config.window.dTag,
|
|
19819
|
-
aggregateHash: config.window.aggregateHash
|
|
19820
|
-
},
|
|
19866
|
+
getIdentity: resolveIdentity,
|
|
19821
19867
|
userActivation
|
|
19822
19868
|
}).then((fsBackend) => {
|
|
19823
19869
|
if (!fsBackend) return;
|
|
@@ -19839,7 +19885,12 @@ function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadca
|
|
|
19839
19885
|
windowManager: { createWindow: () => null },
|
|
19840
19886
|
auth: {
|
|
19841
19887
|
getUserPubkey: () => getRuntimePubkey(getSimulation, signerProvider),
|
|
19842
|
-
getSigner: () => createRuntimeSigner(
|
|
19888
|
+
getSigner: (windowId) => createRuntimeSigner(
|
|
19889
|
+
getSimulation,
|
|
19890
|
+
confirmRequest,
|
|
19891
|
+
signerProvider,
|
|
19892
|
+
windowId ? signerSource(windowId) : void 0
|
|
19893
|
+
)
|
|
19843
19894
|
},
|
|
19844
19895
|
services,
|
|
19845
19896
|
get capabilities() {
|
|
@@ -20105,13 +20156,18 @@ function renderAclControls(state, bridge) {
|
|
|
20105
20156
|
function renderSignerStatus(state, signerPubkey) {
|
|
20106
20157
|
const el = document.getElementById("signer-status");
|
|
20107
20158
|
const controls = document.getElementById("signer-controls");
|
|
20108
|
-
|
|
20159
|
+
const clearConsent = document.getElementById("signer-consent-clear");
|
|
20160
|
+
if (!el || !controls || !(clearConsent instanceof HTMLButtonElement)) return;
|
|
20109
20161
|
const fallbackPubkey = state.simulation.identity.pubkey || (state.signer.method === "dev" ? signerPubkey : "");
|
|
20110
20162
|
const pubkey2 = state.simulation.identity.pubkey || state.signer.pubkey || (state.signer.method === "dev" ? signerPubkey : "");
|
|
20111
20163
|
const methodLabel = state.signer.method === "nip07" ? "NIP-07" : state.signer.method === "nip46" ? "bunker" : state.signer.method === "dev" ? "dev" : "none";
|
|
20112
20164
|
const relay = state.signer.relay ? ` \xB7 relay ${state.signer.relay}` : "";
|
|
20113
20165
|
const prefix = state.signer.status === "disconnected" ? "no signer connected" : state.signer.status === "error" ? `${methodLabel} error: ${state.signer.error ?? "unknown error"} \xB7 fallback pubkey ${fallbackPubkey}` : `${methodLabel} ${state.signer.status} \xB7 pubkey ${pubkey2 || "(none)"}${relay}`;
|
|
20114
|
-
|
|
20166
|
+
const consentSummary = state.signerConsentCount === 0 ? "signing asks every time" : `${state.signerConsentCount} remembered signing approval${state.signerConsentCount === 1 ? "" : "s"}`;
|
|
20167
|
+
el.textContent = `${prefix} \xB7 ${consentSummary} \xB7 publishing asks every time`;
|
|
20168
|
+
clearConsent.hidden = state.signerConsentCount === 0;
|
|
20169
|
+
clearConsent.textContent = `Forget ${state.signerConsentCount} remembered approval${state.signerConsentCount === 1 ? "" : "s"}`;
|
|
20170
|
+
clearConsent.onclick = () => state.clearSignerConsent();
|
|
20115
20171
|
const previousInput = controls.querySelector("#signer-bunker-uri")?.value ?? "";
|
|
20116
20172
|
const devButton = document.createElement("button");
|
|
20117
20173
|
devButton.type = "button";
|
|
@@ -20325,20 +20381,35 @@ function readNip07Signer() {
|
|
|
20325
20381
|
if (typeof signer.getPublicKey !== "function" || typeof signer.signEvent !== "function") return null;
|
|
20326
20382
|
return signer;
|
|
20327
20383
|
}
|
|
20328
|
-
function createConfirmedSigner(signer, confirmRequest) {
|
|
20384
|
+
function createConfirmedSigner(signer, confirmRequest, source) {
|
|
20329
20385
|
return {
|
|
20330
20386
|
...signer,
|
|
20331
20387
|
async signEvent(event) {
|
|
20332
20388
|
if (typeof signer.signEvent !== "function") {
|
|
20333
20389
|
throw new Error("Signer does not support event signing");
|
|
20334
20390
|
}
|
|
20335
|
-
|
|
20391
|
+
const signerContext = await resolveSignerContext(signer, source);
|
|
20392
|
+
if (!await confirmRequest({
|
|
20393
|
+
action: "sign",
|
|
20394
|
+
event,
|
|
20395
|
+
...signerContext ? { signerContext } : {}
|
|
20396
|
+
})) {
|
|
20336
20397
|
throw new Error("Paja signing request denied");
|
|
20337
20398
|
}
|
|
20338
20399
|
return signer.signEvent(event);
|
|
20339
20400
|
}
|
|
20340
20401
|
};
|
|
20341
20402
|
}
|
|
20403
|
+
async function resolveSignerContext(signer, source) {
|
|
20404
|
+
if (!source || typeof signer.getPublicKey !== "function") return void 0;
|
|
20405
|
+
try {
|
|
20406
|
+
const signerPubkey = await signer.getPublicKey();
|
|
20407
|
+
if (!/^[0-9a-f]{64}$/i.test(signerPubkey)) return void 0;
|
|
20408
|
+
return { ...source, signerPubkey };
|
|
20409
|
+
} catch {
|
|
20410
|
+
return void 0;
|
|
20411
|
+
}
|
|
20412
|
+
}
|
|
20342
20413
|
function createNip07Adapter(signer) {
|
|
20343
20414
|
return {
|
|
20344
20415
|
getPublicKey: signer.getPublicKey?.bind(signer),
|
|
@@ -20370,7 +20441,10 @@ function createPajaSignerController(options) {
|
|
|
20370
20441
|
};
|
|
20371
20442
|
return {
|
|
20372
20443
|
getState: () => cloneState(state),
|
|
20373
|
-
getSigner
|
|
20444
|
+
getSigner(source) {
|
|
20445
|
+
if (!activeSigner) return null;
|
|
20446
|
+
return createConfirmedSigner(activeSigner, options.confirmRequest, source);
|
|
20447
|
+
},
|
|
20374
20448
|
getMethod: () => state.method,
|
|
20375
20449
|
getPubkey: () => state.pubkey,
|
|
20376
20450
|
subscribe(listener) {
|
|
@@ -20396,7 +20470,7 @@ function createPajaSignerController(options) {
|
|
|
20396
20470
|
const signer = readNip07Signer();
|
|
20397
20471
|
if (!signer) throw new Error("NIP-07 signer not found");
|
|
20398
20472
|
const pubkey2 = await signer.getPublicKey();
|
|
20399
|
-
activeSigner =
|
|
20473
|
+
activeSigner = createNip07Adapter(signer);
|
|
20400
20474
|
update({ method: "nip07", status: "connected", pubkey: pubkey2, relay: null, error: null });
|
|
20401
20475
|
} catch (error) {
|
|
20402
20476
|
activeSigner = null;
|
|
@@ -20426,7 +20500,7 @@ function createPajaSignerController(options) {
|
|
|
20426
20500
|
});
|
|
20427
20501
|
const pubkey2 = await client.connect();
|
|
20428
20502
|
activeClient = client;
|
|
20429
|
-
activeSigner =
|
|
20503
|
+
activeSigner = client.getSigner();
|
|
20430
20504
|
update({ method: "nip46", status: "connected", pubkey: pubkey2, relay: parsed.relay, error: null });
|
|
20431
20505
|
} catch (error) {
|
|
20432
20506
|
closeClient();
|
|
@@ -20437,6 +20511,171 @@ function createPajaSignerController(options) {
|
|
|
20437
20511
|
};
|
|
20438
20512
|
}
|
|
20439
20513
|
|
|
20514
|
+
// src/browser-signer-consent.ts
|
|
20515
|
+
var PAJA_SIGNER_CONSENT_STORAGE_KEY = "kehto:paja:signer-consent:v1";
|
|
20516
|
+
var PAJA_SIGNER_CONSENT_VERSION = 1;
|
|
20517
|
+
var MAX_CONSENT_SUBJECTS = 128;
|
|
20518
|
+
var MAX_KINDS_PER_SUBJECT = 128;
|
|
20519
|
+
var MAX_IDENTITY_FIELD_LENGTH = 512;
|
|
20520
|
+
function createPajaSignerConsentStore(storage = readPajaSignerConsentStorage()) {
|
|
20521
|
+
let grants = readConsentDocument(storage).grants;
|
|
20522
|
+
const persist = () => {
|
|
20523
|
+
if (!storage) return;
|
|
20524
|
+
const document2 = {
|
|
20525
|
+
version: PAJA_SIGNER_CONSENT_VERSION,
|
|
20526
|
+
grants
|
|
20527
|
+
};
|
|
20528
|
+
try {
|
|
20529
|
+
storage.setItem(PAJA_SIGNER_CONSENT_STORAGE_KEY, JSON.stringify(document2));
|
|
20530
|
+
} catch {
|
|
20531
|
+
}
|
|
20532
|
+
};
|
|
20533
|
+
const recordFor = (context) => {
|
|
20534
|
+
const subject = normalizeContext(context);
|
|
20535
|
+
if (!subject) return null;
|
|
20536
|
+
return grants.find((grant2) => sameSubject(grant2, subject)) ?? null;
|
|
20537
|
+
};
|
|
20538
|
+
const ensureRecord = (context) => {
|
|
20539
|
+
const subject = normalizeContext(context);
|
|
20540
|
+
if (!subject) return null;
|
|
20541
|
+
const existing = grants.find((grant2) => sameSubject(grant2, subject));
|
|
20542
|
+
if (existing) return existing;
|
|
20543
|
+
if (grants.length >= MAX_CONSENT_SUBJECTS) return null;
|
|
20544
|
+
const created = { ...subject, trusted: false, kinds: [] };
|
|
20545
|
+
grants = [...grants, created];
|
|
20546
|
+
return created;
|
|
20547
|
+
};
|
|
20548
|
+
return {
|
|
20549
|
+
match(context, kind) {
|
|
20550
|
+
const normalizedKind = normalizeKind(kind);
|
|
20551
|
+
if (normalizedKind === null) return null;
|
|
20552
|
+
const record = recordFor(context);
|
|
20553
|
+
if (!record) return null;
|
|
20554
|
+
if (record.trusted) return "napplet";
|
|
20555
|
+
return record.kinds.includes(normalizedKind) ? "kind" : null;
|
|
20556
|
+
},
|
|
20557
|
+
rememberKind(context, kind) {
|
|
20558
|
+
const normalizedKind = normalizeKind(kind);
|
|
20559
|
+
if (normalizedKind === null) return false;
|
|
20560
|
+
const record = ensureRecord(context);
|
|
20561
|
+
if (!record) return false;
|
|
20562
|
+
if (record.trusted || record.kinds.includes(normalizedKind)) return true;
|
|
20563
|
+
if (record.kinds.length >= MAX_KINDS_PER_SUBJECT) return false;
|
|
20564
|
+
record.kinds = [...record.kinds, normalizedKind].sort((left, right) => left - right);
|
|
20565
|
+
persist();
|
|
20566
|
+
return true;
|
|
20567
|
+
},
|
|
20568
|
+
trustNapplet(context) {
|
|
20569
|
+
const record = ensureRecord(context);
|
|
20570
|
+
if (!record) return false;
|
|
20571
|
+
if (!record.trusted || record.kinds.length > 0) {
|
|
20572
|
+
record.trusted = true;
|
|
20573
|
+
record.kinds = [];
|
|
20574
|
+
persist();
|
|
20575
|
+
}
|
|
20576
|
+
return true;
|
|
20577
|
+
},
|
|
20578
|
+
clear() {
|
|
20579
|
+
const previous = grants;
|
|
20580
|
+
grants = [];
|
|
20581
|
+
if (!storage) return true;
|
|
20582
|
+
try {
|
|
20583
|
+
storage.removeItem(PAJA_SIGNER_CONSENT_STORAGE_KEY);
|
|
20584
|
+
return true;
|
|
20585
|
+
} catch {
|
|
20586
|
+
try {
|
|
20587
|
+
const empty = {
|
|
20588
|
+
version: PAJA_SIGNER_CONSENT_VERSION,
|
|
20589
|
+
grants: []
|
|
20590
|
+
};
|
|
20591
|
+
storage.setItem(PAJA_SIGNER_CONSENT_STORAGE_KEY, JSON.stringify(empty));
|
|
20592
|
+
return true;
|
|
20593
|
+
} catch {
|
|
20594
|
+
grants = previous;
|
|
20595
|
+
return false;
|
|
20596
|
+
}
|
|
20597
|
+
}
|
|
20598
|
+
},
|
|
20599
|
+
count() {
|
|
20600
|
+
return grants.reduce((total, grant2) => total + (grant2.trusted ? 1 : grant2.kinds.length), 0);
|
|
20601
|
+
}
|
|
20602
|
+
};
|
|
20603
|
+
}
|
|
20604
|
+
function isPajaSignerConsentContext(context) {
|
|
20605
|
+
return normalizeContext(context) !== null;
|
|
20606
|
+
}
|
|
20607
|
+
function readPajaSignerConsentStorage() {
|
|
20608
|
+
if (typeof window === "undefined") return null;
|
|
20609
|
+
try {
|
|
20610
|
+
return window.localStorage;
|
|
20611
|
+
} catch {
|
|
20612
|
+
return null;
|
|
20613
|
+
}
|
|
20614
|
+
}
|
|
20615
|
+
function readConsentDocument(storage) {
|
|
20616
|
+
const empty = () => ({ version: PAJA_SIGNER_CONSENT_VERSION, grants: [] });
|
|
20617
|
+
if (!storage) return empty();
|
|
20618
|
+
try {
|
|
20619
|
+
const raw = storage.getItem(PAJA_SIGNER_CONSENT_STORAGE_KEY);
|
|
20620
|
+
if (!raw) return empty();
|
|
20621
|
+
const parsed = JSON.parse(raw);
|
|
20622
|
+
if (parsed.version !== PAJA_SIGNER_CONSENT_VERSION || !Array.isArray(parsed.grants)) return empty();
|
|
20623
|
+
const grants = [];
|
|
20624
|
+
for (const candidate of parsed.grants.slice(0, MAX_CONSENT_SUBJECTS)) {
|
|
20625
|
+
const record = normalizeRecord(candidate);
|
|
20626
|
+
if (!record || grants.some((existing) => sameSubject(existing, record))) continue;
|
|
20627
|
+
grants.push(record);
|
|
20628
|
+
}
|
|
20629
|
+
return { version: PAJA_SIGNER_CONSENT_VERSION, grants };
|
|
20630
|
+
} catch {
|
|
20631
|
+
return empty();
|
|
20632
|
+
}
|
|
20633
|
+
}
|
|
20634
|
+
function normalizeRecord(value) {
|
|
20635
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
20636
|
+
const candidate = value;
|
|
20637
|
+
const subject = normalizeSubject(
|
|
20638
|
+
candidate.signerPubkey,
|
|
20639
|
+
candidate.dTag,
|
|
20640
|
+
candidate.aggregateHash,
|
|
20641
|
+
candidate.runtimeScope
|
|
20642
|
+
);
|
|
20643
|
+
if (!subject || typeof candidate.trusted !== "boolean" || !Array.isArray(candidate.kinds)) return null;
|
|
20644
|
+
const kinds = [...new Set(candidate.kinds.map((kind) => normalizeKind(kind)).filter((kind) => kind !== null))].slice(0, MAX_KINDS_PER_SUBJECT).sort((left, right) => left - right);
|
|
20645
|
+
return {
|
|
20646
|
+
...subject,
|
|
20647
|
+
trusted: candidate.trusted,
|
|
20648
|
+
kinds: candidate.trusted ? [] : kinds
|
|
20649
|
+
};
|
|
20650
|
+
}
|
|
20651
|
+
function normalizeContext(context) {
|
|
20652
|
+
return normalizeSubject(
|
|
20653
|
+
context.signerPubkey,
|
|
20654
|
+
context.napplet.dTag,
|
|
20655
|
+
context.napplet.aggregateHash,
|
|
20656
|
+
context.runtimeScope
|
|
20657
|
+
);
|
|
20658
|
+
}
|
|
20659
|
+
function normalizeSubject(signerPubkey, dTag, aggregateHash, runtimeScope) {
|
|
20660
|
+
if (typeof signerPubkey !== "string" || !/^[0-9a-f]{64}$/i.test(signerPubkey)) return null;
|
|
20661
|
+
if (!validIdentityField(dTag) || !validIdentityField(aggregateHash) || !validIdentityField(runtimeScope)) return null;
|
|
20662
|
+
return {
|
|
20663
|
+
signerPubkey: signerPubkey.toLowerCase(),
|
|
20664
|
+
dTag,
|
|
20665
|
+
aggregateHash,
|
|
20666
|
+
runtimeScope
|
|
20667
|
+
};
|
|
20668
|
+
}
|
|
20669
|
+
function validIdentityField(value) {
|
|
20670
|
+
return typeof value === "string" && value.length > 0 && value.length <= MAX_IDENTITY_FIELD_LENGTH;
|
|
20671
|
+
}
|
|
20672
|
+
function normalizeKind(value) {
|
|
20673
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 65535 ? value : null;
|
|
20674
|
+
}
|
|
20675
|
+
function sameSubject(left, right) {
|
|
20676
|
+
return left.signerPubkey === right.signerPubkey && left.dTag === right.dTag && left.aggregateHash === right.aggregateHash && left.runtimeScope === right.runtimeScope;
|
|
20677
|
+
}
|
|
20678
|
+
|
|
20440
20679
|
// src/browser-host-signer.ts
|
|
20441
20680
|
function confirmationDetails(...lines) {
|
|
20442
20681
|
return lines.filter((line) => line !== void 0).join("\n");
|
|
@@ -20444,27 +20683,86 @@ function confirmationDetails(...lines) {
|
|
|
20444
20683
|
function confirmationCopy(title, summary, approveLabel, ...details) {
|
|
20445
20684
|
return { title, summary, details: confirmationDetails(...details), approveLabel };
|
|
20446
20685
|
}
|
|
20447
|
-
function createPajaConfirmationController(getState) {
|
|
20686
|
+
function createPajaConfirmationController(getState, options = {}) {
|
|
20448
20687
|
const dialog = document.getElementById("paja-confirmation-dialog");
|
|
20449
20688
|
const title = document.getElementById("paja-confirmation-title");
|
|
20450
20689
|
const summary = document.getElementById("paja-confirmation-summary");
|
|
20451
20690
|
const details = document.getElementById("paja-confirmation-details");
|
|
20452
20691
|
const approve = document.getElementById("paja-confirmation-approve");
|
|
20453
20692
|
const deny = document.getElementById("paja-confirmation-deny");
|
|
20693
|
+
const signerConsent = document.getElementById("paja-signer-consent");
|
|
20694
|
+
const signerConsentOnce = document.getElementById("paja-signer-consent-once");
|
|
20695
|
+
const signerConsentKind = document.getElementById("paja-signer-consent-kind");
|
|
20696
|
+
const signerConsentNapplet = document.getElementById("paja-signer-consent-napplet");
|
|
20697
|
+
const signerConsentKindValue = document.getElementById("paja-signer-consent-kind-value");
|
|
20698
|
+
const signerConsentNappletValue = document.getElementById("paja-signer-consent-napplet-value");
|
|
20454
20699
|
const ready = dialog instanceof HTMLDialogElement && title instanceof HTMLElement && summary instanceof HTMLElement && details instanceof HTMLElement && approve instanceof HTMLButtonElement && deny instanceof HTMLButtonElement;
|
|
20700
|
+
const signerConsentReady = signerConsent instanceof HTMLFieldSetElement && signerConsentOnce instanceof HTMLInputElement && signerConsentKind instanceof HTMLInputElement && signerConsentNapplet instanceof HTMLInputElement && signerConsentKindValue instanceof HTMLElement && signerConsentNappletValue instanceof HTMLElement;
|
|
20701
|
+
const consentStore = createPajaSignerConsentStore(
|
|
20702
|
+
options.storage === void 0 ? void 0 : options.storage
|
|
20703
|
+
);
|
|
20455
20704
|
const queue = [];
|
|
20456
20705
|
let active = null;
|
|
20457
20706
|
let previousFocus = null;
|
|
20458
20707
|
let disposed = false;
|
|
20708
|
+
const reportSignerConsentChange = () => {
|
|
20709
|
+
options.onSignerConsentChange?.(consentStore.count());
|
|
20710
|
+
};
|
|
20711
|
+
const configureSignerConsent = (request) => {
|
|
20712
|
+
if (!signerConsentReady) return;
|
|
20713
|
+
signerConsent.hidden = true;
|
|
20714
|
+
signerConsentOnce.checked = true;
|
|
20715
|
+
signerConsentKind.checked = false;
|
|
20716
|
+
signerConsentNapplet.checked = false;
|
|
20717
|
+
const candidate = signerConsentCandidate(request);
|
|
20718
|
+
if (!candidate) return;
|
|
20719
|
+
signerConsent.hidden = false;
|
|
20720
|
+
signerConsentKindValue.textContent = String(candidate.kind);
|
|
20721
|
+
signerConsentNappletValue.textContent = candidate.context.napplet.dTag;
|
|
20722
|
+
};
|
|
20723
|
+
const rememberSignerChoice = (request) => {
|
|
20724
|
+
if (!signerConsentReady) return "once";
|
|
20725
|
+
const candidate = signerConsentCandidate(request);
|
|
20726
|
+
if (!candidate) return "once";
|
|
20727
|
+
if (signerConsentNapplet.checked && consentStore.trustNapplet(candidate.context)) {
|
|
20728
|
+
reportSignerConsentChange();
|
|
20729
|
+
return "trust-napplet";
|
|
20730
|
+
}
|
|
20731
|
+
if (signerConsentKind.checked && consentStore.rememberKind(candidate.context, candidate.kind)) {
|
|
20732
|
+
reportSignerConsentChange();
|
|
20733
|
+
return "remember-kind";
|
|
20734
|
+
}
|
|
20735
|
+
return "once";
|
|
20736
|
+
};
|
|
20737
|
+
const rememberedSignerChoice = (request) => {
|
|
20738
|
+
const candidate = signerConsentCandidate(request);
|
|
20739
|
+
return candidate ? consentStore.match(candidate.context, candidate.kind) : null;
|
|
20740
|
+
};
|
|
20459
20741
|
const pump = () => {
|
|
20460
20742
|
if (!ready || disposed || active) return;
|
|
20461
|
-
active
|
|
20743
|
+
while (!active && queue.length > 0) {
|
|
20744
|
+
const candidate = queue.shift() ?? null;
|
|
20745
|
+
if (!candidate) break;
|
|
20746
|
+
const remembered = rememberedSignerChoice(candidate.request);
|
|
20747
|
+
if (!remembered) {
|
|
20748
|
+
active = candidate;
|
|
20749
|
+
break;
|
|
20750
|
+
}
|
|
20751
|
+
recordPajaConfirmation(
|
|
20752
|
+
getState(),
|
|
20753
|
+
candidate.request,
|
|
20754
|
+
true,
|
|
20755
|
+
remembered === "kind" ? "remembered-kind" : "remembered-napplet"
|
|
20756
|
+
);
|
|
20757
|
+
candidate.approve();
|
|
20758
|
+
}
|
|
20462
20759
|
if (!active) return;
|
|
20463
20760
|
const copy = describeConfirmation(active.request);
|
|
20464
20761
|
title.textContent = copy.title;
|
|
20465
20762
|
summary.textContent = copy.summary;
|
|
20466
20763
|
details.textContent = copy.details;
|
|
20467
20764
|
approve.textContent = copy.approveLabel;
|
|
20765
|
+
configureSignerConsent(active.request);
|
|
20468
20766
|
previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
20469
20767
|
dialog.showModal();
|
|
20470
20768
|
deny.focus();
|
|
@@ -20474,7 +20772,8 @@ function createPajaConfirmationController(getState) {
|
|
|
20474
20772
|
if (!current) return;
|
|
20475
20773
|
active = null;
|
|
20476
20774
|
if (ready && dialog.open) dialog.close();
|
|
20477
|
-
|
|
20775
|
+
const approvalMode = allowed2 ? rememberSignerChoice(current.request) : void 0;
|
|
20776
|
+
recordPajaConfirmation(getState(), current.request, allowed2, approvalMode);
|
|
20478
20777
|
if (allowed2) current.approve();
|
|
20479
20778
|
else current.deny();
|
|
20480
20779
|
previousFocus?.focus();
|
|
@@ -20528,6 +20827,12 @@ function createPajaConfirmationController(getState) {
|
|
|
20528
20827
|
});
|
|
20529
20828
|
}
|
|
20530
20829
|
},
|
|
20830
|
+
clearSignerConsent() {
|
|
20831
|
+
const cleared = consentStore.clear();
|
|
20832
|
+
reportSignerConsentChange();
|
|
20833
|
+
return cleared;
|
|
20834
|
+
},
|
|
20835
|
+
getSignerConsentCount: () => consentStore.count(),
|
|
20531
20836
|
dispose() {
|
|
20532
20837
|
if (disposed) return;
|
|
20533
20838
|
disposed = true;
|
|
@@ -20544,6 +20849,13 @@ function createPajaConfirmationController(getState) {
|
|
|
20544
20849
|
}
|
|
20545
20850
|
};
|
|
20546
20851
|
}
|
|
20852
|
+
function signerConsentCandidate(request) {
|
|
20853
|
+
if (request.action !== "sign" || !request.signerContext) return null;
|
|
20854
|
+
if (!isPajaSignerConsentContext(request.signerContext)) return null;
|
|
20855
|
+
const kind = request.event.kind;
|
|
20856
|
+
if (typeof kind !== "number" || !Number.isSafeInteger(kind) || kind < 0 || kind > 65535) return null;
|
|
20857
|
+
return { context: request.signerContext, kind };
|
|
20858
|
+
}
|
|
20547
20859
|
function describeConfirmation(request) {
|
|
20548
20860
|
if (request.action === "upload") {
|
|
20549
20861
|
return confirmationCopy(
|
|
@@ -20625,15 +20937,22 @@ function describeConfirmation(request) {
|
|
|
20625
20937
|
const event = request.event;
|
|
20626
20938
|
const kind = typeof event.kind === "number" ? event.kind : "unknown";
|
|
20627
20939
|
const content = typeof event.content === "string" && event.content.length > 0 ? event.content.slice(0, 240) : "(empty content)";
|
|
20940
|
+
const signerContext = request.signerContext;
|
|
20628
20941
|
return {
|
|
20629
20942
|
title: request.action === "sign" ? "Sign this Nostr event?" : "Publish this Nostr event?",
|
|
20630
|
-
summary: request.action === "sign" ?
|
|
20631
|
-
details:
|
|
20632
|
-
|
|
20943
|
+
summary: request.action === "sign" ? signerContext ? `${signerContext.napplet.dTag} requests a signature from the active signer.` : "A Paja host operation requests a signature from the active signer." : "Paja will send this event to the configured relay set.",
|
|
20944
|
+
details: confirmationDetails(
|
|
20945
|
+
signerContext ? `Napplet: ${signerContext.napplet.dTag} (${signerContext.windowId})` : void 0,
|
|
20946
|
+
signerContext ? `Identity hash: ${signerContext.napplet.aggregateHash}` : void 0,
|
|
20947
|
+
signerContext?.runtimeScope.startsWith("target-url:") ? `Target URL: ${signerContext.runtimeScope.slice("target-url:".length)}` : void 0,
|
|
20948
|
+
signerContext ? `Signer: ${signerContext.signerPubkey}` : void 0,
|
|
20949
|
+
`Kind: ${kind}`,
|
|
20950
|
+
`Content: ${content}`
|
|
20951
|
+
),
|
|
20633
20952
|
approveLabel: request.action === "sign" ? "Sign" : "Publish"
|
|
20634
20953
|
};
|
|
20635
20954
|
}
|
|
20636
|
-
function recordPajaConfirmation(state, request, allowed2) {
|
|
20955
|
+
function recordPajaConfirmation(state, request, allowed2, signerApprovalMode) {
|
|
20637
20956
|
if (request.action === "upload") {
|
|
20638
20957
|
appendPajaMessageLog(state, "paja", {
|
|
20639
20958
|
type: `paja.upload.${allowed2 ? "confirmed" : "denied"}`,
|
|
@@ -20702,9 +21021,16 @@ function recordPajaConfirmation(state, request, allowed2) {
|
|
|
20702
21021
|
}
|
|
20703
21022
|
if (!("event" in request)) return;
|
|
20704
21023
|
const event = request.event;
|
|
21024
|
+
const signerContext = request.signerContext;
|
|
20705
21025
|
appendPajaMessageLog(state, "paja", {
|
|
20706
21026
|
type: `paja.${request.action}.${allowed2 ? "confirmed" : "denied"}`,
|
|
20707
|
-
kind: typeof event.kind === "number" ? event.kind : "unknown"
|
|
21027
|
+
kind: typeof event.kind === "number" ? event.kind : "unknown",
|
|
21028
|
+
windowId: signerContext?.windowId,
|
|
21029
|
+
dTag: signerContext?.napplet.dTag,
|
|
21030
|
+
aggregateHash: signerContext?.napplet.aggregateHash,
|
|
21031
|
+
runtimeScope: signerContext?.runtimeScope,
|
|
21032
|
+
signerPubkey: signerContext?.signerPubkey,
|
|
21033
|
+
approval: signerApprovalMode
|
|
20708
21034
|
});
|
|
20709
21035
|
}
|
|
20710
21036
|
function createHostSignerController(getState, refreshState, confirmRequest) {
|
|
@@ -22316,6 +22642,7 @@ function snapshotPajaBrowserState(state, runtime) {
|
|
|
22316
22642
|
services: state.services,
|
|
22317
22643
|
simulation: runtime.currentSimulation,
|
|
22318
22644
|
signer: state.signer,
|
|
22645
|
+
signerConsentCount: state.signerConsentCount,
|
|
22319
22646
|
resolvedTarget: state.resolvedTarget,
|
|
22320
22647
|
pointerStatus: state.pointerStatus,
|
|
22321
22648
|
activeTabId: state.activeTabId,
|
|
@@ -22331,13 +22658,22 @@ function snapshotPajaBrowserState(state, runtime) {
|
|
|
22331
22658
|
};
|
|
22332
22659
|
}
|
|
22333
22660
|
function createPajaBrowserState(context) {
|
|
22334
|
-
const {
|
|
22661
|
+
const {
|
|
22662
|
+
config,
|
|
22663
|
+
bridge,
|
|
22664
|
+
adapter,
|
|
22665
|
+
signerController,
|
|
22666
|
+
confirmationController,
|
|
22667
|
+
capabilities,
|
|
22668
|
+
runtime
|
|
22669
|
+
} = context;
|
|
22335
22670
|
return {
|
|
22336
22671
|
config,
|
|
22337
22672
|
capabilities,
|
|
22338
22673
|
services: Object.keys(adapter.services ?? {}).sort(),
|
|
22339
22674
|
simulation: runtime.currentSimulation,
|
|
22340
22675
|
signer: signerController.getState(),
|
|
22676
|
+
signerConsentCount: confirmationController.getSignerConsentCount(),
|
|
22341
22677
|
resolvedTarget: null,
|
|
22342
22678
|
pointerValue: readInitialPointerValue(config),
|
|
22343
22679
|
pointerStatus: config.target.mode === "runtime-pointer" ? "idle" : "",
|
|
@@ -22390,6 +22726,12 @@ function createPajaBrowserState(context) {
|
|
|
22390
22726
|
connectBunker(uri) {
|
|
22391
22727
|
return signerController.connectBunker(uri);
|
|
22392
22728
|
},
|
|
22729
|
+
clearSignerConsent() {
|
|
22730
|
+
const cleared = confirmationController.clearSignerConsent();
|
|
22731
|
+
appendPajaMessageLog(this, "paja", {
|
|
22732
|
+
type: cleared ? "paja.signer-consent.cleared" : "paja.signer-consent.clear-failed"
|
|
22733
|
+
});
|
|
22734
|
+
},
|
|
22393
22735
|
async loadPointer(value) {
|
|
22394
22736
|
await loadRuntimePointer(this, context, value);
|
|
22395
22737
|
},
|
|
@@ -22425,7 +22767,16 @@ async function installPajaHost() {
|
|
|
22425
22767
|
() => contextRef,
|
|
22426
22768
|
{ persistTabs: persistRuntimeTabs }
|
|
22427
22769
|
));
|
|
22428
|
-
const confirmationController = createPajaConfirmationController(
|
|
22770
|
+
const confirmationController = createPajaConfirmationController(
|
|
22771
|
+
() => stateRef,
|
|
22772
|
+
{
|
|
22773
|
+
onSignerConsentChange(count) {
|
|
22774
|
+
if (!stateRef) return;
|
|
22775
|
+
stateRef.signerConsentCount = count;
|
|
22776
|
+
setSimulationStatus(stateRef);
|
|
22777
|
+
}
|
|
22778
|
+
}
|
|
22779
|
+
);
|
|
22429
22780
|
const signerController = createHostSignerController(
|
|
22430
22781
|
() => stateRef,
|
|
22431
22782
|
setSimulationStatus,
|
|
@@ -22466,6 +22817,7 @@ async function installPajaHost() {
|
|
|
22466
22817
|
bridge,
|
|
22467
22818
|
adapter,
|
|
22468
22819
|
signerController,
|
|
22820
|
+
confirmationController,
|
|
22469
22821
|
capabilities,
|
|
22470
22822
|
runtime,
|
|
22471
22823
|
navigateFrame,
|