@lapis-notes/api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE.md +4 -0
- package/README.md +43 -0
- package/dist/__tests__/data-adapter-conformance.js +205 -0
- package/dist/__tests__/lapis-runtime-manifest.js +32 -0
- package/dist/__tests__/svelte-internal-client.d.ts +3 -0
- package/dist/agent-result-views.d.ts +52 -0
- package/dist/agent-result-views.js +89 -0
- package/dist/agent-skills.d.ts +99 -0
- package/dist/agent-skills.js +182 -0
- package/dist/agent-tools.d.ts +92 -0
- package/dist/agent-tools.js +139 -0
- package/dist/app-database-host.d.ts +4 -0
- package/dist/app-database-host.js +4 -0
- package/dist/app-shell-plugin-persistence.d.ts +15 -0
- package/dist/app-shell-plugin-persistence.js +34 -0
- package/dist/app-url.d.ts +54 -0
- package/dist/app-url.js +269 -0
- package/dist/application-compatibility.d.ts +31 -0
- package/dist/application-compatibility.js +64 -0
- package/dist/application-state.svelte.d.ts +14 -0
- package/dist/application-state.svelte.js +30 -0
- package/dist/assets/lapis-logo.svg +471 -0
- package/dist/bases.d.ts +281 -0
- package/dist/bases.js +475 -0
- package/dist/cache.svelte.d.ts +569 -0
- package/dist/cache.svelte.js +1433 -0
- package/dist/command.svelte.d.ts +337 -0
- package/dist/command.svelte.js +571 -0
- package/dist/compat.d.ts +29 -0
- package/dist/compat.js +30 -0
- package/dist/components/configuration/configuration.css +199 -0
- package/dist/components/configuration/date-setting.svelte +71 -0
- package/dist/components/configuration/date-setting.svelte.d.ts +8 -0
- package/dist/components/configuration/list-setting.svelte +128 -0
- package/dist/components/configuration/list-setting.svelte.d.ts +13 -0
- package/dist/components/configuration/object-array-row-grip.svelte +78 -0
- package/dist/components/configuration/object-array-row-grip.svelte.d.ts +9 -0
- package/dist/components/configuration/object-array-row.svelte +163 -0
- package/dist/components/configuration/object-array-row.svelte.d.ts +19 -0
- package/dist/components/configuration/object-array-setting.svelte +196 -0
- package/dist/components/configuration/object-array-setting.svelte.d.ts +15 -0
- package/dist/components/configuration/object-array-table-dnd.d.ts +8 -0
- package/dist/components/configuration/object-array-table-dnd.js +10 -0
- package/dist/components/configuration/object-array-utils.d.ts +11 -0
- package/dist/components/configuration/object-array-utils.js +103 -0
- package/dist/components/configuration/object-grid-setting.svelte +79 -0
- package/dist/components/configuration/object-grid-setting.svelte.d.ts +10 -0
- package/dist/components/configuration/object-map-row.svelte +147 -0
- package/dist/components/configuration/object-map-row.svelte.d.ts +15 -0
- package/dist/components/configuration/object-map-setting.svelte +137 -0
- package/dist/components/configuration/object-map-setting.svelte.d.ts +14 -0
- package/dist/components/configuration/object-map-types.d.ts +5 -0
- package/dist/components/configuration/object-map-types.js +1 -0
- package/dist/components/configuration/options-combobox-setting.svelte +129 -0
- package/dist/components/configuration/options-combobox-setting.svelte.d.ts +12 -0
- package/dist/components/copy-button/button.svelte +93 -0
- package/dist/components/copy-button/button.svelte.d.ts +13 -0
- package/dist/components/copy-button/index.d.ts +2 -0
- package/dist/components/copy-button/index.js +2 -0
- package/dist/components/editor/editor.css +565 -0
- package/dist/components/editor/editor.d.ts +33 -0
- package/dist/components/editor/editor.js +132 -0
- package/dist/components/editor/editor.svelte +176 -0
- package/dist/components/editor/editor.svelte.d.ts +15 -0
- package/dist/components/editor/embedded-editor-surface.d.ts +12 -0
- package/dist/components/editor/embedded-editor-surface.js +22 -0
- package/dist/components/editor/embedded-editor-surface.svelte +115 -0
- package/dist/components/editor/embedded-editor-surface.svelte.d.ts +17 -0
- package/dist/components/editor/extensions/autocomplete/index.d.ts +2 -0
- package/dist/components/editor/extensions/autocomplete/index.js +2 -0
- package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.d.ts +16 -0
- package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.js +34 -0
- package/dist/components/editor/extensions/autocomplete/vault-completion-sources.d.ts +27 -0
- package/dist/components/editor/extensions/autocomplete/vault-completion-sources.js +55 -0
- package/dist/components/editor/extensions/class-highlighter/index.d.ts +1 -0
- package/dist/components/editor/extensions/class-highlighter/index.js +47 -0
- package/dist/components/editor/extensions/fold-gutter/index.d.ts +8 -0
- package/dist/components/editor/extensions/fold-gutter/index.js +40 -0
- package/dist/components/editor/extensions/gutter-line-style-sync/index.d.ts +2 -0
- package/dist/components/editor/extensions/gutter-line-style-sync/index.js +118 -0
- package/dist/components/editor/extensions/lint/index.d.ts +4 -0
- package/dist/components/editor/extensions/lint/index.js +4 -0
- package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.d.ts +26 -0
- package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.js +101 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.d.ts +8 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.js +7 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.d.ts +20 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.js +47 -0
- package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.d.ts +9 -0
- package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.js +373 -0
- package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.d.ts +31 -0
- package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.js +191 -0
- package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte +176 -0
- package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte.d.ts +15 -0
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.d.ts +11 -0
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.js +21 -0
- package/dist/components/editor/extensions/lint/mount-lint-tooltip.d.ts +16 -0
- package/dist/components/editor/extensions/lint/mount-lint-tooltip.js +60 -0
- package/dist/components/editor/extensions/search/dom.d.ts +2 -0
- package/dist/components/editor/extensions/search/dom.js +6 -0
- package/dist/components/editor/extensions/search/index.d.ts +35 -0
- package/dist/components/editor/extensions/search/index.js +44 -0
- package/dist/components/editor/extensions/search/search.svelte +335 -0
- package/dist/components/editor/extensions/search/search.svelte.d.ts +8 -0
- package/dist/components/editor/index.d.ts +9 -0
- package/dist/components/editor/index.js +9 -0
- package/dist/components/editor/language-service/dom.d.ts +1 -0
- package/dist/components/editor/language-service/dom.js +6 -0
- package/dist/components/editor/language-service/index.d.ts +28 -0
- package/dist/components/editor/language-service/index.js +342 -0
- package/dist/components/empty-view/empty-view.css +39 -0
- package/dist/components/empty-view/empty-view.svelte +26 -0
- package/dist/components/empty-view/empty-view.svelte.d.ts +9 -0
- package/dist/components/empty-view/index.d.ts +2 -0
- package/dist/components/empty-view/index.js +2 -0
- package/dist/components/icon/icon-list.css +74 -0
- package/dist/components/icon/icon-list.svelte +213 -0
- package/dist/components/icon/icon-list.svelte.d.ts +11 -0
- package/dist/components/icon/icon.svelte +15 -0
- package/dist/components/icon/icon.svelte.d.ts +7 -0
- package/dist/components/icon/index.d.ts +3 -0
- package/dist/components/icon/index.js +3 -0
- package/dist/components/menu/context-menu.svelte +89 -0
- package/dist/components/menu/context-menu.svelte.d.ts +11 -0
- package/dist/components/menu/drawer-menu.svelte +83 -0
- package/dist/components/menu/drawer-menu.svelte.d.ts +8 -0
- package/dist/components/menu/dropdown-menu.svelte +89 -0
- package/dist/components/menu/dropdown-menu.svelte.d.ts +11 -0
- package/dist/components/menu/index.d.ts +6 -0
- package/dist/components/menu/index.js +6 -0
- package/dist/components/menu/menu.css +139 -0
- package/dist/components/menu/menu.svelte +37 -0
- package/dist/components/menu/menu.svelte.d.ts +11 -0
- package/dist/components/menu/mobile-menu-query.svelte.d.ts +3 -0
- package/dist/components/menu/mobile-menu-query.svelte.js +21 -0
- package/dist/components/menu/popover-menu.svelte +90 -0
- package/dist/components/menu/popover-menu.svelte.d.ts +10 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.js +2 -0
- package/dist/components/select/select.svelte +72 -0
- package/dist/components/select/select.svelte.d.ts +25 -0
- package/dist/configuration-option-source-providers.d.ts +17 -0
- package/dist/configuration-option-source-providers.js +59 -0
- package/dist/configuration-option-source-registry.d.ts +59 -0
- package/dist/configuration-option-source-registry.js +123 -0
- package/dist/configuration.svelte.d.ts +258 -0
- package/dist/configuration.svelte.js +887 -0
- package/dist/context-keys.svelte.d.ts +42 -0
- package/dist/context-keys.svelte.js +278 -0
- package/dist/context.svelte.d.ts +247 -0
- package/dist/context.svelte.js +540 -0
- package/dist/daily-document-provider.d.ts +38 -0
- package/dist/daily-document-provider.js +176 -0
- package/dist/design-core-button-types.d.ts +7 -0
- package/dist/design-core-button-types.js +1 -0
- package/dist/diagnostics.d.ts +93 -0
- package/dist/diagnostics.js +84 -0
- package/dist/dialog-portal.d.ts +3 -0
- package/dist/dialog-portal.js +3 -0
- package/dist/docs-api.d.ts +44 -0
- package/dist/docs-api.js +29 -0
- package/dist/editor-view-registry.d.ts +35 -0
- package/dist/editor-view-registry.js +112 -0
- package/dist/editor.svelte.d.ts +126 -0
- package/dist/editor.svelte.js +454 -0
- package/dist/embed-registry.d.ts +15 -0
- package/dist/embed-registry.js +16 -0
- package/dist/enhance.d.ts +111 -0
- package/dist/enhance.js +309 -0
- package/dist/events.d.ts +28 -0
- package/dist/events.js +49 -0
- package/dist/file-manager.d.ts +90 -0
- package/dist/file-manager.js +289 -0
- package/dist/file-tools/app-tools.d.ts +3 -0
- package/dist/file-tools/app-tools.js +242 -0
- package/dist/file-tools/index.d.ts +3 -0
- package/dist/file-tools/index.js +3 -0
- package/dist/file-tools/paths.d.ts +14 -0
- package/dist/file-tools/paths.js +33 -0
- package/dist/file-tools/vault-operations.d.ts +3 -0
- package/dist/file-tools/vault-operations.js +60 -0
- package/dist/generated/plugin-host-modules.schema.generated.d.ts +371 -0
- package/dist/generated/plugin-host-modules.schema.generated.js +374 -0
- package/dist/glob.d.ts +15 -0
- package/dist/glob.js +68 -0
- package/dist/history.svelte.d.ts +24 -0
- package/dist/history.svelte.js +74 -0
- package/dist/hooks/createVirtualizer.svelte.d.ts +7 -0
- package/dist/hooks/createVirtualizer.svelte.js +70 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/is-mobile.svelte.d.ts +5 -0
- package/dist/hooks/is-mobile.svelte.js +23 -0
- package/dist/hooks/mountComponent.svelte.d.ts +7 -0
- package/dist/hooks/mountComponent.svelte.js +16 -0
- package/dist/hooks/use-text-highlight.d.ts +17 -0
- package/dist/hooks/use-text-highlight.js +82 -0
- package/dist/hooks/useBoundingRect.svelte.d.ts +11 -0
- package/dist/hooks/useBoundingRect.svelte.js +46 -0
- package/dist/icons/index.d.ts +57 -0
- package/dist/icons/index.js +288 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +87 -0
- package/dist/language-service/index.d.ts +9 -0
- package/dist/language-service/index.js +7 -0
- package/dist/language-service/manager.d.ts +47 -0
- package/dist/language-service/manager.js +404 -0
- package/dist/language-service/types.d.ts +92 -0
- package/dist/language-service/types.js +1 -0
- package/dist/language-service/virtual-document.d.ts +11 -0
- package/dist/language-service/virtual-document.js +45 -0
- package/dist/language-service/worker-protocol.d.ts +63 -0
- package/dist/language-service/worker-protocol.js +1 -0
- package/dist/language-service/worker-provider.d.ts +9 -0
- package/dist/language-service/worker-provider.js +38 -0
- package/dist/lapis-extension.d.ts +244 -0
- package/dist/lapis-extension.js +591 -0
- package/dist/links.d.ts +34 -0
- package/dist/links.js +191 -0
- package/dist/loaders.d.ts +9 -0
- package/dist/loaders.js +59 -0
- package/dist/localization-manager.svelte.d.ts +56 -0
- package/dist/localization-manager.svelte.js +157 -0
- package/dist/logging.d.ts +172 -0
- package/dist/logging.js +352 -0
- package/dist/markdown-extension-registry.d.ts +31 -0
- package/dist/markdown-extension-registry.js +31 -0
- package/dist/markdown-file-surface-registry.d.ts +25 -0
- package/dist/markdown-file-surface-registry.js +23 -0
- package/dist/markdown.d.ts +205 -0
- package/dist/markdown.js +147 -0
- package/dist/menu-utils.d.ts +1 -0
- package/dist/menu-utils.js +47 -0
- package/dist/menu.svelte.d.ts +121 -0
- package/dist/menu.svelte.js +295 -0
- package/dist/metadata-value.d.ts +10 -0
- package/dist/metadata-value.js +173 -0
- package/dist/metadata.svelte.d.ts +106 -0
- package/dist/metadata.svelte.js +608 -0
- package/dist/notifications.d.ts +209 -0
- package/dist/notifications.js +338 -0
- package/dist/open-file.d.ts +23 -0
- package/dist/open-file.js +102 -0
- package/dist/platform.d.ts +86 -0
- package/dist/platform.js +86 -0
- package/dist/plugin-asset-server.d.ts +60 -0
- package/dist/plugin-asset-server.js +222 -0
- package/dist/plugin-assets.d.ts +4 -0
- package/dist/plugin-assets.js +3 -0
- package/dist/plugin-capability-facade.d.ts +104 -0
- package/dist/plugin-capability-facade.js +61 -0
- package/dist/plugin-dependency-resolver.d.ts +36 -0
- package/dist/plugin-dependency-resolver.js +54 -0
- package/dist/plugin-dependency-scanner.d.ts +9 -0
- package/dist/plugin-dependency-scanner.js +274 -0
- package/dist/plugin-distribution/canonical-json.d.ts +1 -0
- package/dist/plugin-distribution/canonical-json.js +40 -0
- package/dist/plugin-distribution/compatibility.d.ts +5 -0
- package/dist/plugin-distribution/compatibility.js +38 -0
- package/dist/plugin-distribution/errors.d.ts +11 -0
- package/dist/plugin-distribution/errors.js +13 -0
- package/dist/plugin-distribution/hashes.d.ts +2 -0
- package/dist/plugin-distribution/hashes.js +17 -0
- package/dist/plugin-distribution/index.d.ts +18 -0
- package/dist/plugin-distribution/index.js +18 -0
- package/dist/plugin-distribution/installed-plugin-state.d.ts +20 -0
- package/dist/plugin-distribution/installed-plugin-state.js +73 -0
- package/dist/plugin-distribution/installer.d.ts +89 -0
- package/dist/plugin-distribution/installer.js +395 -0
- package/dist/plugin-distribution/manager.d.ts +128 -0
- package/dist/plugin-distribution/manager.js +358 -0
- package/dist/plugin-distribution/path-safety.d.ts +2 -0
- package/dist/plugin-distribution/path-safety.js +20 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.d.ts +28 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.js +96 -0
- package/dist/plugin-distribution/plugin-bundle-worker.d.ts +34 -0
- package/dist/plugin-distribution/plugin-bundle-worker.js +99 -0
- package/dist/plugin-distribution/plugin-bundle.d.ts +16 -0
- package/dist/plugin-distribution/plugin-bundle.js +244 -0
- package/dist/plugin-distribution/plugin-bundle.worker.d.ts +1 -0
- package/dist/plugin-distribution/plugin-bundle.worker.js +38 -0
- package/dist/plugin-distribution/registry-cache.d.ts +26 -0
- package/dist/plugin-distribution/registry-cache.js +85 -0
- package/dist/plugin-distribution/registry-client.d.ts +36 -0
- package/dist/plugin-distribution/registry-client.js +115 -0
- package/dist/plugin-distribution/reserved-ids.d.ts +4 -0
- package/dist/plugin-distribution/reserved-ids.js +10 -0
- package/dist/plugin-distribution/runtime-validation.d.ts +14 -0
- package/dist/plugin-distribution/runtime-validation.js +388 -0
- package/dist/plugin-distribution/schemas.d.ts +4895 -0
- package/dist/plugin-distribution/schemas.js +299 -0
- package/dist/plugin-distribution/signing.d.ts +5 -0
- package/dist/plugin-distribution/signing.js +38 -0
- package/dist/plugin-distribution/types.d.ts +217 -0
- package/dist/plugin-distribution/types.js +1 -0
- package/dist/plugin-install-progress.d.ts +15 -0
- package/dist/plugin-install-progress.js +86 -0
- package/dist/plugin-manager.d.ts +306 -0
- package/dist/plugin-manager.js +2848 -0
- package/dist/plugin-runtime-entry.d.ts +19 -0
- package/dist/plugin-runtime-entry.js +135 -0
- package/dist/plugin.d.ts +350 -0
- package/dist/plugin.js +754 -0
- package/dist/popover.d.ts +20 -0
- package/dist/popover.js +32 -0
- package/dist/promise-worker.d.ts +22 -0
- package/dist/promise-worker.js +65 -0
- package/dist/prompt-confirm.d.ts +9 -0
- package/dist/prompt-confirm.js +33 -0
- package/dist/search-document-provider.d.ts +45 -0
- package/dist/search-document-provider.js +54 -0
- package/dist/search-query/index.d.ts +78 -0
- package/dist/search-query/index.js +426 -0
- package/dist/search-query/parser.d.ts +3 -0
- package/dist/search-query/parser.js +16 -0
- package/dist/search-query/parser.terms.js +26 -0
- package/dist/search-query/search-query.grammar +128 -0
- package/dist/search.d.ts +19 -0
- package/dist/search.js +104 -0
- package/dist/setting-section-layout.d.ts +19 -0
- package/dist/setting-section-layout.js +194 -0
- package/dist/settings.svelte.d.ts +474 -0
- package/dist/settings.svelte.js +1421 -0
- package/dist/source-text-file-view.d.ts +25 -0
- package/dist/source-text-file-view.js +78 -0
- package/dist/status-bar.svelte.d.ts +29 -0
- package/dist/status-bar.svelte.js +72 -0
- package/dist/storage/app-database.d.ts +860 -0
- package/dist/storage/app-database.js +2372 -0
- package/dist/storage/blob-store.d.ts +12 -0
- package/dist/storage/blob-store.js +1 -0
- package/dist/storage/browser-app-database-coordination.d.ts +35 -0
- package/dist/storage/browser-app-database-coordination.js +207 -0
- package/dist/storage/browser-coordinated-app-database.d.ts +109 -0
- package/dist/storage/browser-coordinated-app-database.js +704 -0
- package/dist/storage/browser.d.ts +153 -0
- package/dist/storage/browser.js +794 -0
- package/dist/storage/desktop-native.d.ts +184 -0
- package/dist/storage/desktop-native.js +415 -0
- package/dist/storage/fs.d.ts +188 -0
- package/dist/storage/fs.js +307 -0
- package/dist/storage/index-projection.d.ts +236 -0
- package/dist/storage/index-projection.js +402 -0
- package/dist/storage/index.d.ts +17 -0
- package/dist/storage/index.js +17 -0
- package/dist/storage/keyval.d.ts +94 -0
- package/dist/storage/keyval.js +199 -0
- package/dist/storage/memory-vault-adapter.d.ts +44 -0
- package/dist/storage/memory-vault-adapter.js +351 -0
- package/dist/storage/native-vault-bootstrap-kv.d.ts +22 -0
- package/dist/storage/native-vault-bootstrap-kv.js +96 -0
- package/dist/storage/path.d.ts +22 -0
- package/dist/storage/path.js +110 -0
- package/dist/storage/search-embedding-provider.d.ts +75 -0
- package/dist/storage/search-embedding-provider.js +446 -0
- package/dist/storage/search-embedding-worker.d.ts +1 -0
- package/dist/storage/search-embedding-worker.js +63 -0
- package/dist/storage/search-query-evaluator.d.ts +14 -0
- package/dist/storage/search-query-evaluator.js +678 -0
- package/dist/storage/task-projection.d.ts +152 -0
- package/dist/storage/task-projection.js +154 -0
- package/dist/storage/turso-app-database.d.ts +150 -0
- package/dist/storage/turso-app-database.js +2671 -0
- package/dist/storage/vault-session.d.ts +37 -0
- package/dist/storage/vault-session.js +196 -0
- package/dist/storage/vault-state.d.ts +74 -0
- package/dist/storage/vault-state.js +181 -0
- package/dist/storage/vault.svelte.d.ts +257 -0
- package/dist/storage/vault.svelte.js +661 -0
- package/dist/suggest.d.ts +193 -0
- package/dist/suggest.js +391 -0
- package/dist/tasks.d.ts +7 -0
- package/dist/tasks.js +18 -0
- package/dist/telemetry.d.ts +116 -0
- package/dist/telemetry.js +103 -0
- package/dist/utils.d.ts +55 -0
- package/dist/utils.js +314 -0
- package/dist/vault-api.d.ts +5 -0
- package/dist/vault-api.js +3 -0
- package/dist/vault-maintenance-commands.d.ts +5 -0
- package/dist/vault-maintenance-commands.js +32 -0
- package/dist/view.svelte.d.ts +215 -0
- package/dist/view.svelte.js +381 -0
- package/dist/vite-env.d.ts +2 -0
- package/dist/workspace-host-internal.d.ts +7 -0
- package/dist/workspace-host-internal.js +7 -0
- package/dist/workspace-host.d.ts +10 -0
- package/dist/workspace-host.js +14 -0
- package/dist/workspace-layout-commands.d.ts +3 -0
- package/dist/workspace-layout-commands.js +72 -0
- package/dist/workspace-layout-normalizer.d.ts +92 -0
- package/dist/workspace-layout-normalizer.js +301 -0
- package/dist/workspace-layouts.d.ts +7 -0
- package/dist/workspace-layouts.js +23 -0
- package/dist/workspace-trust.d.ts +32 -0
- package/dist/workspace-trust.js +72 -0
- package/dist/workspace.svelte.d.ts +796 -0
- package/dist/workspace.svelte.js +4380 -0
- package/package.json +240 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { EventDispatcher } from "../events";
|
|
2
|
+
import { hasNativeDesktopCapability } from "../storage/desktop-native";
|
|
3
|
+
import { checkCatalogEntryCompatibility, checkCatalogReleaseCompatibility, compareVersions, } from "./compatibility";
|
|
4
|
+
import { PluginDistributionError } from "./errors";
|
|
5
|
+
import { InstalledPluginStateStore } from "./installed-plugin-state";
|
|
6
|
+
import { VerifiedPluginInstaller, } from "./installer";
|
|
7
|
+
import { PluginRegistryCache } from "./registry-cache";
|
|
8
|
+
import { PluginRegistryClient } from "./registry-client";
|
|
9
|
+
export const DEFAULT_OFFICIAL_PLUGIN_REGISTRY_SOURCE = {
|
|
10
|
+
id: "lapis-official",
|
|
11
|
+
name: "Lapis Official Plugins",
|
|
12
|
+
url: "https://registry.lapis.md/v1/index.json",
|
|
13
|
+
trustTier: "official",
|
|
14
|
+
enabled: true,
|
|
15
|
+
builtin: true,
|
|
16
|
+
};
|
|
17
|
+
export const DEFAULT_PLUGIN_REGISTRY_SOURCES = [
|
|
18
|
+
DEFAULT_OFFICIAL_PLUGIN_REGISTRY_SOURCE,
|
|
19
|
+
];
|
|
20
|
+
export const EMBEDDED_PLUGIN_REGISTRY_KEYS = [
|
|
21
|
+
{
|
|
22
|
+
keyId: "lapis-registry-2026-06",
|
|
23
|
+
alg: "ed25519",
|
|
24
|
+
publicKey: "/MmGQHJ5rOUECVsU5Ee/PLTouW8V3TyxGBnPKJT87JA=",
|
|
25
|
+
trustTier: "official",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
keyId: "lapis-plugin-release-2026-06",
|
|
29
|
+
alg: "ed25519",
|
|
30
|
+
publicKey: "bMPnlO7b+Xtthfk5wGUQAWWUV7NuIAhED4kX2EGzJqc=",
|
|
31
|
+
trustTier: "official",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
export class DefaultPluginDistributionManager extends EventDispatcher {
|
|
35
|
+
options;
|
|
36
|
+
catalog = null;
|
|
37
|
+
registryClient;
|
|
38
|
+
stateStore;
|
|
39
|
+
installer;
|
|
40
|
+
registries;
|
|
41
|
+
trustedKeys;
|
|
42
|
+
installedIds = new Set();
|
|
43
|
+
revocations = null;
|
|
44
|
+
constructor(options) {
|
|
45
|
+
super();
|
|
46
|
+
this.options = options;
|
|
47
|
+
this.registries = options.registries ?? DEFAULT_PLUGIN_REGISTRY_SOURCES;
|
|
48
|
+
this.trustedKeys = options.trustedKeys ?? EMBEDDED_PLUGIN_REGISTRY_KEYS;
|
|
49
|
+
this.stateStore = new InstalledPluginStateStore(options.adapter);
|
|
50
|
+
this.registryClient = new PluginRegistryClient({
|
|
51
|
+
trustedKeys: this.trustedKeys,
|
|
52
|
+
fetch: options.fetch,
|
|
53
|
+
cache: new PluginRegistryCache({ adapter: options.adapter }),
|
|
54
|
+
});
|
|
55
|
+
this.installer = new VerifiedPluginInstaller({
|
|
56
|
+
adapter: options.adapter,
|
|
57
|
+
trustedKeys: this.trustedKeys,
|
|
58
|
+
fetch: options.fetch,
|
|
59
|
+
stateStore: this.stateStore,
|
|
60
|
+
pluginManager: options.pluginManager,
|
|
61
|
+
bundleVerification: options.bundleVerification ??
|
|
62
|
+
(this.platform === "web" ? "auto" : "main-thread"),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async refreshCatalog(_options = {}) {
|
|
66
|
+
this.emitProgress("*", "fetching-catalog");
|
|
67
|
+
const source = this.registries.find((registry) => registry.enabled);
|
|
68
|
+
if (!source) {
|
|
69
|
+
this.catalog = {
|
|
70
|
+
schemaVersion: 1,
|
|
71
|
+
generatedAt: new Date(0).toISOString(),
|
|
72
|
+
plugins: [],
|
|
73
|
+
};
|
|
74
|
+
return this.catalog;
|
|
75
|
+
}
|
|
76
|
+
const refreshed = await this.registryClient.refresh(source);
|
|
77
|
+
this.catalog = refreshed.index;
|
|
78
|
+
this.revocations = refreshed.revocations;
|
|
79
|
+
await this.applyRevocationsToInstalled();
|
|
80
|
+
return refreshed.index;
|
|
81
|
+
}
|
|
82
|
+
search(query = {}) {
|
|
83
|
+
const text = query.text?.trim().toLowerCase();
|
|
84
|
+
return (this.catalog?.plugins ?? []).filter((entry) => {
|
|
85
|
+
if (text) {
|
|
86
|
+
const haystack = `${entry.name} ${entry.id} ${entry.description} ${entry.author}`.toLowerCase();
|
|
87
|
+
if (!haystack.includes(text))
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if (query.category && !entry.categories.includes(query.category)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if (query.channel &&
|
|
94
|
+
query.channel !== "all" &&
|
|
95
|
+
entry.channel !== query.channel) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (typeof query.installed === "boolean" &&
|
|
99
|
+
this.installedIds.has(entry.id) !== query.installed) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (query.compatibleOnly) {
|
|
103
|
+
const compatibility = checkCatalogEntryCompatibility(entry, this.compatibilityInput(false));
|
|
104
|
+
if (!compatibility.compatible)
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
getCatalogEntry(pluginId) {
|
|
111
|
+
return this.catalog?.plugins.find((entry) => entry.id === pluginId);
|
|
112
|
+
}
|
|
113
|
+
async getPluginDetail(pluginId) {
|
|
114
|
+
const entry = this.getCatalogEntry(pluginId);
|
|
115
|
+
if (!entry)
|
|
116
|
+
return null;
|
|
117
|
+
const result = await this.registryClient.getDetail(entry);
|
|
118
|
+
return result.detail;
|
|
119
|
+
}
|
|
120
|
+
async install(pluginId, options = {}) {
|
|
121
|
+
try {
|
|
122
|
+
this.emitProgress(pluginId, "resolving", "Resolving plugin");
|
|
123
|
+
const entry = this.requireEntry(pluginId);
|
|
124
|
+
if (options.requireOfficial && entry.channel !== "official") {
|
|
125
|
+
throw new PluginDistributionError("metadata-invalid", `Plugin ${pluginId} is not an official registry entry.`);
|
|
126
|
+
}
|
|
127
|
+
const entryCompatibility = checkCatalogEntryCompatibility(entry, this.compatibilityInput(false));
|
|
128
|
+
if (!entryCompatibility.compatible) {
|
|
129
|
+
throw new PluginDistributionError("compatibility-failed", `Plugin ${pluginId} is not compatible: ${entryCompatibility.reasons.join(", ")}`, { details: { reasons: entryCompatibility.reasons } });
|
|
130
|
+
}
|
|
131
|
+
this.emitProgress(pluginId, "fetching-release", "Fetching release metadata");
|
|
132
|
+
const detail = await this.getPluginDetail(pluginId);
|
|
133
|
+
if (!detail) {
|
|
134
|
+
throw new PluginDistributionError("metadata-invalid", `Plugin ${pluginId} is missing registry detail metadata.`);
|
|
135
|
+
}
|
|
136
|
+
const release = detail.versions[options.version ?? detail.latestVersion];
|
|
137
|
+
if (!release) {
|
|
138
|
+
throw new PluginDistributionError("metadata-invalid", `Plugin ${pluginId} has no requested release.`);
|
|
139
|
+
}
|
|
140
|
+
const releaseCompatibility = checkCatalogReleaseCompatibility(release, this.compatibilityInput(false));
|
|
141
|
+
if (!releaseCompatibility.compatible) {
|
|
142
|
+
throw new PluginDistributionError("compatibility-failed", `Plugin ${pluginId} release is not compatible: ${releaseCompatibility.reasons.join(", ")}`, { details: { reasons: releaseCompatibility.reasons } });
|
|
143
|
+
}
|
|
144
|
+
this.emitProgress(pluginId, "verifying-signature", "Verifying release signature");
|
|
145
|
+
const installOptions = {
|
|
146
|
+
entry,
|
|
147
|
+
detail,
|
|
148
|
+
version: options.version,
|
|
149
|
+
enable: options.enable,
|
|
150
|
+
registryId: options.registryId ?? this.sourceForEntry(entry)?.id,
|
|
151
|
+
registryUrl: this.sourceForEntry(entry)?.url,
|
|
152
|
+
provenance: entry.channel,
|
|
153
|
+
compatibility: this.compatibilityInput(await this.isWorkspaceTrusted()),
|
|
154
|
+
signal: options.signal,
|
|
155
|
+
onProgress: ({ phase, message, ...details }) => {
|
|
156
|
+
this.emitProgress(pluginId, phase, message, details);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
const record = await this.installer.install(installOptions);
|
|
160
|
+
this.installedIds.add(pluginId);
|
|
161
|
+
this.emitProgress(pluginId, "complete", "Plugin installed");
|
|
162
|
+
return record;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
this.emitProgress(pluginId, "failed", error instanceof Error ? error.message : String(error));
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async installBundle(bundle, options = {}) {
|
|
170
|
+
let progressPluginId = "*";
|
|
171
|
+
try {
|
|
172
|
+
this.emitProgress(progressPluginId, "verifying-signature", "Verifying plugin bundle");
|
|
173
|
+
const installOptions = {
|
|
174
|
+
bundle,
|
|
175
|
+
enable: options.enable,
|
|
176
|
+
provenance: "official",
|
|
177
|
+
compatibility: this.compatibilityInput(await this.isWorkspaceTrusted()),
|
|
178
|
+
signal: options.signal,
|
|
179
|
+
onProgress: ({ phase, message, ...details }) => {
|
|
180
|
+
this.emitProgress(progressPluginId, phase, message, details);
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
const record = await this.installer.installBundle(installOptions);
|
|
184
|
+
progressPluginId = record.pluginId;
|
|
185
|
+
this.installedIds.add(record.pluginId);
|
|
186
|
+
this.emitProgress(record.pluginId, "complete", "Plugin installed");
|
|
187
|
+
return record;
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
this.emitProgress(progressPluginId, "failed", error instanceof Error ? error.message : String(error));
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async update(pluginId, version, options = {}) {
|
|
195
|
+
const installed = await this.getInstalled(pluginId);
|
|
196
|
+
const entry = this.getCatalogEntry(pluginId);
|
|
197
|
+
if (!installed || !entry)
|
|
198
|
+
return null;
|
|
199
|
+
const targetVersion = version ?? installed.revoked?.replacementVersion ?? entry.latestVersion;
|
|
200
|
+
if (compareVersions(targetVersion, installed.installedVersion) <= 0) {
|
|
201
|
+
return installed;
|
|
202
|
+
}
|
|
203
|
+
const wasEnabled = Boolean(this.options.pluginManager?.isPluginEnabled?.(pluginId));
|
|
204
|
+
const updated = await this.install(pluginId, {
|
|
205
|
+
version: targetVersion,
|
|
206
|
+
enable: wasEnabled,
|
|
207
|
+
signal: options.signal,
|
|
208
|
+
});
|
|
209
|
+
if (updated.installedAt !== installed.installedAt) {
|
|
210
|
+
const merged = { ...updated, installedAt: installed.installedAt };
|
|
211
|
+
await this.stateStore.upsert(merged);
|
|
212
|
+
return merged;
|
|
213
|
+
}
|
|
214
|
+
return updated;
|
|
215
|
+
}
|
|
216
|
+
async uninstall(pluginId, options = {}) {
|
|
217
|
+
await this.installer.uninstall(pluginId, options);
|
|
218
|
+
this.installedIds.delete(pluginId);
|
|
219
|
+
}
|
|
220
|
+
async listInstalled() {
|
|
221
|
+
const installed = await this.stateStore.list();
|
|
222
|
+
this.installedIds = new Set(installed.map((record) => record.pluginId));
|
|
223
|
+
return installed;
|
|
224
|
+
}
|
|
225
|
+
getInstalled(pluginId) {
|
|
226
|
+
return this.stateStore.get(pluginId);
|
|
227
|
+
}
|
|
228
|
+
async listUpdates() {
|
|
229
|
+
const installed = await this.listInstalled();
|
|
230
|
+
const updates = [];
|
|
231
|
+
for (const record of installed) {
|
|
232
|
+
if (record.provenance !== "official")
|
|
233
|
+
continue;
|
|
234
|
+
const entry = this.getCatalogEntry(record.pluginId);
|
|
235
|
+
const revoked = this.revocationFor(record) ?? record.revoked;
|
|
236
|
+
if (!entry) {
|
|
237
|
+
if (revoked) {
|
|
238
|
+
updates.push({
|
|
239
|
+
id: record.pluginId,
|
|
240
|
+
name: record.pluginId,
|
|
241
|
+
currentVersion: record.installedVersion,
|
|
242
|
+
latestVersion: record.installedVersion,
|
|
243
|
+
targetVersion: record.installedVersion,
|
|
244
|
+
provenance: record.provenance,
|
|
245
|
+
registryId: record.registryId,
|
|
246
|
+
compatible: false,
|
|
247
|
+
canUpdate: false,
|
|
248
|
+
status: "revoked",
|
|
249
|
+
reasons: ["revoked"],
|
|
250
|
+
revoked,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const targetVersion = revoked?.replacementVersion ?? entry.latestVersion;
|
|
256
|
+
const hasNewerTarget = compareVersions(targetVersion, record.installedVersion) > 0;
|
|
257
|
+
if (!hasNewerTarget && !revoked)
|
|
258
|
+
continue;
|
|
259
|
+
let compatibility = checkCatalogEntryCompatibility(entry, this.compatibilityInput(false));
|
|
260
|
+
const detail = await this.getPluginDetail(record.pluginId);
|
|
261
|
+
const targetRelease = detail?.versions[targetVersion];
|
|
262
|
+
if (targetRelease) {
|
|
263
|
+
compatibility = checkCatalogReleaseCompatibility(targetRelease, this.compatibilityInput(false));
|
|
264
|
+
}
|
|
265
|
+
const canUpdate = hasNewerTarget && compatibility.compatible;
|
|
266
|
+
const update = {
|
|
267
|
+
id: record.pluginId,
|
|
268
|
+
name: entry.name,
|
|
269
|
+
currentVersion: record.installedVersion,
|
|
270
|
+
latestVersion: entry.latestVersion,
|
|
271
|
+
targetVersion,
|
|
272
|
+
provenance: record.provenance,
|
|
273
|
+
registryId: record.registryId,
|
|
274
|
+
compatible: compatibility.compatible,
|
|
275
|
+
canUpdate,
|
|
276
|
+
bundleSize: targetRelease?.bundle.size,
|
|
277
|
+
status: revoked
|
|
278
|
+
? "revoked"
|
|
279
|
+
: compatibility.compatible
|
|
280
|
+
? "update-available"
|
|
281
|
+
: "incompatible",
|
|
282
|
+
reasons: compatibility.reasons,
|
|
283
|
+
};
|
|
284
|
+
if (revoked)
|
|
285
|
+
update.revoked = revoked;
|
|
286
|
+
updates.push(update);
|
|
287
|
+
}
|
|
288
|
+
return updates;
|
|
289
|
+
}
|
|
290
|
+
addProgressListener(listener) {
|
|
291
|
+
this.on("progress", listener);
|
|
292
|
+
return () => this.off("progress", listener);
|
|
293
|
+
}
|
|
294
|
+
requireEntry(pluginId) {
|
|
295
|
+
const entry = this.getCatalogEntry(pluginId);
|
|
296
|
+
if (!entry) {
|
|
297
|
+
throw new PluginDistributionError("metadata-invalid", `Plugin ${pluginId} is not in the current registry catalog.`);
|
|
298
|
+
}
|
|
299
|
+
return entry;
|
|
300
|
+
}
|
|
301
|
+
sourceForEntry(entry) {
|
|
302
|
+
return (this.registries.find((source) => source.enabled &&
|
|
303
|
+
(entry.channel === "official") === (source.trustTier === "official")) ?? null);
|
|
304
|
+
}
|
|
305
|
+
compatibilityInput(workspaceTrusted) {
|
|
306
|
+
return {
|
|
307
|
+
appVersion: this.options.appVersion,
|
|
308
|
+
platform: this.platform,
|
|
309
|
+
workspaceTrusted,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
get platform() {
|
|
313
|
+
return (this.options.platform ??
|
|
314
|
+
(hasNativeDesktopCapability("resource") ? "electron" : "web"));
|
|
315
|
+
}
|
|
316
|
+
async isWorkspaceTrusted() {
|
|
317
|
+
return Boolean(await this.options.workspaceTrusted?.());
|
|
318
|
+
}
|
|
319
|
+
async applyRevocationsToInstalled() {
|
|
320
|
+
const installed = await this.stateStore.list();
|
|
321
|
+
for (const record of installed) {
|
|
322
|
+
if (record.provenance !== "official")
|
|
323
|
+
continue;
|
|
324
|
+
const revoked = this.revocationFor(record);
|
|
325
|
+
if (sameRevocation(record.revoked, revoked))
|
|
326
|
+
continue;
|
|
327
|
+
const next = {
|
|
328
|
+
...record,
|
|
329
|
+
updatedAt: new Date().toISOString(),
|
|
330
|
+
};
|
|
331
|
+
if (revoked) {
|
|
332
|
+
next.revoked = revoked;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
delete next.revoked;
|
|
336
|
+
}
|
|
337
|
+
await this.stateStore.upsert(next);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
revocationFor(record) {
|
|
341
|
+
const match = this.revocations?.revoked.find((revocation) => revocation.pluginId === record.pluginId &&
|
|
342
|
+
revocation.versions.includes(record.installedVersion));
|
|
343
|
+
if (!match)
|
|
344
|
+
return undefined;
|
|
345
|
+
const { pluginId: _pluginId, versions: _versions, ...revocation } = match;
|
|
346
|
+
return revocation;
|
|
347
|
+
}
|
|
348
|
+
emitProgress(pluginId, phase, message, details = {}) {
|
|
349
|
+
this.dispatch("progress", { pluginId, phase, message, ...details });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const sameRevocation = (left, right) => (!left && !right) ||
|
|
353
|
+
Boolean(left &&
|
|
354
|
+
right &&
|
|
355
|
+
left.revokedAt === right.revokedAt &&
|
|
356
|
+
left.reason === right.reason &&
|
|
357
|
+
left.message === right.message &&
|
|
358
|
+
left.replacementVersion === right.replacementVersion);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PluginDistributionError } from "./errors";
|
|
2
|
+
export const assertSafePluginRelativePath = (path) => {
|
|
3
|
+
if (path.length === 0 ||
|
|
4
|
+
path.startsWith("/") ||
|
|
5
|
+
path.startsWith("\\") ||
|
|
6
|
+
path.includes("\\") ||
|
|
7
|
+
path.includes("\0") ||
|
|
8
|
+
path.split("/").some((part) => part === "" || part === "." || part === "..")) {
|
|
9
|
+
throw new PluginDistributionError("invalid-path", `Plugin release file path is not safe: ${path}`, { details: { path } });
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export const isSafePluginRelativePath = (path) => {
|
|
13
|
+
try {
|
|
14
|
+
assertSafePluginRelativePath(path);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PluginReleaseManifest, RemoteFileReference, SignedEnvelope, TrustedSigningKey } from "./types";
|
|
2
|
+
export type PluginBundleVerificationProgressPhase = "verifying-bundle" | "extracting-files" | "verifying-files";
|
|
3
|
+
export interface PluginBundleVerificationProgressEvent {
|
|
4
|
+
phase: PluginBundleVerificationProgressPhase;
|
|
5
|
+
message?: string;
|
|
6
|
+
filePath?: string;
|
|
7
|
+
fileIndex?: number;
|
|
8
|
+
fileCount?: number;
|
|
9
|
+
processedBytes?: number;
|
|
10
|
+
totalBytes?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface VerifyPluginBundlePayloadOptions {
|
|
13
|
+
bundle: ArrayBuffer | Uint8Array;
|
|
14
|
+
trustedKeys: TrustedSigningKey[];
|
|
15
|
+
expectedBundle?: RemoteFileReference;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
onProgress?: (event: PluginBundleVerificationProgressEvent) => void;
|
|
18
|
+
}
|
|
19
|
+
export interface VerifiedPluginBundlePayload {
|
|
20
|
+
releaseEnvelope: SignedEnvelope<PluginReleaseManifest>;
|
|
21
|
+
releaseManifest: PluginReleaseManifest;
|
|
22
|
+
releaseManifestSha256: string;
|
|
23
|
+
files: Map<string, Uint8Array>;
|
|
24
|
+
}
|
|
25
|
+
export declare const verifyPluginBundlePayload: (options: VerifyPluginBundlePayloadOptions) => Promise<VerifiedPluginBundlePayload>;
|
|
26
|
+
export declare function throwIfAborted(signal: AbortSignal | undefined): void;
|
|
27
|
+
export declare function createAbortError(): Error;
|
|
28
|
+
export declare function exactArrayBuffer(bytes: Uint8Array): ArrayBuffer;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { sha256Hex, verifySha256 } from "./hashes";
|
|
2
|
+
import { assertSafePluginRelativePath } from "./path-safety";
|
|
3
|
+
import { PLUGIN_BUNDLE_RELEASE_MANIFEST_PATH, parsePluginBundle, } from "./plugin-bundle";
|
|
4
|
+
import { parsePluginDistributionMetadata, pluginReleaseManifestSchema, signedEnvelopeSchema, } from "./schemas";
|
|
5
|
+
import { verifySignedEnvelope } from "./signing";
|
|
6
|
+
import { PluginDistributionError } from "./errors";
|
|
7
|
+
export const verifyPluginBundlePayload = async (options) => {
|
|
8
|
+
throwIfAborted(options.signal);
|
|
9
|
+
const bundle = toUint8(options.bundle);
|
|
10
|
+
options.onProgress?.({
|
|
11
|
+
phase: "verifying-bundle",
|
|
12
|
+
message: "Verifying plugin bundle",
|
|
13
|
+
});
|
|
14
|
+
if (typeof options.expectedBundle?.size === "number" &&
|
|
15
|
+
bundle.byteLength !== options.expectedBundle.size) {
|
|
16
|
+
throw new PluginDistributionError("hash-mismatch", `Plugin bundle size mismatch: expected ${options.expectedBundle.size}, got ${bundle.byteLength}.`, {
|
|
17
|
+
details: {
|
|
18
|
+
expectedSize: options.expectedBundle.size,
|
|
19
|
+
actualSize: bundle.byteLength,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (options.expectedBundle?.sha256) {
|
|
24
|
+
await verifySha256(bundle, options.expectedBundle.sha256);
|
|
25
|
+
}
|
|
26
|
+
throwIfAborted(options.signal);
|
|
27
|
+
const bundledFiles = parsePluginBundle(bundle, {
|
|
28
|
+
onProgress: options.onProgress,
|
|
29
|
+
});
|
|
30
|
+
const signedReleaseBytes = bundledFiles.get(PLUGIN_BUNDLE_RELEASE_MANIFEST_PATH);
|
|
31
|
+
if (!signedReleaseBytes) {
|
|
32
|
+
throw new PluginDistributionError("metadata-invalid", "Plugin bundle is missing release.signed.json.");
|
|
33
|
+
}
|
|
34
|
+
const releaseEnvelope = parsePluginDistributionMetadata(signedEnvelopeSchema(pluginReleaseManifestSchema), JSON.parse(new TextDecoder().decode(signedReleaseBytes)));
|
|
35
|
+
const releaseManifest = await verifySignedEnvelope(releaseEnvelope, options.trustedKeys);
|
|
36
|
+
const files = new Map();
|
|
37
|
+
const expectedPaths = new Set();
|
|
38
|
+
const fileCount = releaseManifest.files.length;
|
|
39
|
+
for (const [index, file] of releaseManifest.files.entries()) {
|
|
40
|
+
throwIfAborted(options.signal);
|
|
41
|
+
assertSafePluginRelativePath(file.path);
|
|
42
|
+
expectedPaths.add(file.path);
|
|
43
|
+
const bytes = bundledFiles.get(file.path);
|
|
44
|
+
if (!bytes) {
|
|
45
|
+
throw new PluginDistributionError("metadata-invalid", `Plugin bundle is missing signed file: ${file.path}`, { details: { path: file.path } });
|
|
46
|
+
}
|
|
47
|
+
if (bytes.byteLength !== file.size) {
|
|
48
|
+
throw new PluginDistributionError("hash-mismatch", `Plugin file ${file.path} size mismatch: expected ${file.size}, got ${bytes.byteLength}.`, {
|
|
49
|
+
details: {
|
|
50
|
+
path: file.path,
|
|
51
|
+
expectedSize: file.size,
|
|
52
|
+
actualSize: bytes.byteLength,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
options.onProgress?.({
|
|
57
|
+
phase: "verifying-files",
|
|
58
|
+
message: `Verifying ${file.path}`,
|
|
59
|
+
filePath: file.path,
|
|
60
|
+
fileIndex: index + 1,
|
|
61
|
+
fileCount,
|
|
62
|
+
});
|
|
63
|
+
await verifySha256(bytes, file.sha256);
|
|
64
|
+
files.set(file.path, bytes);
|
|
65
|
+
}
|
|
66
|
+
const extraPaths = [...bundledFiles.keys()].filter((path) => path !== PLUGIN_BUNDLE_RELEASE_MANIFEST_PATH && !expectedPaths.has(path));
|
|
67
|
+
if (extraPaths.length) {
|
|
68
|
+
throw new PluginDistributionError("metadata-invalid", "Plugin bundle includes files that are not listed in the signed release manifest.", { details: { paths: extraPaths } });
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
releaseEnvelope,
|
|
72
|
+
releaseManifest,
|
|
73
|
+
releaseManifestSha256: await sha256Hex(signedReleaseBytes),
|
|
74
|
+
files,
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export function throwIfAborted(signal) {
|
|
78
|
+
if (!signal?.aborted) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
throw createAbortError();
|
|
82
|
+
}
|
|
83
|
+
export function createAbortError() {
|
|
84
|
+
if (typeof DOMException !== "undefined") {
|
|
85
|
+
return new DOMException("Operation cancelled", "AbortError");
|
|
86
|
+
}
|
|
87
|
+
const error = new Error("Operation cancelled");
|
|
88
|
+
error.name = "AbortError";
|
|
89
|
+
return error;
|
|
90
|
+
}
|
|
91
|
+
export function exactArrayBuffer(bytes) {
|
|
92
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
93
|
+
copy.set(bytes);
|
|
94
|
+
return copy.buffer;
|
|
95
|
+
}
|
|
96
|
+
const toUint8 = (input) => input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type PluginBundleVerificationProgressEvent, type VerifiedPluginBundlePayload, type VerifyPluginBundlePayloadOptions } from "./plugin-bundle-verifier";
|
|
2
|
+
import type { PluginReleaseManifest, RemoteFileReference, SignedEnvelope, TrustedSigningKey } from "./types";
|
|
3
|
+
export type PluginBundleVerificationMode = "auto" | "worker" | "main-thread";
|
|
4
|
+
export interface PluginBundleWorkerVerifyRequest {
|
|
5
|
+
type: "verify";
|
|
6
|
+
bundle: ArrayBuffer;
|
|
7
|
+
trustedKeys: TrustedSigningKey[];
|
|
8
|
+
expectedBundle?: RemoteFileReference;
|
|
9
|
+
}
|
|
10
|
+
export type PluginBundleWorkerResponse = {
|
|
11
|
+
type: "progress";
|
|
12
|
+
progress: PluginBundleVerificationProgressEvent;
|
|
13
|
+
} | {
|
|
14
|
+
type: "result";
|
|
15
|
+
payload: SerializedVerifiedPluginBundlePayload;
|
|
16
|
+
} | {
|
|
17
|
+
type: "error";
|
|
18
|
+
error: SerializedPluginBundleWorkerError;
|
|
19
|
+
};
|
|
20
|
+
export interface SerializedVerifiedPluginBundlePayload {
|
|
21
|
+
releaseEnvelope: SignedEnvelope<PluginReleaseManifest>;
|
|
22
|
+
releaseManifest: PluginReleaseManifest;
|
|
23
|
+
releaseManifestSha256: string;
|
|
24
|
+
files: Array<[string, Uint8Array]>;
|
|
25
|
+
}
|
|
26
|
+
export interface SerializedPluginBundleWorkerError {
|
|
27
|
+
name: string;
|
|
28
|
+
message: string;
|
|
29
|
+
code?: string;
|
|
30
|
+
details?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
export declare const canUsePluginBundleWorker: () => boolean;
|
|
33
|
+
export declare const verifyPluginBundlePayloadInWorker: (options: VerifyPluginBundlePayloadOptions) => Promise<VerifiedPluginBundlePayload>;
|
|
34
|
+
export declare const serializeWorkerError: (error: unknown) => SerializedPluginBundleWorkerError;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { PluginDistributionError } from "./errors";
|
|
2
|
+
import { createAbortError, exactArrayBuffer, } from "./plugin-bundle-verifier";
|
|
3
|
+
export const canUsePluginBundleWorker = () => typeof Worker !== "undefined";
|
|
4
|
+
export const verifyPluginBundlePayloadInWorker = async (options) => {
|
|
5
|
+
if (!canUsePluginBundleWorker()) {
|
|
6
|
+
throw new Error("Plugin bundle worker is not available.");
|
|
7
|
+
}
|
|
8
|
+
const bundle = exactArrayBuffer(toUint8(options.bundle));
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
let settled = false;
|
|
11
|
+
let worker = null;
|
|
12
|
+
const settle = (callback, abortListener) => {
|
|
13
|
+
if (settled) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
settled = true;
|
|
17
|
+
if (abortListener) {
|
|
18
|
+
options.signal?.removeEventListener("abort", abortListener);
|
|
19
|
+
}
|
|
20
|
+
worker?.terminate();
|
|
21
|
+
callback();
|
|
22
|
+
};
|
|
23
|
+
const abortListener = () => {
|
|
24
|
+
settle(() => reject(createAbortError()), abortListener);
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
worker = createPluginBundleWorker();
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
reject(error);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (options.signal?.aborted) {
|
|
34
|
+
worker.terminate();
|
|
35
|
+
reject(createAbortError());
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const activeWorker = worker;
|
|
39
|
+
options.signal?.addEventListener("abort", abortListener, { once: true });
|
|
40
|
+
activeWorker.addEventListener("message", (event) => {
|
|
41
|
+
const message = event.data;
|
|
42
|
+
if (message.type === "progress") {
|
|
43
|
+
options.onProgress?.(message.progress);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (message.type === "result") {
|
|
47
|
+
settle(() => resolve(deserializeVerifiedPayload(message.payload)), abortListener);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (message.type === "error") {
|
|
51
|
+
settle(() => reject(deserializeWorkerError(message.error)), abortListener);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
activeWorker.addEventListener("error", (event) => {
|
|
55
|
+
const message = event.message;
|
|
56
|
+
settle(() => reject(new Error(message || "Plugin bundle worker verification failed.")), abortListener);
|
|
57
|
+
});
|
|
58
|
+
activeWorker.postMessage({
|
|
59
|
+
type: "verify",
|
|
60
|
+
bundle,
|
|
61
|
+
trustedKeys: options.trustedKeys,
|
|
62
|
+
...(options.expectedBundle
|
|
63
|
+
? { expectedBundle: options.expectedBundle }
|
|
64
|
+
: {}),
|
|
65
|
+
}, [bundle]);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
export const serializeWorkerError = (error) => {
|
|
69
|
+
if (error instanceof PluginDistributionError) {
|
|
70
|
+
return {
|
|
71
|
+
name: error.name,
|
|
72
|
+
message: error.message,
|
|
73
|
+
code: error.code,
|
|
74
|
+
details: error.details,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (error instanceof Error) {
|
|
78
|
+
return { name: error.name, message: error.message };
|
|
79
|
+
}
|
|
80
|
+
return { name: "Error", message: String(error) };
|
|
81
|
+
};
|
|
82
|
+
const createPluginBundleWorker = () => new Worker(new URL("./plugin-bundle.worker.js", import.meta.url), {
|
|
83
|
+
type: "module",
|
|
84
|
+
});
|
|
85
|
+
const deserializeVerifiedPayload = (payload) => ({
|
|
86
|
+
releaseEnvelope: payload.releaseEnvelope,
|
|
87
|
+
releaseManifest: payload.releaseManifest,
|
|
88
|
+
releaseManifestSha256: payload.releaseManifestSha256,
|
|
89
|
+
files: new Map(payload.files),
|
|
90
|
+
});
|
|
91
|
+
const deserializeWorkerError = (error) => {
|
|
92
|
+
if (error.code) {
|
|
93
|
+
return new PluginDistributionError(error.code, error.message, { details: error.details });
|
|
94
|
+
}
|
|
95
|
+
const deserialized = new Error(error.message);
|
|
96
|
+
deserialized.name = error.name;
|
|
97
|
+
return deserialized;
|
|
98
|
+
};
|
|
99
|
+
const toUint8 = (input) => input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const PLUGIN_BUNDLE_RELEASE_MANIFEST_PATH = "release.signed.json";
|
|
2
|
+
export declare const PLUGIN_BUNDLE_STORED_METHOD = 0;
|
|
3
|
+
export declare const PLUGIN_BUNDLE_DEFLATE_METHOD = 8;
|
|
4
|
+
export interface ParsePluginBundleProgressEvent {
|
|
5
|
+
phase: "extracting-files";
|
|
6
|
+
message?: string;
|
|
7
|
+
filePath?: string;
|
|
8
|
+
fileIndex?: number;
|
|
9
|
+
fileCount?: number;
|
|
10
|
+
processedBytes?: number;
|
|
11
|
+
totalBytes?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ParsePluginBundleOptions {
|
|
14
|
+
onProgress?: (event: ParsePluginBundleProgressEvent) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare const parsePluginBundle: (input: ArrayBuffer | Uint8Array, options?: ParsePluginBundleOptions) => Map<string, Uint8Array>;
|