@lapis-notes/api 0.1.0 → 0.1.2

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.
Files changed (109) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +5 -0
  3. package/dist/app-database-host.d.ts +1 -0
  4. package/dist/app-database-host.js +1 -0
  5. package/dist/browser-sync-security.d.ts +45 -0
  6. package/dist/browser-sync-security.js +189 -0
  7. package/dist/build-tools/vite.d.ts +15 -0
  8. package/dist/build-tools/vite.js +36 -0
  9. package/dist/cache.svelte.d.ts +1 -1
  10. package/dist/cache.svelte.js +4 -1
  11. package/dist/components/editor/editor.css +54 -2
  12. package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte +18 -5
  13. package/dist/components/editor/extensions/lint/lint-tooltip-actions.d.ts +0 -2
  14. package/dist/components/editor/extensions/lint/lint-tooltip-actions.js +0 -14
  15. package/dist/components/editor/language-service/index.js +23 -2
  16. package/dist/components/icon/icon-list.svelte.d.ts +1 -1
  17. package/dist/components/select/select.svelte.d.ts +1 -1
  18. package/dist/context.svelte.d.ts +11 -1
  19. package/dist/context.svelte.js +38 -1
  20. package/dist/docs-api.d.ts +2 -0
  21. package/dist/docs-api.js +1 -0
  22. package/dist/generated/plugin-host-modules.schema.generated.d.ts +443 -13
  23. package/dist/generated/plugin-host-modules.schema.generated.js +970 -306
  24. package/dist/index.d.ts +5 -0
  25. package/dist/index.js +5 -0
  26. package/dist/language-service/manager.d.ts +5 -0
  27. package/dist/language-service/manager.js +58 -14
  28. package/dist/lapis-extension.d.ts +1 -1
  29. package/dist/lapis-extension.js +12 -0
  30. package/dist/nostr-signer.d.ts +68 -0
  31. package/dist/nostr-signer.js +49 -0
  32. package/dist/plugin-asset-server.d.ts +1 -0
  33. package/dist/plugin-asset-server.js +3 -0
  34. package/dist/plugin-capability-facade.d.ts +1 -1
  35. package/dist/plugin-capability-facade.js +6 -0
  36. package/dist/plugin-distribution/catalog-media.d.ts +4 -0
  37. package/dist/plugin-distribution/catalog-media.js +40 -0
  38. package/dist/plugin-distribution/download-stats.d.ts +4 -0
  39. package/dist/plugin-distribution/download-stats.js +19 -0
  40. package/dist/plugin-distribution/index.d.ts +4 -0
  41. package/dist/plugin-distribution/index.js +4 -0
  42. package/dist/plugin-distribution/installer.d.ts +3 -0
  43. package/dist/plugin-distribution/installer.js +9 -2
  44. package/dist/plugin-distribution/manager.d.ts +71 -5
  45. package/dist/plugin-distribution/manager.js +376 -37
  46. package/dist/plugin-distribution/nostr-registry-adapter.d.ts +29 -0
  47. package/dist/plugin-distribution/nostr-registry-adapter.js +152 -0
  48. package/dist/plugin-distribution/plugin-bundle-verifier.d.ts +43 -0
  49. package/dist/plugin-distribution/plugin-bundle-verifier.js +64 -1
  50. package/dist/plugin-distribution/plugin-bundle.d.ts +14 -0
  51. package/dist/plugin-distribution/plugin-bundle.js +46 -7
  52. package/dist/plugin-distribution/plugin-markdown.d.ts +6 -0
  53. package/dist/plugin-distribution/plugin-markdown.js +94 -0
  54. package/dist/plugin-distribution/registry-client.d.ts +6 -1
  55. package/dist/plugin-distribution/registry-client.js +32 -10
  56. package/dist/plugin-distribution/registry-source-state.d.ts +15 -0
  57. package/dist/plugin-distribution/registry-source-state.js +42 -0
  58. package/dist/plugin-distribution/schemas.d.ts +2511 -2824
  59. package/dist/plugin-distribution/schemas.js +111 -42
  60. package/dist/plugin-distribution/types.d.ts +116 -5
  61. package/dist/plugin-distribution-host.d.ts +4 -0
  62. package/dist/plugin-distribution-host.js +2 -0
  63. package/dist/plugin-install-progress.js +1 -1
  64. package/dist/plugin-manager.d.ts +3 -0
  65. package/dist/plugin-manager.js +21 -13
  66. package/dist/plugin-settings.d.ts +21 -0
  67. package/dist/plugin-settings.js +41 -0
  68. package/dist/plugin.d.ts +13 -0
  69. package/dist/plugin.js +56 -0
  70. package/dist/registry-schema/catalog-v1.d.ts +4143 -0
  71. package/dist/registry-schema/catalog-v1.js +172 -0
  72. package/dist/registry-schema/catalog-v2.d.ts +5275 -0
  73. package/dist/registry-schema/catalog-v2.js +137 -0
  74. package/dist/registry-schema/index.d.ts +1 -0
  75. package/dist/registry-schema/index.js +1 -0
  76. package/dist/registry-schema/release-notes.d.ts +5 -0
  77. package/dist/registry-schema/release-notes.js +21 -0
  78. package/dist/server-host.d.ts +28 -0
  79. package/dist/server-host.js +17 -0
  80. package/dist/storage/app-database.d.ts +37 -2
  81. package/dist/storage/app-database.js +41 -1
  82. package/dist/storage/browser-coordinated-app-database.d.ts +3 -1
  83. package/dist/storage/browser-coordinated-app-database.js +8 -0
  84. package/dist/storage/browser.d.ts +4 -1
  85. package/dist/storage/browser.js +84 -0
  86. package/dist/storage/desktop-native.d.ts +4 -2
  87. package/dist/storage/desktop-native.js +90 -1
  88. package/dist/storage/fs.d.ts +17 -0
  89. package/dist/storage/index.d.ts +1 -0
  90. package/dist/storage/index.js +1 -0
  91. package/dist/storage/memory-vault-adapter.d.ts +3 -1
  92. package/dist/storage/memory-vault-adapter.js +60 -0
  93. package/dist/storage/remote-app-database.d.ts +19 -0
  94. package/dist/storage/remote-app-database.js +130 -0
  95. package/dist/storage/task-projection.d.ts +2 -1
  96. package/dist/storage/task-projection.js +7 -2
  97. package/dist/storage/turso-app-database.d.ts +3 -1
  98. package/dist/storage/turso-app-database.js +45 -0
  99. package/dist/storage/vault-session.js +11 -0
  100. package/dist/storage/vault-state.d.ts +1 -1
  101. package/dist/storage/vault.svelte.d.ts +3 -1
  102. package/dist/storage/vault.svelte.js +76 -3
  103. package/dist/sync-host-security.d.ts +285 -0
  104. package/dist/sync-host-security.js +2237 -0
  105. package/dist/sync-host.d.ts +179 -0
  106. package/dist/sync-host.js +769 -0
  107. package/dist/workspace.svelte.d.ts +1 -1
  108. package/dist/workspace.svelte.js +1 -1
  109. 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
- this.documents.delete(uri);
100
- this.deleteCachedCodeActions(uri);
101
- const collection = this.diagnosticsBinding?.collection;
102
- if (collection && !collection.disposed) {
103
- collection.delete(resourceForUri(uri));
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 || this.providerFailures.has(provider.metadata.id)) {
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;
@@ -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;
@@ -118,8 +118,12 @@ export class VerifiedPluginInstaller {
118
118
  }
119
119
  return verifyPluginBundlePayload(verifierOptions);
120
120
  }
121
+ /** Install payload bytes already verified by a trusted registry adapter. */
122
+ async installVerifiedPayload(payload, options) {
123
+ return this.installVerifiedBundlePayload(payload, options);
124
+ }
121
125
  async installVerifiedBundlePayload(payload, options) {
122
- const { releaseEnvelope, releaseManifest, releaseManifestSha256, files } = payload;
126
+ const { releaseEnvelope, releaseManifest, releaseManifestSha256, files, nostrProof, } = payload;
123
127
  if (options.compatibility) {
124
128
  const compatibility = checkReleaseManifestCompatibility(releaseManifest, options.compatibility);
125
129
  if (!compatibility.compatible) {
@@ -193,7 +197,10 @@ export class VerifiedPluginInstaller {
193
197
  ...(runtimeValidation.warnings.length
194
198
  ? { runtimeWarnings: runtimeValidation.warnings }
195
199
  : {}),
196
- signature: releaseEnvelope.signatures[0],
200
+ ...(releaseEnvelope.signatures[0]
201
+ ? { signature: releaseEnvelope.signatures[0] }
202
+ : {}),
203
+ ...(nostrProof ? { nostrProof } : {}),
197
204
  restartRequired: Boolean(this.options.pluginManager),
198
205
  };
199
206
  await this.stateStore.upsert(record);