@kehto/paja 0.20.0 → 0.20.1
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 +22 -12
- package/dist/browser-host.js +218 -229
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-6WEO5D3Z.js → chunk-FUBJAN4W.js} +15 -16
- package/dist/chunk-FUBJAN4W.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-6WEO5D3Z.js.map +0 -1
package/README.md
CHANGED
|
@@ -482,7 +482,7 @@ Parameters start omitted; selecting Include with an empty field sends an explici
|
|
|
482
482
|
empty string. Names stay in advertised order and extra named fields are allowed.
|
|
483
483
|
No parameter type or requiredness is inferred. An empty advertised list still
|
|
484
484
|
permits a payload. Opening or editing the builder never invokes an intent.
|
|
485
|
-
Use **Copy link** or
|
|
485
|
+
Use **Copy link** to share it, or **Test intent** to open the shared content.
|
|
486
486
|
|
|
487
487
|
The outer URL carries a complete percent-encoded convention URI in `intent`.
|
|
488
488
|
For example, `?intent=napplet%3Aprofile%2Fopen%3Fpubkey%3Dabc%252B123`
|
|
@@ -493,17 +493,27 @@ cannot accompany an inner query. URLs are limited to 16 KiB UTF-8. Duplicate
|
|
|
493
493
|
keys, malformed encoding, and conflicting target instructions are rejected.
|
|
494
494
|
Ordinary pointer-only app links retain their existing behavior.
|
|
495
495
|
|
|
496
|
-
|
|
497
|
-
exact outer `pointer`.
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
496
|
+
New links recommend the app using a named-35129 `#naddr` when available;
|
|
497
|
+
snapshot or unnamed apps use an exact outer `pointer`. The builder also offers
|
|
498
|
+
recipient-default routing. Explicit pointers must verify and advertise the exact
|
|
499
|
+
contract; failure never silently falls back. Recommendation relay hints remain
|
|
500
|
+
untrusted discovery inputs. Invalid, incompatible, or disallowed recommendations
|
|
501
|
+
do not override an applicable saved default.
|
|
502
|
+
|
|
503
|
+
Opening a shared link opens its verified app and delivers the content directly,
|
|
504
|
+
following Paja's ordinary pointer navigation and capability policy. No URI or
|
|
505
|
+
payload review is shown. If an applicable saved default conflicts with the
|
|
506
|
+
link's app, Paja names both apps and asks which to use. A one-time choice leaves
|
|
507
|
+
the default unchanged; **Always use this app** explicitly saves the chosen app.
|
|
508
|
+
Without a usable hint or default, ordinary installed-handler selection applies.
|
|
509
|
+
Installed app pointers are saved separately from open tabs and reverified when
|
|
510
|
+
loaded; cached pointers never authorize execution or supply trusted metadata.
|
|
511
|
+
|
|
512
|
+
Progress and failures use a small notice. Successful delivery dismisses it.
|
|
513
|
+
Closing the notice or cancelling an app choice before dispatch sends nothing.
|
|
514
|
+
After acceptance, the incoming intent is removed from the current URL so reload
|
|
515
|
+
restores the app without delivering the shared content again. Accepted work is
|
|
516
|
+
retained until the verified target is ready.
|
|
507
517
|
|
|
508
518
|
Paja invokes through a separate signed launcher napplet with a verified artifact
|
|
509
519
|
and authenticated iframe endpoint. Its source supplies the opaque sender; the
|
package/dist/browser-host.js
CHANGED
|
@@ -14497,12 +14497,12 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
|
|
|
14497
14497
|
}
|
|
14498
14498
|
return Object.freeze(strings);
|
|
14499
14499
|
}
|
|
14500
|
-
function
|
|
14500
|
+
function supports2(domain) {
|
|
14501
14501
|
return typeof domain === "string" && environment?.capabilities.domains.includes(domain) === true;
|
|
14502
14502
|
}
|
|
14503
14503
|
const shell2 = {
|
|
14504
14504
|
ready: () => ready,
|
|
14505
|
-
supports,
|
|
14505
|
+
supports: supports2,
|
|
14506
14506
|
get services() {
|
|
14507
14507
|
return environment?.services ?? emptyServices;
|
|
14508
14508
|
},
|
|
@@ -23732,6 +23732,21 @@ function createPajaThemeBroadcastLink() {
|
|
|
23732
23732
|
|
|
23733
23733
|
// src/browser-host-storage.ts
|
|
23734
23734
|
var PAJA_INTENT_DEFAULTS_STORAGE_KEY = "kehto.paja.intent-defaults.v1";
|
|
23735
|
+
var PAJA_INTENT_CATALOG_STORAGE_KEY = "kehto.paja.intent-catalog.v1";
|
|
23736
|
+
function readPajaIntentCatalogPointers() {
|
|
23737
|
+
try {
|
|
23738
|
+
const value = JSON.parse(window.localStorage.getItem(PAJA_INTENT_CATALOG_STORAGE_KEY) ?? "[]");
|
|
23739
|
+
return Array.isArray(value) ? value.filter((pointer) => typeof pointer === "string" && pointer.length > 0) : [];
|
|
23740
|
+
} catch {
|
|
23741
|
+
return [];
|
|
23742
|
+
}
|
|
23743
|
+
}
|
|
23744
|
+
function writePajaIntentCatalogPointers(pointers) {
|
|
23745
|
+
try {
|
|
23746
|
+
window.localStorage.setItem(PAJA_INTENT_CATALOG_STORAGE_KEY, JSON.stringify([...new Set(pointers)]));
|
|
23747
|
+
} catch {
|
|
23748
|
+
}
|
|
23749
|
+
}
|
|
23735
23750
|
function runtimeTabsStorage(config) {
|
|
23736
23751
|
if (config.target.mode !== "runtime-pointer") return null;
|
|
23737
23752
|
try {
|
|
@@ -23957,193 +23972,139 @@ function createPajaIntentLink(descriptor, href) {
|
|
|
23957
23972
|
}
|
|
23958
23973
|
|
|
23959
23974
|
// src/browser-intent-links.ts
|
|
23960
|
-
function
|
|
23975
|
+
function createPajaIntentLinkController(document2 = window.document, options = {}) {
|
|
23961
23976
|
const dialog = document2.getElementById("paja-intent-link-dialog");
|
|
23962
|
-
const
|
|
23963
|
-
const
|
|
23964
|
-
const
|
|
23965
|
-
const
|
|
23977
|
+
const title = document2.getElementById("paja-intent-link-title");
|
|
23978
|
+
const copy = document2.getElementById("paja-intent-link-copy");
|
|
23979
|
+
const apps = document2.getElementById("paja-intent-link-apps");
|
|
23980
|
+
const remember = document2.getElementById("paja-intent-link-save-default");
|
|
23966
23981
|
const cancel = document2.getElementById("paja-intent-link-cancel");
|
|
23967
|
-
const
|
|
23982
|
+
const notice = document2.getElementById("paja-intent-link-notice");
|
|
23983
|
+
const status = document2.getElementById("paja-intent-link-status");
|
|
23968
23984
|
const retry = document2.getElementById("paja-intent-link-retry");
|
|
23969
|
-
const
|
|
23970
|
-
|
|
23971
|
-
const useHandler = document2.getElementById("paja-intent-link-use-handler");
|
|
23972
|
-
const saveDefault = document2.getElementById("paja-intent-link-save-default");
|
|
23973
|
-
const clearDefault = document2.getElementById("paja-intent-link-clear-default");
|
|
23974
|
-
if (!(dialog instanceof HTMLDialogElement) || !(uri instanceof HTMLInputElement) || !(payload instanceof HTMLTextAreaElement) || !(target instanceof HTMLElement) || !(status instanceof HTMLElement) || !(cancel instanceof HTMLButtonElement) || !(launch instanceof HTMLButtonElement) || !(retry instanceof HTMLButtonElement) || !(choose2 instanceof HTMLButtonElement) || !(handler instanceof HTMLSelectElement) || !(useHandler instanceof HTMLButtonElement) || !(saveDefault instanceof HTMLInputElement)) return null;
|
|
23975
|
-
const clearDefaultButton = clearDefault instanceof HTMLButtonElement ? clearDefault : null;
|
|
23976
|
-
let settle6 = null;
|
|
23977
|
-
let trigger = null;
|
|
23978
|
-
let pointer;
|
|
23979
|
-
let forceChooser = false;
|
|
23980
|
-
let run = null;
|
|
23981
|
-
let chooseSettle = null;
|
|
23982
|
-
let activeArchetype;
|
|
23985
|
+
const dismiss = document2.getElementById("paja-intent-link-dismiss");
|
|
23986
|
+
if (!(dialog instanceof HTMLDialogElement) || !(title instanceof HTMLElement) || !(copy instanceof HTMLElement) || !(apps instanceof HTMLElement) || !(remember instanceof HTMLInputElement) || !(cancel instanceof HTMLButtonElement) || !(notice instanceof HTMLElement) || !(status instanceof HTMLElement) || !(retry instanceof HTMLButtonElement) || !(dismiss instanceof HTMLButtonElement)) return null;
|
|
23983
23987
|
let generation = 0;
|
|
23984
|
-
let
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
|
|
23988
|
-
|
|
23989
|
-
|
|
23990
|
-
|
|
23991
|
-
|
|
23992
|
-
const setTargetCopy = () => {
|
|
23993
|
-
target.textContent = pointer ? "This link names one verified target. If it cannot satisfy this exact convention, choose another deliberately." : forceChooser ? "Choose a compatible handler deliberately. This launch will not use a saved default or recommendation." : "Paja will use your saved default, recommendation, or ask you to choose a compatible handler.";
|
|
23994
|
-
};
|
|
23995
|
-
const finish = () => {
|
|
23996
|
-
generation += 1;
|
|
23997
|
-
busy = false;
|
|
23998
|
-
const done = settle6;
|
|
23999
|
-
settle6 = null;
|
|
24000
|
-
run = null;
|
|
24001
|
-
chooseSettle?.(void 0);
|
|
24002
|
-
chooseSettle = null;
|
|
23988
|
+
let run;
|
|
23989
|
+
let archetype = "";
|
|
23990
|
+
let settleChoice;
|
|
23991
|
+
let trigger = null;
|
|
23992
|
+
const closeChoice = (id) => {
|
|
23993
|
+
const settle6 = settleChoice;
|
|
23994
|
+
settleChoice = void 0;
|
|
23995
|
+
if (id && remember.checked) options.setDefault?.(archetype, id);
|
|
24003
23996
|
if (dialog.open) dialog.close();
|
|
24004
23997
|
trigger?.focus();
|
|
24005
23998
|
trigger = null;
|
|
24006
|
-
|
|
24007
|
-
};
|
|
24008
|
-
const
|
|
24009
|
-
|
|
24010
|
-
|
|
24011
|
-
|
|
24012
|
-
|
|
24013
|
-
|
|
24014
|
-
|
|
24015
|
-
|
|
24016
|
-
|
|
24017
|
-
|
|
24018
|
-
|
|
24019
|
-
|
|
24020
|
-
|
|
24021
|
-
const
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
|
|
24027
|
-
|
|
24028
|
-
|
|
24029
|
-
|
|
24030
|
-
|
|
24031
|
-
|
|
24032
|
-
|
|
24033
|
-
|
|
24034
|
-
|
|
24035
|
-
|
|
24036
|
-
|
|
24037
|
-
|
|
24038
|
-
|
|
24039
|
-
|
|
23999
|
+
settle6?.(id);
|
|
24000
|
+
};
|
|
24001
|
+
const cancelChoice = () => {
|
|
24002
|
+
closeChoice();
|
|
24003
|
+
generation += 1;
|
|
24004
|
+
notice.hidden = true;
|
|
24005
|
+
run = void 0;
|
|
24006
|
+
};
|
|
24007
|
+
const dismissNotice = () => {
|
|
24008
|
+
generation += 1;
|
|
24009
|
+
notice.hidden = true;
|
|
24010
|
+
run = void 0;
|
|
24011
|
+
closeChoice();
|
|
24012
|
+
};
|
|
24013
|
+
const retryLink = () => run?.();
|
|
24014
|
+
const showError = (_error) => {
|
|
24015
|
+
status.textContent = "Couldn\u2019t open this shared content. Try again, or ask the sender for a new link.";
|
|
24016
|
+
notice.hidden = false;
|
|
24017
|
+
retry.hidden = !run;
|
|
24018
|
+
};
|
|
24019
|
+
const choose2 = (heading, description, candidates) => {
|
|
24020
|
+
closeChoice();
|
|
24021
|
+
trigger = document2.activeElement instanceof HTMLElement ? document2.activeElement : null;
|
|
24022
|
+
title.textContent = heading;
|
|
24023
|
+
copy.textContent = description;
|
|
24024
|
+
remember.checked = false;
|
|
24025
|
+
apps.replaceChildren();
|
|
24026
|
+
for (const candidate of candidates) {
|
|
24027
|
+
const button2 = document2.createElement("button");
|
|
24028
|
+
button2.type = "button";
|
|
24029
|
+
button2.textContent = `Open with ${candidate.title}`;
|
|
24030
|
+
button2.addEventListener("click", () => closeChoice(candidate.id));
|
|
24031
|
+
apps.append(button2);
|
|
24032
|
+
}
|
|
24033
|
+
notice.hidden = true;
|
|
24034
|
+
const result2 = new Promise((resolve) => {
|
|
24035
|
+
settleChoice = resolve;
|
|
24036
|
+
});
|
|
24037
|
+
dialog.showModal();
|
|
24038
|
+
return result2;
|
|
24039
|
+
};
|
|
24040
|
+
cancel.addEventListener("click", cancelChoice);
|
|
24041
|
+
dialog.addEventListener("cancel", cancelChoice);
|
|
24042
|
+
dismiss.addEventListener("click", dismissNotice);
|
|
24043
|
+
retry.addEventListener("click", retryLink);
|
|
24040
24044
|
return {
|
|
24041
|
-
|
|
24042
|
-
|
|
24043
|
-
|
|
24044
|
-
pointer = link.pointer;
|
|
24045
|
-
forceChooser = false;
|
|
24046
|
-
payload.value = Object.hasOwn(link, "payload") ? JSON.stringify(link.payload) : "";
|
|
24047
|
-
trigger = document2.activeElement instanceof HTMLElement ? document2.activeElement : null;
|
|
24048
|
-
activeArchetype = link.request.archetype;
|
|
24049
|
-
setTargetCopy();
|
|
24050
|
-
status.textContent = "Review the URI and payload, then explicitly launch.";
|
|
24045
|
+
open(link, launch) {
|
|
24046
|
+
dismissNotice();
|
|
24047
|
+
archetype = link.request.archetype;
|
|
24051
24048
|
run = () => {
|
|
24052
|
-
|
|
24053
|
-
const runGeneration = ++generation;
|
|
24054
|
-
let review;
|
|
24055
|
-
try {
|
|
24056
|
-
const hasPayload = payload.value.trim().length > 0;
|
|
24057
|
-
const parsedPayload = hasPayload ? JSON.parse(payload.value) : void 0;
|
|
24058
|
-
const request = normalizeIntentUri(uri.value, {
|
|
24059
|
-
...hasPayload ? { payload: parsedPayload } : {},
|
|
24060
|
-
...forceChooser ? { handler: "choose" } : {}
|
|
24061
|
-
});
|
|
24062
|
-
if (pointer !== void 0 && request.handlerHint !== void 0) {
|
|
24063
|
-
throw new TypeError("Paja intent link cannot combine a target pointer with a URI recommendation");
|
|
24064
|
-
}
|
|
24065
|
-
activeArchetype = request.archetype;
|
|
24066
|
-
review = { uri: uri.value, ...pointer === void 0 ? {} : { pointer }, ...hasPayload ? { payload: parsedPayload } : {}, request };
|
|
24067
|
-
} catch (error) {
|
|
24068
|
-
if (isCurrent(runGeneration)) status.textContent = error instanceof Error ? error.message : "Intent link is invalid.";
|
|
24069
|
-
return;
|
|
24070
|
-
}
|
|
24071
|
-
setBusy(true);
|
|
24072
|
-
status.textContent = "Resolving verified handler policy\u2026";
|
|
24049
|
+
const current = ++generation;
|
|
24073
24050
|
let terminal = false;
|
|
24051
|
+
let consumed = false;
|
|
24052
|
+
const isActive = () => current === generation;
|
|
24053
|
+
const consume = () => {
|
|
24054
|
+
if (!consumed) {
|
|
24055
|
+
consumed = true;
|
|
24056
|
+
options.onAccepted?.();
|
|
24057
|
+
}
|
|
24058
|
+
};
|
|
24059
|
+
status.textContent = "Opening shared content\u2026";
|
|
24060
|
+
notice.hidden = false;
|
|
24061
|
+
retry.hidden = true;
|
|
24074
24062
|
const progress = {
|
|
24075
|
-
isActive
|
|
24076
|
-
selectedTarget(
|
|
24077
|
-
if (
|
|
24078
|
-
target.textContent = `Verified target: ${title}`;
|
|
24063
|
+
isActive,
|
|
24064
|
+
selectedTarget(name) {
|
|
24065
|
+
if (isActive() && !terminal) status.textContent = `Opening ${name}\u2026`;
|
|
24079
24066
|
},
|
|
24067
|
+
chooseConflict: (saved, shared) => choose2(
|
|
24068
|
+
"Which app would you like to use?",
|
|
24069
|
+
`This link uses ${shared.title}. Your saved preference is ${saved.title}.`,
|
|
24070
|
+
[saved, shared]
|
|
24071
|
+
),
|
|
24080
24072
|
accepted() {
|
|
24081
|
-
if (
|
|
24082
|
-
status.textContent = "Accepted: delivery is queued and the target may still be loading.";
|
|
24073
|
+
if (isActive()) consume();
|
|
24083
24074
|
},
|
|
24084
24075
|
delivered() {
|
|
24085
|
-
if (!
|
|
24076
|
+
if (!isActive() || terminal) return;
|
|
24086
24077
|
terminal = true;
|
|
24087
|
-
|
|
24088
|
-
|
|
24078
|
+
consume();
|
|
24079
|
+
notice.hidden = true;
|
|
24080
|
+
run = void 0;
|
|
24089
24081
|
},
|
|
24090
24082
|
failed(error) {
|
|
24091
|
-
if (
|
|
24092
|
-
|
|
24093
|
-
|
|
24094
|
-
|
|
24083
|
+
if (isActive() && !terminal) {
|
|
24084
|
+
terminal = true;
|
|
24085
|
+
showError(error);
|
|
24086
|
+
}
|
|
24095
24087
|
}
|
|
24096
24088
|
};
|
|
24097
|
-
void Promise.resolve().then(() =>
|
|
24089
|
+
void Promise.resolve().then(() => launch(link, progress)).catch(progress.failed);
|
|
24098
24090
|
};
|
|
24099
|
-
|
|
24100
|
-
dialog.showModal();
|
|
24101
|
-
uri.focus();
|
|
24102
|
-
return new Promise((resolve) => {
|
|
24103
|
-
settle6 = resolve;
|
|
24104
|
-
});
|
|
24091
|
+
run();
|
|
24105
24092
|
},
|
|
24106
|
-
chooseHandler(
|
|
24107
|
-
|
|
24108
|
-
|
|
24109
|
-
|
|
24110
|
-
|
|
24111
|
-
|
|
24112
|
-
|
|
24113
|
-
uri.value = "";
|
|
24114
|
-
payload.value = "";
|
|
24115
|
-
setTargetCopy();
|
|
24116
|
-
dialog.showModal();
|
|
24117
|
-
}
|
|
24118
|
-
handler.replaceChildren();
|
|
24119
|
-
for (const candidate of candidates) {
|
|
24120
|
-
const option = document2.createElement("option");
|
|
24121
|
-
option.value = candidate.id;
|
|
24122
|
-
option.textContent = candidate.title ?? candidate.id;
|
|
24123
|
-
handler.append(option);
|
|
24124
|
-
}
|
|
24125
|
-
handler.disabled = candidates.length === 0;
|
|
24126
|
-
useHandler.disabled = candidates.length === 0;
|
|
24127
|
-
saveDefault.checked = false;
|
|
24128
|
-
status.textContent = `Choose a compatible handler for ${archetype}.`;
|
|
24129
|
-
return new Promise((resolve) => {
|
|
24130
|
-
chooseSettle = resolve;
|
|
24131
|
-
});
|
|
24132
|
-
},
|
|
24133
|
-
consumeSaveDefault() {
|
|
24134
|
-
const selected = saveDefault.checked;
|
|
24135
|
-
saveDefault.checked = false;
|
|
24136
|
-
return selected;
|
|
24093
|
+
chooseHandler(role, candidates) {
|
|
24094
|
+
archetype = role;
|
|
24095
|
+
return choose2(
|
|
24096
|
+
"Choose an app",
|
|
24097
|
+
"Open this shared content with:",
|
|
24098
|
+
candidates.map((candidate) => ({ id: candidate.id, title: candidate.title ?? "Untitled app" }))
|
|
24099
|
+
);
|
|
24137
24100
|
},
|
|
24101
|
+
showError,
|
|
24138
24102
|
dispose() {
|
|
24139
|
-
|
|
24140
|
-
|
|
24141
|
-
|
|
24142
|
-
|
|
24143
|
-
|
|
24144
|
-
clearDefaultButton?.removeEventListener("click", clearSavedDefault);
|
|
24145
|
-
dialog.removeEventListener("cancel", cancelReview);
|
|
24146
|
-
finish();
|
|
24103
|
+
dismissNotice();
|
|
24104
|
+
cancel.removeEventListener("click", cancelChoice);
|
|
24105
|
+
dialog.removeEventListener("cancel", cancelChoice);
|
|
24106
|
+
dismiss.removeEventListener("click", dismissNotice);
|
|
24107
|
+
retry.removeEventListener("click", retryLink);
|
|
24147
24108
|
}
|
|
24148
24109
|
};
|
|
24149
24110
|
}
|
|
@@ -24456,6 +24417,7 @@ Routing: ${targetDescription}`;
|
|
|
24456
24417
|
appendIntentLinkBuilderOption(routing.control, "default", "Recipient default");
|
|
24457
24418
|
appendIntentLinkBuilderOption(routing.control, "recommend", "Recommend this app", nextTarget.event.kind !== 35129 || !nextTarget.manifest.dTag);
|
|
24458
24419
|
appendIntentLinkBuilderOption(routing.control, "exact", "Exact target pointer");
|
|
24420
|
+
routing.control.value = nextTarget.event.kind === 35129 && nextTarget.manifest.dTag ? "recommend" : "exact";
|
|
24459
24421
|
appendIntentLinkBuilderOption(payloadMode.control, "text", "Text fields");
|
|
24460
24422
|
appendIntentLinkBuilderOption(payloadMode.control, "json", "JSON payload");
|
|
24461
24423
|
json.value = "";
|
|
@@ -24542,6 +24504,61 @@ Routing: ${targetDescription}`;
|
|
|
24542
24504
|
};
|
|
24543
24505
|
}
|
|
24544
24506
|
|
|
24507
|
+
// src/browser-intent-link-target.ts
|
|
24508
|
+
function supports(target, link) {
|
|
24509
|
+
const contracts = "manifest" in target ? target.manifest.archetypes : target.archetypes;
|
|
24510
|
+
return contracts.some((entry) => entry.slug === link.request.archetype && entry.convention === link.request.convention);
|
|
24511
|
+
}
|
|
24512
|
+
function permitted(context, target) {
|
|
24513
|
+
const identity = "manifest" in target ? { dTag: target.dTag, aggregateHash: target.aggregateHash, requires: target.manifest.requires, optional: target.manifest.optional } : target;
|
|
24514
|
+
return resolveShellEnvironment(context.adapter, identity).capabilities.domains.includes("intent");
|
|
24515
|
+
}
|
|
24516
|
+
async function recommended(context, link) {
|
|
24517
|
+
const hint2 = link.request.handlerHint;
|
|
24518
|
+
if (!hint2) return null;
|
|
24519
|
+
const installed = context.runtime.catalog.get(`nip5d:${hint2.address}`);
|
|
24520
|
+
if (installed && supports(installed, link)) return installed;
|
|
24521
|
+
const match = /^35129:([a-f0-9]{64}):(.+)$/.exec(hint2.address);
|
|
24522
|
+
if (!match) return null;
|
|
24523
|
+
const [, pubkey2, identifier] = match;
|
|
24524
|
+
try {
|
|
24525
|
+
const pointer = naddrEncode({ identifier, pubkey: pubkey2, kind: 35129, relays: [...hint2.relays ?? []] });
|
|
24526
|
+
const target = await resolvePajaPointer(pointer, pajaPointerResolverOptions(context));
|
|
24527
|
+
return target.event.kind === 35129 && target.event.pubkey === pubkey2 && target.manifest.dTag === identifier && supports(target, link) ? target : null;
|
|
24528
|
+
} catch {
|
|
24529
|
+
return null;
|
|
24530
|
+
}
|
|
24531
|
+
}
|
|
24532
|
+
async function resolvePajaSharedLinkHandler(context, link, progress) {
|
|
24533
|
+
if (link.request.handler === "choose") return void 0;
|
|
24534
|
+
const shared = link.pointer ? await resolvePajaPointer(link.pointer, pajaPointerResolverOptions(context)) : await recommended(context, link);
|
|
24535
|
+
if (!progress.isActive() || !shared) return void 0;
|
|
24536
|
+
if (!supports(shared, link) || !permitted(context, shared)) {
|
|
24537
|
+
if (link.pointer) throw new Error("The named app cannot open this shared content");
|
|
24538
|
+
return void 0;
|
|
24539
|
+
}
|
|
24540
|
+
const id = "manifest" in shared ? shared.manifest.catalogId : shared.id;
|
|
24541
|
+
const title = ("manifest" in shared ? shared.manifest.title : shared.title) ?? shared.dTag;
|
|
24542
|
+
const savedId = context.runtime.intentDefaults.get(link.request.archetype);
|
|
24543
|
+
const saved = savedId ? context.runtime.catalog.get(savedId) : void 0;
|
|
24544
|
+
let selected = id;
|
|
24545
|
+
let explicit = link.pointer !== void 0;
|
|
24546
|
+
if (!link.pointer && saved && saved.id !== id && supports(saved, link) && permitted(context, saved)) {
|
|
24547
|
+
const choice = await progress.chooseConflict(
|
|
24548
|
+
{ id: saved.id, title: saved.title ?? saved.dTag },
|
|
24549
|
+
{ id, title }
|
|
24550
|
+
);
|
|
24551
|
+
if (!progress.isActive()) return void 0;
|
|
24552
|
+
if (choice !== id && choice !== saved.id) throw new Error("App choice cancelled");
|
|
24553
|
+
selected = choice;
|
|
24554
|
+
explicit = true;
|
|
24555
|
+
}
|
|
24556
|
+
if (!progress.isActive()) return void 0;
|
|
24557
|
+
if (selected === id && "manifest" in shared) context.runtime.catalog.install(shared);
|
|
24558
|
+
progress.selectedTarget(selected === id ? title : saved.title ?? saved.dTag);
|
|
24559
|
+
return explicit ? selected : void 0;
|
|
24560
|
+
}
|
|
24561
|
+
|
|
24545
24562
|
// src/intent-launcher.ts
|
|
24546
24563
|
var LAUNCHER_HTML = `<!doctype html><meta charset="utf-8"><title>Paja intent launcher</title><script>let launched=false;addEventListener('message',(event)=>{if(launched||event.source!==parent)return;const data=event.data;if(!data||data.type!=='paja.intent.launch'||typeof data.uri!=='string'||typeof data.correlation!=='string')return;launched=true;window.napplet?.intent?.invoke(data.uri,data.options).then((result)=>parent.postMessage({type:'paja.intent.launcher.result',correlation:data.correlation,result},'*')).catch(()=>parent.postMessage({type:'paja.intent.launcher.result',correlation:data.correlation,result:{ok:false,error:'invoke rejected'}},'*'));});parent.postMessage({type:'paja.intent.launcher.ready'},'*');<\/script>`;
|
|
24547
24564
|
var LAUNCHER_EVENT = {
|
|
@@ -24585,27 +24602,10 @@ function postPajaIntentLauncherMessage(frame, message2) {
|
|
|
24585
24602
|
}
|
|
24586
24603
|
|
|
24587
24604
|
// src/browser-intent-launcher-host.ts
|
|
24588
|
-
async function
|
|
24605
|
+
async function launchPajaSharedIntent(state, context, intent, progress, setPointerStatus2) {
|
|
24589
24606
|
const { runtime } = context;
|
|
24590
|
-
let explicitHandler;
|
|
24591
24607
|
if (!progress.isActive()) return;
|
|
24592
|
-
|
|
24593
|
-
const target = await resolvePajaPointer(intent.pointer, pajaPointerResolverOptions(context));
|
|
24594
|
-
if (!progress.isActive()) return;
|
|
24595
|
-
const exact = target.manifest.archetypes.some((contract) => contract.slug === intent.request.archetype && contract.convention === intent.request.convention);
|
|
24596
|
-
if (!exact) throw new Error("The explicitly named napplet does not advertise this exact intent convention");
|
|
24597
|
-
runtime.catalog.install(target);
|
|
24598
|
-
progress.selectedTarget(target.manifest.title ?? target.manifest.dTag);
|
|
24599
|
-
explicitHandler = target.manifest.catalogId;
|
|
24600
|
-
}
|
|
24601
|
-
if (intent.request.handlerHint && shouldResolveRecommendation(intent, runtime)) {
|
|
24602
|
-
const target = await resolveRecommendedIntentTarget(context, intent.request.handlerHint, intent.request.archetype, intent.request.convention);
|
|
24603
|
-
if (!progress.isActive()) return;
|
|
24604
|
-
if (target) {
|
|
24605
|
-
runtime.catalog.install(target);
|
|
24606
|
-
progress.selectedTarget(target.manifest.title ?? target.manifest.dTag);
|
|
24607
|
-
}
|
|
24608
|
-
}
|
|
24608
|
+
const explicitHandler = await resolvePajaSharedLinkHandler(context, intent, progress);
|
|
24609
24609
|
if (!progress.isActive()) return;
|
|
24610
24610
|
const launcher = await resolvePajaIntentLauncher();
|
|
24611
24611
|
if (!progress.isActive()) return;
|
|
@@ -24690,32 +24690,6 @@ async function launchPajaReviewedIntent(state, context, intent, progress, setPoi
|
|
|
24690
24690
|
teardown();
|
|
24691
24691
|
}
|
|
24692
24692
|
}
|
|
24693
|
-
function shouldResolveRecommendation(intent, runtime) {
|
|
24694
|
-
if (intent.request.handler !== void 0) return false;
|
|
24695
|
-
const defaultId = runtime.intentDefaults.get(intent.request.archetype);
|
|
24696
|
-
if (!defaultId) return true;
|
|
24697
|
-
const record2 = runtime.catalog.get(defaultId);
|
|
24698
|
-
return !record2 || !record2.archetypes.some((entry) => entry.slug === intent.request.archetype && entry.convention === intent.request.convention);
|
|
24699
|
-
}
|
|
24700
|
-
async function resolveRecommendedIntentTarget(context, hint2, archetype, convention) {
|
|
24701
|
-
const match = /^35129:([a-f0-9]{64}):(.+)$/.exec(hint2.address);
|
|
24702
|
-
if (!match) return null;
|
|
24703
|
-
const [, pubkey2, identifier] = match;
|
|
24704
|
-
const existing = context.runtime.catalog.installed().find((record2) => record2.id === `nip5d:${hint2.address}` && record2.archetypes.some((entry) => entry.slug === archetype && entry.convention === convention));
|
|
24705
|
-
if (existing) return null;
|
|
24706
|
-
const pointer = naddrEncode({ identifier, pubkey: pubkey2, kind: 35129, relays: [...hint2.relays ?? []] });
|
|
24707
|
-
let target;
|
|
24708
|
-
try {
|
|
24709
|
-
target = await resolvePajaPointer(pointer, pajaPointerResolverOptions(context));
|
|
24710
|
-
} catch {
|
|
24711
|
-
return null;
|
|
24712
|
-
}
|
|
24713
|
-
const exactCoordinate = target.event.kind === 35129 && target.event.pubkey === pubkey2 && target.manifest.dTag === identifier;
|
|
24714
|
-
const exactContract = target.manifest.archetypes.some((entry) => entry.slug === archetype && entry.convention === convention);
|
|
24715
|
-
if (!exactCoordinate || !exactContract) return null;
|
|
24716
|
-
if (!window.confirm(`Install and use recommended handler ${target.manifest.title ?? target.manifest.dTag}?`)) return null;
|
|
24717
|
-
return target;
|
|
24718
|
-
}
|
|
24719
24693
|
|
|
24720
24694
|
// src/browser-target-diagnostics.ts
|
|
24721
24695
|
async function reportTargetCorsDiagnostic(state) {
|
|
@@ -25369,8 +25343,8 @@ async function restorePersistedRuntimeTabs(state, context, snapshot) {
|
|
|
25369
25343
|
persistRuntimeTabs2(state);
|
|
25370
25344
|
}
|
|
25371
25345
|
async function hydratePersistedIntentCatalog(state, context, snapshot) {
|
|
25372
|
-
|
|
25373
|
-
await Promise.all(
|
|
25346
|
+
const pointers = /* @__PURE__ */ new Set([...readPajaIntentCatalogPointers(), ...snapshot?.pointers ?? []]);
|
|
25347
|
+
await Promise.all([...pointers].map(async (pointer) => {
|
|
25374
25348
|
try {
|
|
25375
25349
|
const resolved = await resolvePajaPointer(pointer, pajaPointerResolverOptions(context));
|
|
25376
25350
|
context.runtime.catalog.install(resolved);
|
|
@@ -25528,7 +25502,7 @@ async function installPajaHost() {
|
|
|
25528
25502
|
const themeBroadcast = createPajaThemeBroadcastLink();
|
|
25529
25503
|
let stateRef = null;
|
|
25530
25504
|
let contextRef = null;
|
|
25531
|
-
let
|
|
25505
|
+
let intentLinks = null;
|
|
25532
25506
|
const intentController = new BrowserIntentController(createPajaIntentTargetOptions(
|
|
25533
25507
|
() => stateRef,
|
|
25534
25508
|
() => contextRef,
|
|
@@ -25594,12 +25568,7 @@ async function installPajaHost() {
|
|
|
25594
25568
|
return runtime.intentDefaults.get(archetype);
|
|
25595
25569
|
},
|
|
25596
25570
|
async chooseHandler(archetype, candidates) {
|
|
25597
|
-
|
|
25598
|
-
if (selected && intentReview?.consumeSaveDefault()) {
|
|
25599
|
-
runtime.intentDefaults.set(archetype, selected);
|
|
25600
|
-
writePajaIntentDefaults(runtime.intentDefaults);
|
|
25601
|
-
}
|
|
25602
|
-
return selected;
|
|
25571
|
+
return intentLinks?.chooseHandler(archetype, candidates);
|
|
25603
25572
|
},
|
|
25604
25573
|
resolveHandlerHint(hint2, candidates) {
|
|
25605
25574
|
const id = `nip5d:${hint2.address}`;
|
|
@@ -25636,6 +25605,19 @@ async function installPajaHost() {
|
|
|
25636
25605
|
stateRef = state;
|
|
25637
25606
|
const stopIntentCatalogChanges = subscribePajaIntentCatalogChanges(state, context);
|
|
25638
25607
|
window.addEventListener("pagehide", stopIntentCatalogChanges, { once: true });
|
|
25608
|
+
const catalogPointers = /* @__PURE__ */ new Map();
|
|
25609
|
+
const stopCatalogPersistence = runtime.catalog.onChanged((id) => {
|
|
25610
|
+
const pointers = new Set(readPajaIntentCatalogPointers());
|
|
25611
|
+
const previous = catalogPointers.get(id);
|
|
25612
|
+
if (previous) pointers.delete(previous);
|
|
25613
|
+
const record2 = runtime.catalog.get(id);
|
|
25614
|
+
if (record2) {
|
|
25615
|
+
pointers.add(record2.pointer.value);
|
|
25616
|
+
catalogPointers.set(id, record2.pointer.value);
|
|
25617
|
+
} else catalogPointers.delete(id);
|
|
25618
|
+
writePajaIntentCatalogPointers([...pointers]);
|
|
25619
|
+
});
|
|
25620
|
+
window.addEventListener("pagehide", stopCatalogPersistence, { once: true });
|
|
25639
25621
|
window.addEventListener("pagehide", stopIdentityChanges, { once: true });
|
|
25640
25622
|
window.addEventListener("pagehide", () => confirmationController.dispose(), { once: true });
|
|
25641
25623
|
window.addEventListener("pagehide", () => notifyController?.dispose(), { once: true });
|
|
@@ -25689,24 +25671,30 @@ async function installPajaHost() {
|
|
|
25689
25671
|
setStatus(state, "error");
|
|
25690
25672
|
});
|
|
25691
25673
|
installPajaControlListeners(state);
|
|
25692
|
-
const
|
|
25693
|
-
|
|
25694
|
-
runtime.intentDefaults.
|
|
25674
|
+
const links = createPajaIntentLinkController(void 0, {
|
|
25675
|
+
setDefault(archetype, id) {
|
|
25676
|
+
runtime.intentDefaults.set(archetype, id);
|
|
25695
25677
|
writePajaIntentDefaults(runtime.intentDefaults);
|
|
25678
|
+
},
|
|
25679
|
+
onAccepted() {
|
|
25680
|
+
const url = new URL(window.location.href);
|
|
25681
|
+
if (!url.searchParams.has("intent")) return;
|
|
25682
|
+
url.search = "";
|
|
25683
|
+
window.history.replaceState(window.history.state, "", url);
|
|
25696
25684
|
}
|
|
25697
25685
|
});
|
|
25698
|
-
|
|
25686
|
+
intentLinks = links;
|
|
25699
25687
|
const builder = createPajaIntentLinkBuilder({
|
|
25700
25688
|
onTest(link) {
|
|
25701
|
-
if (!
|
|
25702
|
-
|
|
25689
|
+
if (!links) return;
|
|
25690
|
+
links.open(link, (intent, progress) => launchPajaSharedIntent(state, context, intent, progress, setPointerStatus));
|
|
25703
25691
|
}
|
|
25704
25692
|
});
|
|
25705
25693
|
state.createIntentLink = (tab) => {
|
|
25706
25694
|
if (isPajaLocalTarget(tab.resolvedTarget) || tab.resolvedTarget.manifest.archetypes.length === 0) return;
|
|
25707
25695
|
builder.open(tab.resolvedTarget);
|
|
25708
25696
|
};
|
|
25709
|
-
window.addEventListener("pagehide", () =>
|
|
25697
|
+
window.addEventListener("pagehide", () => links?.dispose(), { once: true });
|
|
25710
25698
|
window.addEventListener("pagehide", () => builder.dispose(), { once: true });
|
|
25711
25699
|
window.addEventListener("pagehide", () => {
|
|
25712
25700
|
for (const [source, launcher] of runtime.launcherFrames) {
|
|
@@ -25728,18 +25716,19 @@ async function installPajaHost() {
|
|
|
25728
25716
|
} catch (error) {
|
|
25729
25717
|
setStatus(state, "error");
|
|
25730
25718
|
setPointerStatus(state, error instanceof Error ? error.message : String(error));
|
|
25719
|
+
links?.showError(error);
|
|
25731
25720
|
return;
|
|
25732
25721
|
}
|
|
25733
25722
|
const persistedTabs = readPersistedRuntimeTabs(config);
|
|
25734
25723
|
const input = document.getElementById("runtime-pointer-input");
|
|
25735
25724
|
if (input instanceof HTMLInputElement) input.value = state.pointerValue;
|
|
25736
|
-
if (incomingIntent &&
|
|
25725
|
+
if (incomingIntent && links) {
|
|
25737
25726
|
void hydratePersistedIntentCatalog(state, context, persistedTabs).then(() => {
|
|
25738
25727
|
setStatus(state, "ready");
|
|
25739
25728
|
setEmptyStageVisible(true);
|
|
25740
25729
|
renderRuntimeTabs(state);
|
|
25741
|
-
|
|
25742
|
-
});
|
|
25730
|
+
links.open(incomingIntent, (intent, progress) => launchPajaSharedIntent(state, context, intent, progress, setPointerStatus));
|
|
25731
|
+
}).catch((error) => links.showError(error));
|
|
25743
25732
|
} else if (state.pointerValue) void state.loadPointer(state.pointerValue);
|
|
25744
25733
|
else if (persistedTabs) void restorePersistedRuntimeTabs(state, context, persistedTabs);
|
|
25745
25734
|
else {
|