@lapis-notes/api 0.1.0 → 0.1.3
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/CHANGELOG.md +39 -0
- package/README.md +5 -0
- package/dist/app-database-host.d.ts +1 -0
- package/dist/app-database-host.js +1 -0
- package/dist/browser-sync-security.d.ts +45 -0
- package/dist/browser-sync-security.js +189 -0
- package/dist/build-tools/vite.d.ts +15 -0
- package/dist/build-tools/vite.js +36 -0
- package/dist/cache.svelte.d.ts +1 -1
- package/dist/cache.svelte.js +4 -1
- package/dist/components/editor/editor.css +54 -2
- package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte +18 -5
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.d.ts +0 -2
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.js +0 -14
- package/dist/components/editor/language-service/index.js +23 -2
- package/dist/components/icon/icon-list.svelte.d.ts +1 -1
- package/dist/components/select/select.svelte.d.ts +1 -1
- package/dist/context.svelte.d.ts +11 -1
- package/dist/context.svelte.js +38 -1
- package/dist/docs-api.d.ts +2 -0
- package/dist/docs-api.js +1 -0
- package/dist/generated/plugin-host-modules.schema.generated.d.ts +443 -13
- package/dist/generated/plugin-host-modules.schema.generated.js +970 -306
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/language-service/manager.d.ts +5 -0
- package/dist/language-service/manager.js +58 -14
- package/dist/lapis-extension.d.ts +1 -1
- package/dist/lapis-extension.js +12 -0
- package/dist/nostr-signer.d.ts +68 -0
- package/dist/nostr-signer.js +49 -0
- package/dist/plugin-asset-server.d.ts +1 -0
- package/dist/plugin-asset-server.js +3 -0
- package/dist/plugin-capability-facade.d.ts +1 -1
- package/dist/plugin-capability-facade.js +6 -0
- package/dist/plugin-distribution/catalog-media.d.ts +4 -0
- package/dist/plugin-distribution/catalog-media.js +40 -0
- package/dist/plugin-distribution/download-stats.d.ts +4 -0
- package/dist/plugin-distribution/download-stats.js +19 -0
- package/dist/plugin-distribution/index.d.ts +4 -0
- package/dist/plugin-distribution/index.js +4 -0
- package/dist/plugin-distribution/installer.d.ts +3 -0
- package/dist/plugin-distribution/installer.js +26 -3
- package/dist/plugin-distribution/manager.d.ts +71 -5
- package/dist/plugin-distribution/manager.js +376 -37
- package/dist/plugin-distribution/nostr-registry-adapter.d.ts +29 -0
- package/dist/plugin-distribution/nostr-registry-adapter.js +152 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.d.ts +43 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.js +64 -1
- package/dist/plugin-distribution/plugin-bundle.d.ts +14 -0
- package/dist/plugin-distribution/plugin-bundle.js +46 -7
- package/dist/plugin-distribution/plugin-markdown.d.ts +6 -0
- package/dist/plugin-distribution/plugin-markdown.js +94 -0
- package/dist/plugin-distribution/registry-client.d.ts +6 -1
- package/dist/plugin-distribution/registry-client.js +32 -10
- package/dist/plugin-distribution/registry-source-state.d.ts +15 -0
- package/dist/plugin-distribution/registry-source-state.js +42 -0
- package/dist/plugin-distribution/schemas.d.ts +2511 -2824
- package/dist/plugin-distribution/schemas.js +111 -42
- package/dist/plugin-distribution/types.d.ts +116 -5
- package/dist/plugin-distribution-host.d.ts +4 -0
- package/dist/plugin-distribution-host.js +2 -0
- package/dist/plugin-install-progress.js +1 -1
- package/dist/plugin-manager.d.ts +3 -0
- package/dist/plugin-manager.js +21 -13
- package/dist/plugin-settings.d.ts +21 -0
- package/dist/plugin-settings.js +41 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.js +56 -0
- package/dist/registry-schema/catalog-v1.d.ts +4143 -0
- package/dist/registry-schema/catalog-v1.js +172 -0
- package/dist/registry-schema/catalog-v2.d.ts +5275 -0
- package/dist/registry-schema/catalog-v2.js +137 -0
- package/dist/registry-schema/index.d.ts +1 -0
- package/dist/registry-schema/index.js +1 -0
- package/dist/registry-schema/release-notes.d.ts +5 -0
- package/dist/registry-schema/release-notes.js +21 -0
- package/dist/server-host.d.ts +28 -0
- package/dist/server-host.js +17 -0
- package/dist/storage/app-database.d.ts +37 -2
- package/dist/storage/app-database.js +41 -1
- package/dist/storage/browser-coordinated-app-database.d.ts +3 -1
- package/dist/storage/browser-coordinated-app-database.js +8 -0
- package/dist/storage/browser.d.ts +4 -1
- package/dist/storage/browser.js +84 -0
- package/dist/storage/desktop-native.d.ts +4 -2
- package/dist/storage/desktop-native.js +90 -1
- package/dist/storage/fs.d.ts +17 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/memory-vault-adapter.d.ts +3 -1
- package/dist/storage/memory-vault-adapter.js +60 -0
- package/dist/storage/remote-app-database.d.ts +19 -0
- package/dist/storage/remote-app-database.js +130 -0
- package/dist/storage/task-projection.d.ts +2 -1
- package/dist/storage/task-projection.js +7 -2
- package/dist/storage/turso-app-database.d.ts +3 -1
- package/dist/storage/turso-app-database.js +45 -0
- package/dist/storage/vault-session.js +11 -0
- package/dist/storage/vault-state.d.ts +1 -1
- package/dist/storage/vault.svelte.d.ts +3 -1
- package/dist/storage/vault.svelte.js +76 -3
- package/dist/sync-host-security.d.ts +285 -0
- package/dist/sync-host-security.js +2237 -0
- package/dist/sync-host.d.ts +179 -0
- package/dist/sync-host.js +769 -0
- package/dist/workspace.svelte.d.ts +1 -1
- package/dist/workspace.svelte.js +1 -1
- package/package.json +48 -10
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export * from "./menu.svelte";
|
|
|
8
8
|
export * from "./cache.svelte";
|
|
9
9
|
export * from "./plugin";
|
|
10
10
|
export * from "./plugin-capability-facade";
|
|
11
|
+
export * from "./sync-host";
|
|
12
|
+
export * from "./sync-host-security";
|
|
13
|
+
export * from "./browser-sync-security";
|
|
11
14
|
export * from "./plugin-asset-server";
|
|
12
15
|
export * from "./plugin-dependency-resolver";
|
|
13
16
|
export * from "./plugin-dependency-scanner";
|
|
@@ -48,6 +51,7 @@ export * from "./command.svelte";
|
|
|
48
51
|
export * from "./configuration.svelte";
|
|
49
52
|
export * from "./configuration-option-source-registry";
|
|
50
53
|
export * from "./configuration-option-source-providers";
|
|
54
|
+
export * from "./plugin-settings";
|
|
51
55
|
export * from "./editor-view-registry";
|
|
52
56
|
export * from "./glob";
|
|
53
57
|
export * from "./platform";
|
|
@@ -58,6 +62,7 @@ export * from "./markdown-file-surface-registry";
|
|
|
58
62
|
export * from "./logging";
|
|
59
63
|
export * from "./telemetry";
|
|
60
64
|
export * from "./notifications";
|
|
65
|
+
export * from "./nostr-signer";
|
|
61
66
|
export * from "./status-bar.svelte";
|
|
62
67
|
export * from "./app-url";
|
|
63
68
|
export * from "./agent-tools";
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,9 @@ export * from "./menu.svelte";
|
|
|
8
8
|
export * from "./cache.svelte";
|
|
9
9
|
export * from "./plugin";
|
|
10
10
|
export * from "./plugin-capability-facade";
|
|
11
|
+
export * from "./sync-host";
|
|
12
|
+
export * from "./sync-host-security";
|
|
13
|
+
export * from "./browser-sync-security";
|
|
11
14
|
export * from "./plugin-asset-server";
|
|
12
15
|
export * from "./plugin-dependency-resolver";
|
|
13
16
|
export * from "./plugin-dependency-scanner";
|
|
@@ -48,6 +51,7 @@ export * from "./command.svelte";
|
|
|
48
51
|
export * from "./configuration.svelte";
|
|
49
52
|
export * from "./configuration-option-source-registry";
|
|
50
53
|
export * from "./configuration-option-source-providers";
|
|
54
|
+
export * from "./plugin-settings";
|
|
51
55
|
export * from "./editor-view-registry";
|
|
52
56
|
export * from "./glob";
|
|
53
57
|
export * from "./platform";
|
|
@@ -58,6 +62,7 @@ export * from "./markdown-file-surface-registry";
|
|
|
58
62
|
export * from "./logging";
|
|
59
63
|
export * from "./telemetry";
|
|
60
64
|
export * from "./notifications";
|
|
65
|
+
export * from "./nostr-signer";
|
|
61
66
|
export * from "./status-bar.svelte";
|
|
62
67
|
export * from "./app-url";
|
|
63
68
|
export * from "./agent-tools";
|
|
@@ -12,12 +12,17 @@ export declare class LanguageServiceManager {
|
|
|
12
12
|
private readonly onBeforeResolve?;
|
|
13
13
|
private diagnosticsBinding;
|
|
14
14
|
private readonly openDocuments;
|
|
15
|
+
private readonly pendingDocumentReleases;
|
|
15
16
|
private readonly cachedCodeActions;
|
|
17
|
+
private readonly diagnosticsRequestIds;
|
|
16
18
|
private readonly providerFailures;
|
|
19
|
+
private readonly diagnosticsInvalidationListeners;
|
|
17
20
|
constructor(options?: {
|
|
18
21
|
onBeforeResolve?: (languageId: string) => Promise<void> | void;
|
|
19
22
|
});
|
|
20
23
|
registerProvider(provider: LanguageServiceProvider): () => void;
|
|
24
|
+
onDiagnosticsInvalidated(listener: (languages: readonly string[]) => void): () => void;
|
|
25
|
+
invalidateDiagnostics(providerId: string): boolean;
|
|
21
26
|
bindDiagnostics(binding: LanguageServiceDiagnosticsBinding): () => void;
|
|
22
27
|
reportProviderFailure(id: string, error: unknown): void;
|
|
23
28
|
clearProviderFailure(id: string): void;
|
|
@@ -36,8 +36,11 @@ export class LanguageServiceManager {
|
|
|
36
36
|
onBeforeResolve;
|
|
37
37
|
diagnosticsBinding = null;
|
|
38
38
|
openDocuments = new Map();
|
|
39
|
+
pendingDocumentReleases = new Map();
|
|
39
40
|
cachedCodeActions = new Map();
|
|
41
|
+
diagnosticsRequestIds = new Map();
|
|
40
42
|
providerFailures = new Map();
|
|
43
|
+
diagnosticsInvalidationListeners = new Set();
|
|
41
44
|
constructor(options = {}) {
|
|
42
45
|
this.onBeforeResolve = options.onBeforeResolve;
|
|
43
46
|
}
|
|
@@ -56,6 +59,26 @@ export class LanguageServiceManager {
|
|
|
56
59
|
}
|
|
57
60
|
};
|
|
58
61
|
}
|
|
62
|
+
onDiagnosticsInvalidated(listener) {
|
|
63
|
+
this.diagnosticsInvalidationListeners.add(listener);
|
|
64
|
+
return () => {
|
|
65
|
+
this.diagnosticsInvalidationListeners.delete(listener);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
invalidateDiagnostics(providerId) {
|
|
69
|
+
const provider = this.providers.get(providerId);
|
|
70
|
+
if (!provider)
|
|
71
|
+
return false;
|
|
72
|
+
for (const document of this.documents.values()) {
|
|
73
|
+
if (provider.metadata.languages.includes(document.languageId)) {
|
|
74
|
+
this.deleteCachedCodeActions(document.uri);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const listener of this.diagnosticsInvalidationListeners) {
|
|
78
|
+
listener([...provider.metadata.languages]);
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
59
82
|
bindDiagnostics(binding) {
|
|
60
83
|
this.diagnosticsBinding = binding;
|
|
61
84
|
this.publishProviderFailures();
|
|
@@ -84,6 +107,7 @@ export class LanguageServiceManager {
|
|
|
84
107
|
this.diagnosticsBinding = null;
|
|
85
108
|
}
|
|
86
109
|
retainDocument(uri) {
|
|
110
|
+
this.pendingDocumentReleases.delete(uri);
|
|
87
111
|
this.openDocuments.set(uri, (this.openDocuments.get(uri) ?? 0) + 1);
|
|
88
112
|
let released = false;
|
|
89
113
|
return () => {
|
|
@@ -96,12 +120,21 @@ export class LanguageServiceManager {
|
|
|
96
120
|
return;
|
|
97
121
|
}
|
|
98
122
|
this.openDocuments.delete(uri);
|
|
99
|
-
|
|
100
|
-
this.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
123
|
+
const release = Symbol(uri);
|
|
124
|
+
this.pendingDocumentReleases.set(uri, release);
|
|
125
|
+
queueMicrotask(() => {
|
|
126
|
+
if (this.pendingDocumentReleases.get(uri) !== release ||
|
|
127
|
+
this.openDocuments.has(uri)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.pendingDocumentReleases.delete(uri);
|
|
131
|
+
this.documents.delete(uri);
|
|
132
|
+
this.deleteCachedCodeActions(uri);
|
|
133
|
+
const collection = this.diagnosticsBinding?.collection;
|
|
134
|
+
if (collection && !collection.disposed) {
|
|
135
|
+
collection.delete(resourceForUri(uri));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
105
138
|
};
|
|
106
139
|
}
|
|
107
140
|
buildDiagnosticItemMenu(menu, entry) {
|
|
@@ -154,24 +187,27 @@ export class LanguageServiceManager {
|
|
|
154
187
|
return [...this.globals.values()].sort((left, right) => left.uri.localeCompare(right.uri));
|
|
155
188
|
}
|
|
156
189
|
async diagnostics(document) {
|
|
190
|
+
const requestId = (this.diagnosticsRequestIds.get(document.uri) ?? 0) + 1;
|
|
191
|
+
this.diagnosticsRequestIds.set(document.uri, requestId);
|
|
157
192
|
this.updateDocument(document);
|
|
158
193
|
await this.onBeforeResolve?.(document.languageId);
|
|
159
194
|
const sorted = this.matchingProviders(document.languageId, "diagnostics");
|
|
160
195
|
if (!sorted.length) {
|
|
161
|
-
this.publishDiagnostics(document, []);
|
|
196
|
+
this.publishDiagnostics(document, [], requestId);
|
|
162
197
|
return [];
|
|
163
198
|
}
|
|
164
199
|
const skipProviderIds = new Set();
|
|
165
200
|
const results = await Promise.all(sorted.map(async (provider) => {
|
|
166
201
|
const result = await this.settleProviderCapability(provider, "diagnostics", provider.provideDiagnostics?.(this.context(document)), []);
|
|
167
|
-
if (result.timedOut ||
|
|
202
|
+
if (result.timedOut ||
|
|
203
|
+
this.providerFailures.has(provider.metadata.id)) {
|
|
168
204
|
skipProviderIds.add(provider.metadata.id);
|
|
169
205
|
}
|
|
170
206
|
return result.value;
|
|
171
207
|
}));
|
|
172
208
|
const diagnostics = results.flatMap((diagnostics) => diagnostics ?? []);
|
|
173
|
-
await this.cacheCodeActions(document, diagnostics, skipProviderIds);
|
|
174
|
-
this.publishDiagnostics(document, diagnostics);
|
|
209
|
+
await this.cacheCodeActions(document, diagnostics, skipProviderIds, requestId);
|
|
210
|
+
this.publishDiagnostics(document, diagnostics, requestId);
|
|
175
211
|
return diagnostics;
|
|
176
212
|
}
|
|
177
213
|
async completions(document, position) {
|
|
@@ -279,20 +315,28 @@ export class LanguageServiceManager {
|
|
|
279
315
|
providersFor(languageId, capability) {
|
|
280
316
|
return this.matchingProviders(languageId, capability);
|
|
281
317
|
}
|
|
282
|
-
publishDiagnostics(document, diagnostics) {
|
|
318
|
+
publishDiagnostics(document, diagnostics, requestId) {
|
|
283
319
|
if (!this.diagnosticsBinding ||
|
|
284
320
|
this.diagnosticsBinding.collection.disposed ||
|
|
285
|
-
!this.openDocuments.has(document.uri)
|
|
321
|
+
!this.openDocuments.has(document.uri) ||
|
|
322
|
+
this.diagnosticsRequestIds.get(document.uri) !== requestId) {
|
|
286
323
|
return;
|
|
287
324
|
}
|
|
288
325
|
this.diagnosticsBinding.collection.set(resourceForUri(document.uri), diagnostics.map(toWorkspaceDiagnostic));
|
|
289
326
|
}
|
|
290
|
-
async cacheCodeActions(document, diagnostics, skipProviderIds) {
|
|
327
|
+
async cacheCodeActions(document, diagnostics, skipProviderIds, requestId) {
|
|
328
|
+
if (requestId !== undefined &&
|
|
329
|
+
this.diagnosticsRequestIds.get(document.uri) !== requestId) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
291
332
|
this.deleteCachedCodeActions(document.uri);
|
|
292
333
|
await Promise.all(diagnostics.map(async (diagnostic) => {
|
|
293
334
|
const actions = await this.codeActions(document, diagnostic.range, skipProviderIds);
|
|
294
|
-
if (!this.openDocuments.has(document.uri)
|
|
335
|
+
if (!this.openDocuments.has(document.uri) ||
|
|
336
|
+
(requestId !== undefined &&
|
|
337
|
+
this.diagnosticsRequestIds.get(document.uri) !== requestId)) {
|
|
295
338
|
return;
|
|
339
|
+
}
|
|
296
340
|
this.cachedCodeActions.set(diagnosticCacheKey(document.uri, diagnostic), uniqueActionsForDiagnostic(actions, diagnostic));
|
|
297
341
|
}));
|
|
298
342
|
}
|
|
@@ -19,7 +19,7 @@ export interface LapisRuntimeEntryMap {
|
|
|
19
19
|
desktop?: LapisRuntimeEntryDescriptor;
|
|
20
20
|
trustedDesktop?: LapisRuntimeEntryDescriptor;
|
|
21
21
|
}
|
|
22
|
-
export type LapisExtensionPermission = "vault.read" | "vault.write" | "plugin.data" | "commands" | "notices" | "settings.read" | "metadata.query" | "events" | "logging";
|
|
22
|
+
export type LapisExtensionPermission = "vault.read" | "vault.write" | "plugin.data" | "commands" | "notices" | "settings.read" | "metadata.query" | "metadata.identity" | "metadata.changes" | "vault.stream" | "plugin.generated-state" | "network.connect" | "crypto.nostr-sync" | "events" | "logging";
|
|
23
23
|
export interface LapisCommandContribution {
|
|
24
24
|
command: string;
|
|
25
25
|
title: string;
|
package/dist/lapis-extension.js
CHANGED
|
@@ -510,6 +510,18 @@ function permissionToCapability(permission) {
|
|
|
510
510
|
return "settings";
|
|
511
511
|
case "metadata.query":
|
|
512
512
|
return "metadata:query";
|
|
513
|
+
case "metadata.identity":
|
|
514
|
+
return "metadata:identity";
|
|
515
|
+
case "metadata.changes":
|
|
516
|
+
return "metadata:changes";
|
|
517
|
+
case "vault.stream":
|
|
518
|
+
return "vault:stream";
|
|
519
|
+
case "plugin.generated-state":
|
|
520
|
+
return "plugin:generated-state";
|
|
521
|
+
case "network.connect":
|
|
522
|
+
return "network:connect";
|
|
523
|
+
case "crypto.nostr-sync":
|
|
524
|
+
return "crypto:nostr-sync";
|
|
513
525
|
default:
|
|
514
526
|
return null;
|
|
515
527
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface NostrUnsignedEvent {
|
|
2
|
+
kind: number;
|
|
3
|
+
created_at: number;
|
|
4
|
+
tags: string[][];
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
export interface NostrSignedEvent extends NostrUnsignedEvent {
|
|
8
|
+
id: string;
|
|
9
|
+
pubkey: string;
|
|
10
|
+
sig: string;
|
|
11
|
+
}
|
|
12
|
+
export type NostrSignerAccountKind = "local" | "nip46";
|
|
13
|
+
export interface NostrSignerAccount {
|
|
14
|
+
/** Opaque host-owned account identifier. */
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
pubkey: string;
|
|
18
|
+
kind: NostrSignerAccountKind;
|
|
19
|
+
}
|
|
20
|
+
export interface NostrProfileCreationRequest {
|
|
21
|
+
displayName?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface NostrRemoteSignerRequest {
|
|
24
|
+
bunkerUrl: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface NostrPrivateKeyImportRequest {
|
|
28
|
+
privateKey: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface NostrSignerBrokerContext {
|
|
32
|
+
/** Identifies the plugin requesting a permissioned signer operation. */
|
|
33
|
+
pluginId: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Host-owned signer boundary. Implementations own consent, OS-keychain
|
|
37
|
+
* storage, NIP-46 client credentials, signature validation, and session
|
|
38
|
+
* teardown. Private keys may cross this boundary once for explicit import,
|
|
39
|
+
* but host implementations must never return them to plugin code.
|
|
40
|
+
*/
|
|
41
|
+
export interface NostrSignerBroker {
|
|
42
|
+
listAccounts(context: NostrSignerBrokerContext): Promise<NostrSignerAccount[]>;
|
|
43
|
+
requestProfileCreation(context: NostrSignerBrokerContext, request: NostrProfileCreationRequest): Promise<NostrSignerAccount>;
|
|
44
|
+
connectRemoteSigner(context: NostrSignerBrokerContext, request: NostrRemoteSignerRequest): Promise<NostrSignerAccount>;
|
|
45
|
+
importPrivateKey(context: NostrSignerBrokerContext, request: NostrPrivateKeyImportRequest): Promise<NostrSignerAccount>;
|
|
46
|
+
getPublicKey(context: NostrSignerBrokerContext, accountId: string): Promise<string>;
|
|
47
|
+
signEvent(context: NostrSignerBrokerContext, accountId: string, event: NostrUnsignedEvent): Promise<NostrSignedEvent>;
|
|
48
|
+
nip44Encrypt(context: NostrSignerBrokerContext, accountId: string, pubkey: string, plaintext: string): Promise<string>;
|
|
49
|
+
nip44Decrypt(context: NostrSignerBrokerContext, accountId: string, pubkey: string, ciphertext: string): Promise<string>;
|
|
50
|
+
close(context: NostrSignerBrokerContext, accountId: string): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
export interface NostrPluginSigner {
|
|
53
|
+
readonly pluginId: string;
|
|
54
|
+
listAccounts(): Promise<NostrSignerAccount[]>;
|
|
55
|
+
requestProfileCreation(request?: NostrProfileCreationRequest): Promise<NostrSignerAccount>;
|
|
56
|
+
connectRemoteSigner(request: NostrRemoteSignerRequest): Promise<NostrSignerAccount>;
|
|
57
|
+
importPrivateKey(request: NostrPrivateKeyImportRequest): Promise<NostrSignerAccount>;
|
|
58
|
+
getPublicKey(accountId: string): Promise<string>;
|
|
59
|
+
signEvent(accountId: string, event: NostrUnsignedEvent): Promise<NostrSignedEvent>;
|
|
60
|
+
nip44Encrypt(accountId: string, pubkey: string, plaintext: string): Promise<string>;
|
|
61
|
+
nip44Decrypt(accountId: string, pubkey: string, ciphertext: string): Promise<string>;
|
|
62
|
+
close(accountId: string): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
export declare class NostrSignerHost {
|
|
65
|
+
#private;
|
|
66
|
+
constructor(broker?: NostrSignerBroker);
|
|
67
|
+
forPlugin(pluginId: string): NostrPluginSigner;
|
|
68
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export class NostrSignerHost {
|
|
2
|
+
#broker;
|
|
3
|
+
constructor(broker = unavailableNostrSignerBroker) {
|
|
4
|
+
this.#broker = broker;
|
|
5
|
+
}
|
|
6
|
+
forPlugin(pluginId) {
|
|
7
|
+
if (!pluginId.trim())
|
|
8
|
+
throw new TypeError("pluginId is required");
|
|
9
|
+
const context = Object.freeze({ pluginId });
|
|
10
|
+
return {
|
|
11
|
+
pluginId,
|
|
12
|
+
listAccounts: () => this.#broker.listAccounts(context),
|
|
13
|
+
requestProfileCreation: (request = {}) => this.#broker.requestProfileCreation(context, request),
|
|
14
|
+
connectRemoteSigner: (request) => this.#broker.connectRemoteSigner(context, request),
|
|
15
|
+
importPrivateKey: (request) => this.#broker.importPrivateKey(context, request),
|
|
16
|
+
getPublicKey: (accountId) => this.#broker.getPublicKey(context, accountId),
|
|
17
|
+
signEvent: async (accountId, event) => {
|
|
18
|
+
const signed = await this.#broker.signEvent(context, accountId, event);
|
|
19
|
+
assertSignedEventMatches(event, signed);
|
|
20
|
+
return signed;
|
|
21
|
+
},
|
|
22
|
+
nip44Encrypt: (accountId, pubkey, plaintext) => this.#broker.nip44Encrypt(context, accountId, pubkey, plaintext),
|
|
23
|
+
nip44Decrypt: (accountId, pubkey, ciphertext) => this.#broker.nip44Decrypt(context, accountId, pubkey, ciphertext),
|
|
24
|
+
close: (accountId) => this.#broker.close(context, accountId),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const unavailable = () => {
|
|
29
|
+
throw new Error("The host does not provide Nostr signing");
|
|
30
|
+
};
|
|
31
|
+
const unavailableNostrSignerBroker = {
|
|
32
|
+
listAccounts: async () => [],
|
|
33
|
+
requestProfileCreation: async () => unavailable(),
|
|
34
|
+
connectRemoteSigner: async () => unavailable(),
|
|
35
|
+
importPrivateKey: async () => unavailable(),
|
|
36
|
+
getPublicKey: async () => unavailable(),
|
|
37
|
+
signEvent: async () => unavailable(),
|
|
38
|
+
nip44Encrypt: async () => unavailable(),
|
|
39
|
+
nip44Decrypt: async () => unavailable(),
|
|
40
|
+
close: async () => undefined,
|
|
41
|
+
};
|
|
42
|
+
function assertSignedEventMatches(requested, signed) {
|
|
43
|
+
if (signed.kind !== requested.kind ||
|
|
44
|
+
signed.created_at !== requested.created_at ||
|
|
45
|
+
signed.content !== requested.content ||
|
|
46
|
+
JSON.stringify(signed.tags) !== JSON.stringify(requested.tags)) {
|
|
47
|
+
throw new Error("Nostr signer changed the requested event");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -53,6 +53,7 @@ export declare function parseWebPluginAssetUrl(value: string, base?: string): Pl
|
|
|
53
53
|
export declare function getInstalledPluginAssetVersion(record: InstalledPluginRecord): string;
|
|
54
54
|
export declare function getInstalledPluginAssetHash(record: InstalledPluginRecord, relativePath: string): string;
|
|
55
55
|
export declare function getInstalledPluginAssetFingerprint(record: InstalledPluginRecord): string;
|
|
56
|
+
export declare function getInstalledPluginAssetFingerprintSha256(record: InstalledPluginRecord): Promise<string>;
|
|
56
57
|
export declare function isSupportedPluginAssetPath(path: string): boolean;
|
|
57
58
|
export declare function assertSupportedPluginAssetPath(path: string): void;
|
|
58
59
|
export declare function getPluginAssetContentType(path: string): string;
|
|
@@ -66,6 +66,9 @@ export function getInstalledPluginAssetFingerprint(record) {
|
|
|
66
66
|
.map((file) => `${file.path}:${file.size}:${file.sha256}`)
|
|
67
67
|
.join("|");
|
|
68
68
|
}
|
|
69
|
+
export async function getInstalledPluginAssetFingerprintSha256(record) {
|
|
70
|
+
return sha256Hex(new TextEncoder().encode(getInstalledPluginAssetFingerprint(record)));
|
|
71
|
+
}
|
|
69
72
|
export function isSupportedPluginAssetPath(path) {
|
|
70
73
|
return /\.(?:mjs|js|cjs|css|json|wasm|svg|png|jpg|jpeg|gif)$/iu.test(path);
|
|
71
74
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type HostedPluginCapability = "vault:read" | "vault:write" | "plugin:data" | "commands" | "notices" | "settings" | "metadata:query" | "events" | "logging";
|
|
1
|
+
export type HostedPluginCapability = "vault:read" | "vault:write" | "plugin:data" | "commands" | "notices" | "settings" | "metadata:query" | "metadata:identity" | "metadata:changes" | "vault:stream" | "plugin:generated-state" | "network:connect" | "crypto:nostr-sync" | "events" | "logging";
|
|
2
2
|
export declare const HOSTED_PLUGIN_CAPABILITIES: readonly HostedPluginCapability[];
|
|
3
3
|
export type HostedPluginLogLevel = "trace" | "debug" | "info" | "warn" | "error";
|
|
4
4
|
export interface HostedPluginCapabilityRequest<TPayload extends Record<string, unknown> = Record<string, unknown>> {
|
|
@@ -6,6 +6,12 @@ export const HOSTED_PLUGIN_CAPABILITIES = [
|
|
|
6
6
|
"notices",
|
|
7
7
|
"settings",
|
|
8
8
|
"metadata:query",
|
|
9
|
+
"metadata:identity",
|
|
10
|
+
"metadata:changes",
|
|
11
|
+
"vault:stream",
|
|
12
|
+
"plugin:generated-state",
|
|
13
|
+
"network:connect",
|
|
14
|
+
"crypto:nostr-sync",
|
|
9
15
|
"events",
|
|
10
16
|
"logging",
|
|
11
17
|
];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { resolveRegistryMedia, type RegistryMediaV2 } from "../registry-schema/index.js";
|
|
2
|
+
export { resolveRegistryMedia };
|
|
3
|
+
export type { RegistryMediaV2 };
|
|
4
|
+
export declare function fetchVerifiedPluginImage(reference: ReturnType<typeof resolveRegistryMedia>, fetchImpl?: typeof fetch, signal?: AbortSignal): Promise<Blob>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { resolveRegistryMedia, } from "../registry-schema/index.js";
|
|
2
|
+
import { verifySha256 } from "./hashes";
|
|
3
|
+
import { PluginDistributionError } from "./errors";
|
|
4
|
+
export { resolveRegistryMedia };
|
|
5
|
+
export async function fetchVerifiedPluginImage(reference, fetchImpl = fetch, signal) {
|
|
6
|
+
if (reference.size > 5 * 1024 * 1024 || reference.size < 1)
|
|
7
|
+
throw new PluginDistributionError("metadata-invalid", "Plugin image exceeds the supported size.");
|
|
8
|
+
const response = await fetchImpl(reference.url, { signal });
|
|
9
|
+
if (!response.ok || !response.body)
|
|
10
|
+
throw new PluginDistributionError("metadata-invalid", "Plugin image request failed.");
|
|
11
|
+
const reader = response.body.getReader();
|
|
12
|
+
const chunks = [];
|
|
13
|
+
let size = 0;
|
|
14
|
+
try {
|
|
15
|
+
while (true) {
|
|
16
|
+
const chunk = await reader.read();
|
|
17
|
+
if (chunk.done)
|
|
18
|
+
break;
|
|
19
|
+
size += chunk.value.byteLength;
|
|
20
|
+
if (size > reference.size) {
|
|
21
|
+
await reader.cancel();
|
|
22
|
+
throw new PluginDistributionError("metadata-invalid", "Plugin image size does not match signed metadata.");
|
|
23
|
+
}
|
|
24
|
+
chunks.push(chunk.value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
reader.releaseLock();
|
|
29
|
+
}
|
|
30
|
+
if (size !== reference.size)
|
|
31
|
+
throw new PluginDistributionError("metadata-invalid", "Plugin image size does not match signed metadata.");
|
|
32
|
+
const bytes = new Uint8Array(size);
|
|
33
|
+
let offset = 0;
|
|
34
|
+
for (const chunk of chunks) {
|
|
35
|
+
bytes.set(chunk, offset);
|
|
36
|
+
offset += chunk.byteLength;
|
|
37
|
+
}
|
|
38
|
+
await verifySha256(bytes, reference.sha256);
|
|
39
|
+
return new Blob([bytes], { type: reference.mediaType });
|
|
40
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PluginDownloadCounts, PluginDownloadStatsSummary } from "./types";
|
|
2
|
+
export declare const PLUGIN_DOWNLOAD_STATS_MAX_AGE_DAYS = 5;
|
|
3
|
+
export declare function isUsablePluginDownloadStatsSummary(summary: PluginDownloadStatsSummary, now?: Date): boolean;
|
|
4
|
+
export declare function pluginDownloadCounts(summary: PluginDownloadStatsSummary, pluginId: string): PluginDownloadCounts;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const PLUGIN_DOWNLOAD_STATS_MAX_AGE_DAYS = 5;
|
|
2
|
+
const ONE_DAY_MS = 86_400_000;
|
|
3
|
+
export function isUsablePluginDownloadStatsSummary(summary, now = new Date()) {
|
|
4
|
+
if (!summary.through || summary.trackedSince > summary.through)
|
|
5
|
+
return false;
|
|
6
|
+
if (Object.values(summary.periods).some((period) => !period.from || !period.through)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
10
|
+
const through = Date.parse(`${summary.through}T00:00:00.000Z`);
|
|
11
|
+
const ageDays = (today - through) / ONE_DAY_MS;
|
|
12
|
+
return ageDays >= 0 && ageDays <= PLUGIN_DOWNLOAD_STATS_MAX_AGE_DAYS;
|
|
13
|
+
}
|
|
14
|
+
export function pluginDownloadCounts(summary, pluginId) {
|
|
15
|
+
return {
|
|
16
|
+
lifetime: summary.periods.lifetime.plugins[pluginId]?.total ?? 0,
|
|
17
|
+
recent: summary.periods["30d"].plugins[pluginId]?.total ?? 0,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export * from "./canonical-json";
|
|
2
2
|
export * from "./compatibility";
|
|
3
|
+
export * from "./download-stats";
|
|
3
4
|
export * from "./errors";
|
|
4
5
|
export * from "./hashes";
|
|
5
6
|
export * from "./installed-plugin-state";
|
|
6
7
|
export * from "./installer";
|
|
7
8
|
export * from "./manager";
|
|
9
|
+
export * from "./nostr-registry-adapter";
|
|
8
10
|
export * from "./path-safety";
|
|
11
|
+
export * from "./plugin-markdown";
|
|
9
12
|
export * from "./plugin-bundle";
|
|
10
13
|
export * from "./plugin-bundle-verifier";
|
|
11
14
|
export * from "./plugin-bundle-worker";
|
|
@@ -16,3 +19,4 @@ export * from "./runtime-validation";
|
|
|
16
19
|
export * from "./schemas";
|
|
17
20
|
export * from "./signing";
|
|
18
21
|
export * from "./types";
|
|
22
|
+
export * from "./catalog-media";
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export * from "./canonical-json";
|
|
2
2
|
export * from "./compatibility";
|
|
3
|
+
export * from "./download-stats";
|
|
3
4
|
export * from "./errors";
|
|
4
5
|
export * from "./hashes";
|
|
5
6
|
export * from "./installed-plugin-state";
|
|
6
7
|
export * from "./installer";
|
|
7
8
|
export * from "./manager";
|
|
9
|
+
export * from "./nostr-registry-adapter";
|
|
8
10
|
export * from "./path-safety";
|
|
11
|
+
export * from "./plugin-markdown";
|
|
9
12
|
export * from "./plugin-bundle";
|
|
10
13
|
export * from "./plugin-bundle-verifier";
|
|
11
14
|
export * from "./plugin-bundle-worker";
|
|
@@ -16,3 +19,4 @@ export * from "./runtime-validation";
|
|
|
16
19
|
export * from "./schemas";
|
|
17
20
|
export * from "./signing";
|
|
18
21
|
export * from "./types";
|
|
22
|
+
export * from "./catalog-media";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DataAdapter } from "../storage/fs";
|
|
2
2
|
import { InstalledPluginStateStore } from "./installed-plugin-state";
|
|
3
3
|
import { type PluginBundleVerificationMode } from "./plugin-bundle-worker";
|
|
4
|
+
import { type VerifiedPluginBundlePayload } from "./plugin-bundle-verifier";
|
|
4
5
|
import type { InstalledPluginRecord, PluginCatalogDetail, PluginCatalogEntry, PluginCompatibilityInput, PluginProvenance, TrustedSigningKey } from "./types";
|
|
5
6
|
export type PluginBinaryFetch = (url: string, init?: RequestInit) => Promise<PluginBinaryResponse>;
|
|
6
7
|
export type PluginBinaryResponse = Pick<Response, "ok" | "status" | "statusText" | "arrayBuffer" | "text"> & {
|
|
@@ -75,6 +76,8 @@ export declare class VerifiedPluginInstaller {
|
|
|
75
76
|
private assertReleaseMatchesCatalog;
|
|
76
77
|
private fetchBundle;
|
|
77
78
|
private verifyPluginBundle;
|
|
79
|
+
/** Install payload bytes already verified by a trusted registry adapter. */
|
|
80
|
+
installVerifiedPayload(payload: VerifiedPluginBundlePayload, options: Omit<InstallVerifiedPluginBundleOptions, "bundle">): Promise<InstalledPluginRecord>;
|
|
78
81
|
private installVerifiedBundlePayload;
|
|
79
82
|
private readBundleResponseBytes;
|
|
80
83
|
private emitBundleDownloadProgress;
|
|
@@ -23,7 +23,23 @@ export class VerifiedPluginInstaller {
|
|
|
23
23
|
throwIfAborted(options.signal);
|
|
24
24
|
const release = this.resolveRelease(options.detail, options.version);
|
|
25
25
|
const bundleUrl = new URL(release.bundle.url, options.entry.detail).toString();
|
|
26
|
-
|
|
26
|
+
let bundle;
|
|
27
|
+
const downloadUrl = release.bundle.downloadUrl
|
|
28
|
+
? new URL(release.bundle.downloadUrl, options.entry.detail).toString()
|
|
29
|
+
: bundleUrl;
|
|
30
|
+
try {
|
|
31
|
+
bundle = await this.fetchBundle(downloadUrl, release.bundle, options);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throwIfAborted(options.signal);
|
|
35
|
+
if (downloadUrl === bundleUrl ||
|
|
36
|
+
isAbortError(error) ||
|
|
37
|
+
(error instanceof PluginDistributionError &&
|
|
38
|
+
error.details?.status === 410)) {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
bundle = await this.fetchBundle(bundleUrl, release.bundle, options);
|
|
42
|
+
}
|
|
27
43
|
const payload = await this.verifyPluginBundle(bundle, {
|
|
28
44
|
...options,
|
|
29
45
|
expectedBundle: release.bundle,
|
|
@@ -118,8 +134,12 @@ export class VerifiedPluginInstaller {
|
|
|
118
134
|
}
|
|
119
135
|
return verifyPluginBundlePayload(verifierOptions);
|
|
120
136
|
}
|
|
137
|
+
/** Install payload bytes already verified by a trusted registry adapter. */
|
|
138
|
+
async installVerifiedPayload(payload, options) {
|
|
139
|
+
return this.installVerifiedBundlePayload(payload, options);
|
|
140
|
+
}
|
|
121
141
|
async installVerifiedBundlePayload(payload, options) {
|
|
122
|
-
const { releaseEnvelope, releaseManifest, releaseManifestSha256, files } = payload;
|
|
142
|
+
const { releaseEnvelope, releaseManifest, releaseManifestSha256, files, nostrProof, } = payload;
|
|
123
143
|
if (options.compatibility) {
|
|
124
144
|
const compatibility = checkReleaseManifestCompatibility(releaseManifest, options.compatibility);
|
|
125
145
|
if (!compatibility.compatible) {
|
|
@@ -193,7 +213,10 @@ export class VerifiedPluginInstaller {
|
|
|
193
213
|
...(runtimeValidation.warnings.length
|
|
194
214
|
? { runtimeWarnings: runtimeValidation.warnings }
|
|
195
215
|
: {}),
|
|
196
|
-
|
|
216
|
+
...(releaseEnvelope.signatures[0]
|
|
217
|
+
? { signature: releaseEnvelope.signatures[0] }
|
|
218
|
+
: {}),
|
|
219
|
+
...(nostrProof ? { nostrProof } : {}),
|
|
197
220
|
restartRequired: Boolean(this.options.pluginManager),
|
|
198
221
|
};
|
|
199
222
|
await this.stateStore.upsert(record);
|