@abraca/nuxt 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/README.md +84 -0
- package/dist/module.d.mts +201 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +170 -0
- package/dist/runtime/components/ACollaborationUsers.d.vue.ts +11 -0
- package/dist/runtime/components/ACollaborationUsers.vue +48 -0
- package/dist/runtime/components/ACollaborationUsers.vue.d.ts +11 -0
- package/dist/runtime/components/AColorPicker.d.vue.ts +13 -0
- package/dist/runtime/components/AColorPicker.vue +71 -0
- package/dist/runtime/components/AColorPicker.vue.d.ts +13 -0
- package/dist/runtime/components/ACommandPalette.d.vue.ts +13 -0
- package/dist/runtime/components/ACommandPalette.vue +31 -0
- package/dist/runtime/components/ACommandPalette.vue.d.ts +13 -0
- package/dist/runtime/components/AConnectionStatus.d.vue.ts +13 -0
- package/dist/runtime/components/AConnectionStatus.vue +50 -0
- package/dist/runtime/components/AConnectionStatus.vue.d.ts +13 -0
- package/dist/runtime/components/ADocTypeSelect.d.vue.ts +10 -0
- package/dist/runtime/components/ADocTypeSelect.vue +34 -0
- package/dist/runtime/components/ADocTypeSelect.vue.d.ts +10 -0
- package/dist/runtime/components/ADocumentTree.d.vue.ts +53 -0
- package/dist/runtime/components/ADocumentTree.vue +350 -0
- package/dist/runtime/components/ADocumentTree.vue.d.ts +53 -0
- package/dist/runtime/components/AEditor.d.vue.ts +60 -0
- package/dist/runtime/components/AEditor.vue +174 -0
- package/dist/runtime/components/AEditor.vue.d.ts +60 -0
- package/dist/runtime/components/AFloatingWindow.d.vue.ts +24 -0
- package/dist/runtime/components/AFloatingWindow.vue +232 -0
- package/dist/runtime/components/AFloatingWindow.vue.d.ts +24 -0
- package/dist/runtime/components/AIconPicker.d.vue.ts +13 -0
- package/dist/runtime/components/AIconPicker.vue +257 -0
- package/dist/runtime/components/AIconPicker.vue.d.ts +13 -0
- package/dist/runtime/components/ANodePanel.d.vue.ts +15 -0
- package/dist/runtime/components/ANodePanel.vue +541 -0
- package/dist/runtime/components/ANodePanel.vue.d.ts +15 -0
- package/dist/runtime/components/ANotifications.d.vue.ts +7 -0
- package/dist/runtime/components/ANotifications.vue +75 -0
- package/dist/runtime/components/ANotifications.vue.d.ts +7 -0
- package/dist/runtime/components/APermissionGuard.d.vue.ts +21 -0
- package/dist/runtime/components/APermissionGuard.vue +22 -0
- package/dist/runtime/components/APermissionGuard.vue.d.ts +21 -0
- package/dist/runtime/components/APresence.d.vue.ts +43 -0
- package/dist/runtime/components/APresence.vue +36 -0
- package/dist/runtime/components/APresence.vue.d.ts +43 -0
- package/dist/runtime/components/AProvider.d.vue.ts +27 -0
- package/dist/runtime/components/AProvider.vue +42 -0
- package/dist/runtime/components/AProvider.vue.d.ts +27 -0
- package/dist/runtime/components/ARoleBadge.d.vue.ts +11 -0
- package/dist/runtime/components/ARoleBadge.vue +29 -0
- package/dist/runtime/components/ARoleBadge.vue.d.ts +11 -0
- package/dist/runtime/components/AVoiceBar.d.vue.ts +13 -0
- package/dist/runtime/components/AVoiceBar.vue +379 -0
- package/dist/runtime/components/AVoiceBar.vue.d.ts +13 -0
- package/dist/runtime/components/AVoiceTile.d.vue.ts +10 -0
- package/dist/runtime/components/AVoiceTile.vue +48 -0
- package/dist/runtime/components/AVoiceTile.vue.d.ts +10 -0
- package/dist/runtime/components/AWindowLayer.d.vue.ts +3 -0
- package/dist/runtime/components/AWindowLayer.vue +17 -0
- package/dist/runtime/components/AWindowLayer.vue.d.ts +3 -0
- package/dist/runtime/components/aware/AArea.d.vue.ts +42 -0
- package/dist/runtime/components/aware/AArea.vue +45 -0
- package/dist/runtime/components/aware/AArea.vue.d.ts +42 -0
- package/dist/runtime/components/aware/AAvatar.d.vue.ts +25 -0
- package/dist/runtime/components/aware/AAvatar.vue +86 -0
- package/dist/runtime/components/aware/AAvatar.vue.d.ts +25 -0
- package/dist/runtime/components/aware/AButton.d.vue.ts +7 -0
- package/dist/runtime/components/aware/AButton.vue +39 -0
- package/dist/runtime/components/aware/AButton.vue.d.ts +7 -0
- package/dist/runtime/components/aware/ACursorLabel.d.vue.ts +12 -0
- package/dist/runtime/components/aware/ACursorLabel.vue +44 -0
- package/dist/runtime/components/aware/ACursorLabel.vue.d.ts +12 -0
- package/dist/runtime/components/aware/ADocBadge.d.vue.ts +11 -0
- package/dist/runtime/components/aware/ADocBadge.vue +27 -0
- package/dist/runtime/components/aware/ADocBadge.vue.d.ts +11 -0
- package/dist/runtime/components/aware/AFacepile.d.vue.ts +20 -0
- package/dist/runtime/components/aware/AFacepile.vue +92 -0
- package/dist/runtime/components/aware/AFacepile.vue.d.ts +20 -0
- package/dist/runtime/components/aware/AInput.d.vue.ts +7 -0
- package/dist/runtime/components/aware/AInput.vue +44 -0
- package/dist/runtime/components/aware/AInput.vue.d.ts +7 -0
- package/dist/runtime/components/aware/ASelect.d.vue.ts +7 -0
- package/dist/runtime/components/aware/ASelect.vue +51 -0
- package/dist/runtime/components/aware/ASelect.vue.d.ts +7 -0
- package/dist/runtime/components/aware/ATextarea.d.vue.ts +7 -0
- package/dist/runtime/components/aware/ATextarea.vue +44 -0
- package/dist/runtime/components/aware/ATextarea.vue.d.ts +7 -0
- package/dist/runtime/components/aware/AUserList.d.vue.ts +17 -0
- package/dist/runtime/components/aware/AUserList.vue +72 -0
- package/dist/runtime/components/aware/AUserList.vue.d.ts +17 -0
- package/dist/runtime/components/renderers/ACalendarRenderer.d.vue.ts +8 -0
- package/dist/runtime/components/renderers/ACalendarRenderer.vue +154 -0
- package/dist/runtime/components/renderers/ACalendarRenderer.vue.d.ts +8 -0
- package/dist/runtime/components/renderers/AGalleryRenderer.d.vue.ts +8 -0
- package/dist/runtime/components/renderers/AGalleryRenderer.vue +88 -0
- package/dist/runtime/components/renderers/AGalleryRenderer.vue.d.ts +8 -0
- package/dist/runtime/components/renderers/AKanbanRenderer.d.vue.ts +8 -0
- package/dist/runtime/components/renderers/AKanbanRenderer.vue +179 -0
- package/dist/runtime/components/renderers/AKanbanRenderer.vue.d.ts +8 -0
- package/dist/runtime/components/renderers/AOutlineRenderer.d.vue.ts +8 -0
- package/dist/runtime/components/renderers/AOutlineRenderer.vue +180 -0
- package/dist/runtime/components/renderers/AOutlineRenderer.vue.d.ts +8 -0
- package/dist/runtime/components/renderers/ATableRenderer.d.vue.ts +8 -0
- package/dist/runtime/components/renderers/ATableRenderer.vue +191 -0
- package/dist/runtime/components/renderers/ATableRenderer.vue.d.ts +8 -0
- package/dist/runtime/composables/useAAField.d.ts +42 -0
- package/dist/runtime/composables/useAAField.js +62 -0
- package/dist/runtime/composables/useAbraLocale.d.ts +14 -0
- package/dist/runtime/composables/useAbraLocale.js +11 -0
- package/dist/runtime/composables/useAbracadabra.d.ts +11 -0
- package/dist/runtime/composables/useAbracadabra.js +3 -0
- package/dist/runtime/composables/useAbracadabraAuth.d.ts +23 -0
- package/dist/runtime/composables/useAbracadabraAuth.js +22 -0
- package/dist/runtime/composables/useAwareness.d.ts +22 -0
- package/dist/runtime/composables/useAwareness.js +48 -0
- package/dist/runtime/composables/useAwarenessPeers.d.ts +34 -0
- package/dist/runtime/composables/useAwarenessPeers.js +33 -0
- package/dist/runtime/composables/useBackgroundSync.d.ts +37 -0
- package/dist/runtime/composables/useBackgroundSync.js +73 -0
- package/dist/runtime/composables/useChat.d.ts +65 -0
- package/dist/runtime/composables/useChat.js +210 -0
- package/dist/runtime/composables/useChatUsers.d.ts +21 -0
- package/dist/runtime/composables/useChatUsers.js +39 -0
- package/dist/runtime/composables/useChildTree.d.ts +119 -0
- package/dist/runtime/composables/useChildTree.js +100 -0
- package/dist/runtime/composables/useCommandPalette.d.ts +58 -0
- package/dist/runtime/composables/useCommandPalette.js +94 -0
- package/dist/runtime/composables/useConnectionStatus.d.ts +17 -0
- package/dist/runtime/composables/useConnectionStatus.js +37 -0
- package/dist/runtime/composables/useDashboard.d.ts +3 -0
- package/dist/runtime/composables/useDashboard.js +23 -0
- package/dist/runtime/composables/useDocExport.d.ts +5 -0
- package/dist/runtime/composables/useDocExport.js +256 -0
- package/dist/runtime/composables/useDocImport.d.ts +10 -0
- package/dist/runtime/composables/useDocImport.js +227 -0
- package/dist/runtime/composables/useDocJump.d.ts +29 -0
- package/dist/runtime/composables/useDocJump.js +17 -0
- package/dist/runtime/composables/useDocumentPermissions.d.ts +20 -0
- package/dist/runtime/composables/useDocumentPermissions.js +33 -0
- package/dist/runtime/composables/useEditor.d.ts +45 -0
- package/dist/runtime/composables/useEditor.js +121 -0
- package/dist/runtime/composables/useEditorDragHandle.d.ts +26 -0
- package/dist/runtime/composables/useEditorDragHandle.js +219 -0
- package/dist/runtime/composables/useEditorMentions.d.ts +28 -0
- package/dist/runtime/composables/useEditorMentions.js +40 -0
- package/dist/runtime/composables/useEditorSuggestions.d.ts +18 -0
- package/dist/runtime/composables/useEditorSuggestions.js +45 -0
- package/dist/runtime/composables/useEditorToolbar.d.ts +22 -0
- package/dist/runtime/composables/useEditorToolbar.js +60 -0
- package/dist/runtime/composables/useFileBlobStore.d.ts +15 -0
- package/dist/runtime/composables/useFileBlobStore.js +22 -0
- package/dist/runtime/composables/useFileIndex.d.ts +20 -0
- package/dist/runtime/composables/useFileIndex.js +69 -0
- package/dist/runtime/composables/useFollowUser.d.ts +5 -0
- package/dist/runtime/composables/useFollowUser.js +40 -0
- package/dist/runtime/composables/useNotifications.d.ts +82 -0
- package/dist/runtime/composables/useNotifications.js +171 -0
- package/dist/runtime/composables/useOfflineUploadQueue.d.ts +90 -0
- package/dist/runtime/composables/useOfflineUploadQueue.js +33 -0
- package/dist/runtime/composables/usePasskeyAccounts.d.ts +32 -0
- package/dist/runtime/composables/usePasskeyAccounts.js +46 -0
- package/dist/runtime/composables/usePluginRegistry.d.ts +6 -0
- package/dist/runtime/composables/usePluginRegistry.js +3 -0
- package/dist/runtime/composables/useRendererBase.d.ts +186 -0
- package/dist/runtime/composables/useRendererBase.js +46 -0
- package/dist/runtime/composables/useSearchIndex.d.ts +20 -0
- package/dist/runtime/composables/useSearchIndex.js +104 -0
- package/dist/runtime/composables/useTrash.d.ts +50 -0
- package/dist/runtime/composables/useTrash.js +127 -0
- package/dist/runtime/composables/useVoice.d.ts +51 -0
- package/dist/runtime/composables/useVoice.js +220 -0
- package/dist/runtime/composables/useWindowManager.d.ts +122 -0
- package/dist/runtime/composables/useWindowManager.js +141 -0
- package/dist/runtime/composables/useYDoc.d.ts +142 -0
- package/dist/runtime/composables/useYDoc.js +172 -0
- package/dist/runtime/extensions/accordion.d.ts +3 -0
- package/dist/runtime/extensions/accordion.js +49 -0
- package/dist/runtime/extensions/badge.d.ts +2 -0
- package/dist/runtime/extensions/badge.js +39 -0
- package/dist/runtime/extensions/callout.d.ts +2 -0
- package/dist/runtime/extensions/callout.js +28 -0
- package/dist/runtime/extensions/card.d.ts +3 -0
- package/dist/runtime/extensions/card.js +53 -0
- package/dist/runtime/extensions/code-collapse.d.ts +2 -0
- package/dist/runtime/extensions/code-collapse.js +32 -0
- package/dist/runtime/extensions/code-group.d.ts +2 -0
- package/dist/runtime/extensions/code-group.js +17 -0
- package/dist/runtime/extensions/collapsible.d.ts +2 -0
- package/dist/runtime/extensions/collapsible.js +35 -0
- package/dist/runtime/extensions/document-header.d.ts +11 -0
- package/dist/runtime/extensions/document-header.js +82 -0
- package/dist/runtime/extensions/document-meta.d.ts +20 -0
- package/dist/runtime/extensions/document-meta.js +121 -0
- package/dist/runtime/extensions/document.d.ts +6 -0
- package/dist/runtime/extensions/document.js +6 -0
- package/dist/runtime/extensions/file-block.d.ts +15 -0
- package/dist/runtime/extensions/file-block.js +34 -0
- package/dist/runtime/extensions/file-drop.d.ts +6 -0
- package/dist/runtime/extensions/file-drop.js +65 -0
- package/dist/runtime/extensions/kbd.d.ts +2 -0
- package/dist/runtime/extensions/kbd.js +33 -0
- package/dist/runtime/extensions/prose-icon.d.ts +2 -0
- package/dist/runtime/extensions/prose-icon.js +33 -0
- package/dist/runtime/extensions/search-highlight.d.ts +10 -0
- package/dist/runtime/extensions/search-highlight.js +129 -0
- package/dist/runtime/extensions/steps.d.ts +2 -0
- package/dist/runtime/extensions/steps.js +32 -0
- package/dist/runtime/extensions/tabs.d.ts +3 -0
- package/dist/runtime/extensions/tabs.js +49 -0
- package/dist/runtime/extensions/views/AccordionItemView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/AccordionItemView.vue +41 -0
- package/dist/runtime/extensions/views/AccordionItemView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/AccordionView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/AccordionView.vue +22 -0
- package/dist/runtime/extensions/views/AccordionView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/BadgeView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/BadgeView.vue +23 -0
- package/dist/runtime/extensions/views/BadgeView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/CalloutView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/CalloutView.vue +43 -0
- package/dist/runtime/extensions/views/CalloutView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/CardGroupView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/CardGroupView.vue +22 -0
- package/dist/runtime/extensions/views/CardGroupView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/CardView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/CardView.vue +28 -0
- package/dist/runtime/extensions/views/CardView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/CodeCollapseView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/CodeCollapseView.vue +45 -0
- package/dist/runtime/extensions/views/CodeCollapseView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/CodeGroupView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/CodeGroupView.vue +53 -0
- package/dist/runtime/extensions/views/CodeGroupView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/CollapsibleView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/CollapsibleView.vue +42 -0
- package/dist/runtime/extensions/views/CollapsibleView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/FileNodeView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/FileNodeView.vue +140 -0
- package/dist/runtime/extensions/views/FileNodeView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/KbdView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/KbdView.vue +23 -0
- package/dist/runtime/extensions/views/KbdView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/ProseIconView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/ProseIconView.vue +23 -0
- package/dist/runtime/extensions/views/ProseIconView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/StepsView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/StepsView.vue +32 -0
- package/dist/runtime/extensions/views/StepsView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/TabsItemView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/TabsItemView.vue +22 -0
- package/dist/runtime/extensions/views/TabsItemView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/TabsView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/TabsView.vue +56 -0
- package/dist/runtime/extensions/views/TabsView.vue.d.ts +4 -0
- package/dist/runtime/locale.d.ts +134 -0
- package/dist/runtime/locale.js +119 -0
- package/dist/runtime/middleware/abracadabra-auth.d.ts +11 -0
- package/dist/runtime/middleware/abracadabra-auth.js +9 -0
- package/dist/runtime/plugin-abracadabra.client.d.ts +7 -0
- package/dist/runtime/plugin-abracadabra.client.js +898 -0
- package/dist/runtime/plugin-abracadabra.server.d.ts +2 -0
- package/dist/runtime/plugin-abracadabra.server.js +71 -0
- package/dist/runtime/plugin-registry.d.ts +34 -0
- package/dist/runtime/plugin-registry.js +83 -0
- package/dist/runtime/plugin-shared-globals.client.d.ts +2 -0
- package/dist/runtime/plugin-shared-globals.client.js +20 -0
- package/dist/runtime/plugins/core.plugin.d.ts +12 -0
- package/dist/runtime/plugins/core.plugin.js +179 -0
- package/dist/runtime/server/api/_abracadabra/render/[docId].get.d.ts +18 -0
- package/dist/runtime/server/api/_abracadabra/render/[docId].get.js +51 -0
- package/dist/runtime/server/api/_abracadabra/spaces.get.d.ts +11 -0
- package/dist/runtime/server/api/_abracadabra/spaces.get.js +16 -0
- package/dist/runtime/server/plugins/abracadabra-service.d.ts +2 -0
- package/dist/runtime/server/plugins/abracadabra-service.js +116 -0
- package/dist/runtime/server/runners/doc-tree-cache.d.ts +11 -0
- package/dist/runtime/server/runners/doc-tree-cache.js +65 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/docCache.d.ts +25 -0
- package/dist/runtime/server/utils/docCache.js +131 -0
- package/dist/runtime/server/utils/serverRunner.d.ts +28 -0
- package/dist/runtime/server/utils/serverRunner.js +58 -0
- package/dist/runtime/types.d.ts +444 -0
- package/dist/runtime/types.js +93 -0
- package/dist/runtime/utils/VoiceClient.d.ts +94 -0
- package/dist/runtime/utils/VoiceClient.js +599 -0
- package/dist/runtime/utils/avatarStyle.d.ts +15 -0
- package/dist/runtime/utils/avatarStyle.js +20 -0
- package/dist/runtime/utils/colorPalettes.d.ts +13 -0
- package/dist/runtime/utils/colorPalettes.js +49 -0
- package/dist/runtime/utils/docTypes.d.ts +129 -0
- package/dist/runtime/utils/docTypes.js +116 -0
- package/dist/runtime/utils/markdownToYjs.d.ts +23 -0
- package/dist/runtime/utils/markdownToYjs.js +440 -0
- package/dist/runtime/utils/metaFieldDefinitions.d.ts +7 -0
- package/dist/runtime/utils/metaFieldDefinitions.js +182 -0
- package/dist/runtime/utils/voiceErrors.d.ts +33 -0
- package/dist/runtime/utils/voiceErrors.js +54 -0
- package/dist/runtime/utils/yjsConvert.d.ts +14 -0
- package/dist/runtime/utils/yjsConvert.js +331 -0
- package/dist/types.d.mts +13 -0
- package/package.json +100 -0
|
@@ -0,0 +1,898 @@
|
|
|
1
|
+
import { shallowRef, ref, computed, watch } from "vue";
|
|
2
|
+
import * as Y from "yjs";
|
|
3
|
+
import { PluginRegistry } from "./plugin-registry.js";
|
|
4
|
+
import { corePlugin } from "./plugins/core.plugin.js";
|
|
5
|
+
import {
|
|
6
|
+
UI_COLORS,
|
|
7
|
+
UI_NEUTRALS,
|
|
8
|
+
COLOR_HUES,
|
|
9
|
+
deriveColorFromPubKey
|
|
10
|
+
} from "./types.js";
|
|
11
|
+
import {
|
|
12
|
+
_initBackgroundSync,
|
|
13
|
+
_destroyBackgroundSync
|
|
14
|
+
} from "./composables/useBackgroundSync.js";
|
|
15
|
+
import {
|
|
16
|
+
_initFileBlobStore,
|
|
17
|
+
_destroyFileBlobStore
|
|
18
|
+
} from "./composables/useFileBlobStore.js";
|
|
19
|
+
import {
|
|
20
|
+
_initSearchIndex,
|
|
21
|
+
_destroySearchIndex
|
|
22
|
+
} from "./composables/useSearchIndex.js";
|
|
23
|
+
import {
|
|
24
|
+
_initNotifications,
|
|
25
|
+
_destroyNotifications,
|
|
26
|
+
_handleStatelessNotification
|
|
27
|
+
} from "./composables/useNotifications.js";
|
|
28
|
+
import {
|
|
29
|
+
_initChat,
|
|
30
|
+
_destroyChat,
|
|
31
|
+
_handleStatelessChat
|
|
32
|
+
} from "./composables/useChat.js";
|
|
33
|
+
import {
|
|
34
|
+
_initFileIndex,
|
|
35
|
+
_destroyFileIndex
|
|
36
|
+
} from "./composables/useFileIndex.js";
|
|
37
|
+
const STORAGE_KEY_EXTERNAL_PLUGINS = "abracadabra_external_plugins";
|
|
38
|
+
const STORAGE_KEY_DISABLED_BUILTINS = "abracadabra_disabled_builtins";
|
|
39
|
+
const CLAIMED_FLAG_KEY = "abracadabra_was_claimed";
|
|
40
|
+
const PUBKEY_KEY = "abracadabra_pubkey";
|
|
41
|
+
const CURRENT_SERVER_KEY = "abracadabra_current_server";
|
|
42
|
+
const REQUIRE_PASSKEY_KEY = "abracadabra_require_passkey";
|
|
43
|
+
const SERVERS_KEY = "abracadabra_servers";
|
|
44
|
+
const DEFAULT_ADJECTIVES = ["Swift", "Bright", "Mystic", "Cosmic", "Lucky", "Bold", "Wild", "Keen", "Deft", "Sly"];
|
|
45
|
+
const DEFAULT_NOUNS = ["Wizard", "Mage", "Phoenix", "Raven", "Wolf", "Sage", "Bard", "Druid", "Seer", "Lynx"];
|
|
46
|
+
function randomName(abraConfig) {
|
|
47
|
+
const adjs = abraConfig.guestName?.adjectives?.length ? abraConfig.guestName.adjectives : DEFAULT_ADJECTIVES;
|
|
48
|
+
const nouns = abraConfig.guestName?.nouns?.length ? abraConfig.guestName.nouns : DEFAULT_NOUNS;
|
|
49
|
+
return `${adjs[Math.floor(Math.random() * adjs.length)]}-${nouns[Math.floor(Math.random() * nouns.length)]}`;
|
|
50
|
+
}
|
|
51
|
+
function ts() {
|
|
52
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
53
|
+
}
|
|
54
|
+
function toBase64Url(bytes) {
|
|
55
|
+
let b = "";
|
|
56
|
+
for (const byte of bytes) b += String.fromCharCode(byte);
|
|
57
|
+
return btoa(b).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
58
|
+
}
|
|
59
|
+
function fromBase64Url(str) {
|
|
60
|
+
const b = atob(str.replace(/-/g, "+").replace(/_/g, "/"));
|
|
61
|
+
const a = new Uint8Array(b.length);
|
|
62
|
+
for (let i = 0; i < b.length; i++) a[i] = b.charCodeAt(i);
|
|
63
|
+
return a;
|
|
64
|
+
}
|
|
65
|
+
async function fetchServerInfo(baseUrl) {
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`${baseUrl}/info`);
|
|
68
|
+
if (res.ok) return await res.json();
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
async function fetchSpacesInfo(baseUrl) {
|
|
74
|
+
try {
|
|
75
|
+
const [spacesRes, hubRes] = await Promise.all([
|
|
76
|
+
fetch(`${baseUrl}/spaces`),
|
|
77
|
+
fetch(`${baseUrl}/spaces/hub`)
|
|
78
|
+
]);
|
|
79
|
+
if (!spacesRes.ok) return null;
|
|
80
|
+
const spacesData = await spacesRes.json();
|
|
81
|
+
const hubDocId = hubRes.ok ? (await hubRes.json()).doc_id : void 0;
|
|
82
|
+
return { spaces: spacesData.spaces, hubDocId };
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export default defineNuxtPlugin({
|
|
88
|
+
name: "abracadabra",
|
|
89
|
+
enforce: "post",
|
|
90
|
+
// run after shared-globals
|
|
91
|
+
async setup(nuxtApp) {
|
|
92
|
+
const config = useRuntimeConfig();
|
|
93
|
+
const abraConfig = config.public.abracadabra ?? {};
|
|
94
|
+
const defaultUrl = abraConfig.url ?? "https://abra.cou.sh";
|
|
95
|
+
const persistAuth = abraConfig.persistAuth ?? true;
|
|
96
|
+
const authStorageKey = abraConfig.authStorageKey ?? "abracadabra:auth";
|
|
97
|
+
const disabledBuiltins = abraConfig.disabledBuiltins ?? [];
|
|
98
|
+
const features = abraConfig.features ?? {};
|
|
99
|
+
const debug = abraConfig.debug ?? false;
|
|
100
|
+
const configEntryDocId = abraConfig.entryDocId || void 0;
|
|
101
|
+
const registry = new PluginRegistry();
|
|
102
|
+
const storedDisabledBuiltins = JSON.parse(
|
|
103
|
+
localStorage.getItem(STORAGE_KEY_DISABLED_BUILTINS) ?? "[]"
|
|
104
|
+
);
|
|
105
|
+
const allDisabled = [...disabledBuiltins, ...storedDisabledBuiltins];
|
|
106
|
+
if (!allDisabled.includes("core")) {
|
|
107
|
+
registry.register(corePlugin);
|
|
108
|
+
}
|
|
109
|
+
const storedExternal = JSON.parse(
|
|
110
|
+
localStorage.getItem(STORAGE_KEY_EXTERNAL_PLUGINS) ?? "[]"
|
|
111
|
+
);
|
|
112
|
+
for (const entry of storedExternal.filter((p) => p.enabled)) {
|
|
113
|
+
try {
|
|
114
|
+
const mod = await import(
|
|
115
|
+
/* @vite-ignore */
|
|
116
|
+
entry.url
|
|
117
|
+
);
|
|
118
|
+
const plugin = mod.default ?? mod;
|
|
119
|
+
if (plugin?.name) {
|
|
120
|
+
registry.register(plugin);
|
|
121
|
+
const updated = storedExternal.map(
|
|
122
|
+
(e) => e.url === entry.url ? { ...e, name: plugin.name, label: plugin.label, version: plugin.version, error: void 0 } : e
|
|
123
|
+
);
|
|
124
|
+
localStorage.setItem(STORAGE_KEY_EXTERNAL_PLUGINS, JSON.stringify(updated));
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
console.error(`[abracadabra] Failed to load plugin from ${entry.url}:`, e);
|
|
128
|
+
const updated = storedExternal.map(
|
|
129
|
+
(en) => en.url === entry.url ? { ...en, error: e?.message ?? String(e) } : en
|
|
130
|
+
);
|
|
131
|
+
localStorage.setItem(STORAGE_KEY_EXTERNAL_PLUGINS, JSON.stringify(updated));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
registry.freeze();
|
|
135
|
+
if (debug) {
|
|
136
|
+
console.log("[abracadabra] plugins:", registry.getPlugins().map((p) => p.name));
|
|
137
|
+
}
|
|
138
|
+
const doc = shallowRef(new Y.Doc());
|
|
139
|
+
const provider = shallowRef(null);
|
|
140
|
+
const client = shallowRef(null);
|
|
141
|
+
const keystore = shallowRef(null);
|
|
142
|
+
const status = ref("disconnected");
|
|
143
|
+
const synced = ref(false);
|
|
144
|
+
const logs = ref([]);
|
|
145
|
+
const isReady = ref(false);
|
|
146
|
+
const publicKeyB64 = ref("");
|
|
147
|
+
const connectionError = ref(null);
|
|
148
|
+
const isClaimed = ref(false);
|
|
149
|
+
const identityLost = ref(false);
|
|
150
|
+
const needsReauth = ref(false);
|
|
151
|
+
const requirePasskeyOnLogin = ref(false);
|
|
152
|
+
const isConnectionBlocked = ref(false);
|
|
153
|
+
const userCount = ref(1);
|
|
154
|
+
const pendingInviteCode = ref(null);
|
|
155
|
+
const userName = ref(localStorage.getItem("abracadabra_username") || randomName(abraConfig));
|
|
156
|
+
const userColor = ref("hsl(217, 70%, 75%)");
|
|
157
|
+
const userColorName = ref("blue");
|
|
158
|
+
const userNeutralColorName = ref("zinc");
|
|
159
|
+
const currentServerUrl = ref(defaultUrl);
|
|
160
|
+
const savedServers = ref([]);
|
|
161
|
+
let _serversLoaded = false;
|
|
162
|
+
let _initPromise = null;
|
|
163
|
+
let _wsp = null;
|
|
164
|
+
let authFailureCount = 0;
|
|
165
|
+
const AUTH_FAILURE_LIMIT = 3;
|
|
166
|
+
let lastClientIds = /* @__PURE__ */ new Set();
|
|
167
|
+
const clientNameCache = /* @__PURE__ */ new Map();
|
|
168
|
+
function addLog(message, type = "system") {
|
|
169
|
+
logs.value = [...logs.value.slice(-200), { time: ts(), message, type }];
|
|
170
|
+
}
|
|
171
|
+
function loadServers() {
|
|
172
|
+
if (_serversLoaded) return;
|
|
173
|
+
_serversLoaded = true;
|
|
174
|
+
const DEFAULT_SERVER = { url: defaultUrl, label: new URL(defaultUrl).hostname };
|
|
175
|
+
try {
|
|
176
|
+
const raw = localStorage.getItem(SERVERS_KEY);
|
|
177
|
+
if (raw) savedServers.value = JSON.parse(raw);
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
if (!savedServers.value.some((s) => s.url === DEFAULT_SERVER.url)) {
|
|
181
|
+
savedServers.value = [DEFAULT_SERVER, ...savedServers.value];
|
|
182
|
+
persistServers();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function persistServers() {
|
|
186
|
+
try {
|
|
187
|
+
localStorage.setItem(SERVERS_KEY, JSON.stringify(savedServers.value));
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function addServer(rawUrl) {
|
|
192
|
+
let url = rawUrl.trim();
|
|
193
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) url = "https://" + url;
|
|
194
|
+
url = url.replace(/\/$/, "");
|
|
195
|
+
if (savedServers.value.some((s) => s.url === url)) return;
|
|
196
|
+
let label = url;
|
|
197
|
+
try {
|
|
198
|
+
label = new URL(url).hostname;
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
const entry = { url, label };
|
|
202
|
+
const [info, spacesInfo] = await Promise.all([fetchServerInfo(url), fetchSpacesInfo(url)]);
|
|
203
|
+
if (info.index_doc_id) entry.entryDocId = info.index_doc_id;
|
|
204
|
+
if (info.name) entry.label = info.name;
|
|
205
|
+
if (spacesInfo) {
|
|
206
|
+
entry.spacesEnabled = true;
|
|
207
|
+
entry.cachedSpaces = spacesInfo.spaces;
|
|
208
|
+
if (spacesInfo.hubDocId) entry.hubDocId = spacesInfo.hubDocId;
|
|
209
|
+
}
|
|
210
|
+
savedServers.value = [...savedServers.value, entry];
|
|
211
|
+
persistServers();
|
|
212
|
+
}
|
|
213
|
+
function removeServer(url) {
|
|
214
|
+
if (url === defaultUrl) return;
|
|
215
|
+
savedServers.value = savedServers.value.filter((s) => s.url !== url);
|
|
216
|
+
persistServers();
|
|
217
|
+
}
|
|
218
|
+
const currentServerSpaces = computed(
|
|
219
|
+
() => savedServers.value.find((s) => s.url === currentServerUrl.value)?.cachedSpaces ?? []
|
|
220
|
+
);
|
|
221
|
+
const currentServerSpacesEnabled = computed(
|
|
222
|
+
() => savedServers.value.find((s) => s.url === currentServerUrl.value)?.spacesEnabled ?? false
|
|
223
|
+
);
|
|
224
|
+
async function refreshSpaces() {
|
|
225
|
+
const url = currentServerUrl.value;
|
|
226
|
+
if (!url) return;
|
|
227
|
+
const spacesInfo = await fetchSpacesInfo(url);
|
|
228
|
+
const idx = savedServers.value.findIndex((s) => s.url === url);
|
|
229
|
+
if (idx !== -1) {
|
|
230
|
+
if (spacesInfo) {
|
|
231
|
+
savedServers.value[idx] = { ...savedServers.value[idx], spacesEnabled: true, cachedSpaces: spacesInfo.spaces, hubDocId: spacesInfo.hubDocId ?? savedServers.value[idx].hubDocId };
|
|
232
|
+
}
|
|
233
|
+
persistServers();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function createSpace(opts) {
|
|
237
|
+
if (!client.value) throw new Error("Not connected");
|
|
238
|
+
const result = await client.value.createSpace(opts);
|
|
239
|
+
await refreshSpaces();
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
async function updateSpace(id, opts) {
|
|
243
|
+
if (!client.value) throw new Error("Not connected");
|
|
244
|
+
const result = await client.value.updateSpace(id, opts);
|
|
245
|
+
await refreshSpaces();
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
async function deleteSpace(id) {
|
|
249
|
+
if (!client.value) throw new Error("Not connected");
|
|
250
|
+
await client.value.deleteSpace(id);
|
|
251
|
+
await refreshSpaces();
|
|
252
|
+
}
|
|
253
|
+
function setUserName(name) {
|
|
254
|
+
userName.value = name;
|
|
255
|
+
localStorage.setItem("abracadabra_username", name);
|
|
256
|
+
provider.value?.setAwarenessField("user", { name, color: userColor.value, publicKey: publicKeyB64.value });
|
|
257
|
+
client.value?.updateMe({ displayName: name }).catch(() => {
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
function setUserColor(colorName) {
|
|
261
|
+
const h = COLOR_HUES[colorName] ?? 0;
|
|
262
|
+
const hsl = `hsl(${h}, 70%, 75%)`;
|
|
263
|
+
userColor.value = hsl;
|
|
264
|
+
userColorName.value = colorName;
|
|
265
|
+
localStorage.setItem("abracadabra_usercolor", colorName);
|
|
266
|
+
const appConfig = useAppConfig();
|
|
267
|
+
if (appConfig.ui?.colors) appConfig.ui.colors.primary = colorName;
|
|
268
|
+
provider.value?.setAwarenessField("user", { name: userName.value, color: hsl, publicKey: publicKeyB64.value });
|
|
269
|
+
}
|
|
270
|
+
function setNeutralColor(colorName) {
|
|
271
|
+
userNeutralColorName.value = colorName;
|
|
272
|
+
localStorage.setItem("abracadabra_neutralcolor", colorName);
|
|
273
|
+
const appConfig = useAppConfig();
|
|
274
|
+
if (appConfig.ui?.colors) appConfig.ui.colors.neutral = colorName;
|
|
275
|
+
}
|
|
276
|
+
function setRequirePasskey(enabled) {
|
|
277
|
+
requirePasskeyOnLogin.value = enabled;
|
|
278
|
+
localStorage.setItem(REQUIRE_PASSKEY_KEY, enabled ? "1" : "0");
|
|
279
|
+
}
|
|
280
|
+
function reconnect() {
|
|
281
|
+
if (!_wsp) return;
|
|
282
|
+
authFailureCount = 0;
|
|
283
|
+
isConnectionBlocked.value = false;
|
|
284
|
+
connectionError.value = null;
|
|
285
|
+
_wsp.connect();
|
|
286
|
+
}
|
|
287
|
+
function _teardown() {
|
|
288
|
+
if (_wsp) {
|
|
289
|
+
_wsp.disconnect();
|
|
290
|
+
_wsp = null;
|
|
291
|
+
}
|
|
292
|
+
if (provider.value) {
|
|
293
|
+
try {
|
|
294
|
+
provider.value.destroy();
|
|
295
|
+
} catch {
|
|
296
|
+
}
|
|
297
|
+
;
|
|
298
|
+
provider.value = null;
|
|
299
|
+
}
|
|
300
|
+
client.value = null;
|
|
301
|
+
keystore.value = null;
|
|
302
|
+
isReady.value = false;
|
|
303
|
+
synced.value = false;
|
|
304
|
+
status.value = "disconnected";
|
|
305
|
+
isConnectionBlocked.value = false;
|
|
306
|
+
connectionError.value = null;
|
|
307
|
+
authFailureCount = 0;
|
|
308
|
+
lastClientIds = /* @__PURE__ */ new Set();
|
|
309
|
+
clientNameCache.clear();
|
|
310
|
+
doc.value = new Y.Doc();
|
|
311
|
+
_destroyBackgroundSync();
|
|
312
|
+
_destroyFileBlobStore();
|
|
313
|
+
_destroySearchIndex();
|
|
314
|
+
_destroyNotifications();
|
|
315
|
+
_destroyChat();
|
|
316
|
+
_destroyFileIndex();
|
|
317
|
+
}
|
|
318
|
+
async function switchServer(url) {
|
|
319
|
+
if (url === currentServerUrl.value) return;
|
|
320
|
+
try {
|
|
321
|
+
localStorage.setItem(CURRENT_SERVER_KEY, url);
|
|
322
|
+
} catch {
|
|
323
|
+
}
|
|
324
|
+
addLog(`Switching server \u2192 ${url}`, "connection");
|
|
325
|
+
_teardown();
|
|
326
|
+
_initPromise = null;
|
|
327
|
+
_initPromise = init(url);
|
|
328
|
+
await _initPromise;
|
|
329
|
+
}
|
|
330
|
+
async function switchSpace(serverUrl, spaceDocId) {
|
|
331
|
+
if (serverUrl !== currentServerUrl.value) await switchServer(serverUrl);
|
|
332
|
+
await navigateTo(`${abraConfig.docBasePath ?? "/doc"}/${spaceDocId}`);
|
|
333
|
+
}
|
|
334
|
+
async function claimAccount() {
|
|
335
|
+
if (!client.value || !keystore.value) {
|
|
336
|
+
addLog("Cannot claim: client or keystore not initialized", "auth");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
addLog("Upgrading to hardware-bound identity...", "auth");
|
|
340
|
+
try {
|
|
341
|
+
const { publicKey: newPubKey, x25519PublicKey } = await keystore.value.register(
|
|
342
|
+
userName.value,
|
|
343
|
+
window.location.hostname,
|
|
344
|
+
"Abracadabra"
|
|
345
|
+
);
|
|
346
|
+
await client.value.addKey({
|
|
347
|
+
publicKey: newPubKey,
|
|
348
|
+
x25519Key: x25519PublicKey,
|
|
349
|
+
deviceName: "Hardware Auth (" + (navigator.platform || "Device") + ")"
|
|
350
|
+
});
|
|
351
|
+
publicKeyB64.value = newPubKey;
|
|
352
|
+
isClaimed.value = true;
|
|
353
|
+
identityLost.value = false;
|
|
354
|
+
localStorage.removeItem("abracadabra_privkey");
|
|
355
|
+
localStorage.setItem(CLAIMED_FLAG_KEY, "1");
|
|
356
|
+
localStorage.setItem(PUBKEY_KEY, newPubKey);
|
|
357
|
+
addLog("Account protected by hardware/biometrics", "auth");
|
|
358
|
+
return true;
|
|
359
|
+
} catch (e) {
|
|
360
|
+
addLog(`Claim failed: ${e.message}`, "auth");
|
|
361
|
+
throw e;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async function loginWithHardware(credentialIdHint) {
|
|
365
|
+
if (!keystore.value) throw new Error("Keystore not initialized");
|
|
366
|
+
try {
|
|
367
|
+
let hasPk;
|
|
368
|
+
let pubKey;
|
|
369
|
+
if (credentialIdHint) {
|
|
370
|
+
try {
|
|
371
|
+
pubKey = await keystore.value.getPublicKey(credentialIdHint);
|
|
372
|
+
hasPk = !!pubKey;
|
|
373
|
+
} catch {
|
|
374
|
+
hasPk = await keystore.value.hasIdentity();
|
|
375
|
+
pubKey = hasPk ? await keystore.value.getPublicKey() : null;
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
hasPk = await keystore.value.hasIdentity();
|
|
379
|
+
pubKey = hasPk ? await keystore.value.getPublicKey() : null;
|
|
380
|
+
}
|
|
381
|
+
if (!hasPk || !pubKey) throw new Error("No passkey found on this device.");
|
|
382
|
+
if (identityLost.value) {
|
|
383
|
+
identityLost.value = false;
|
|
384
|
+
isClaimed.value = true;
|
|
385
|
+
localStorage.setItem(CLAIMED_FLAG_KEY, "1");
|
|
386
|
+
localStorage.setItem(PUBKEY_KEY, pubKey);
|
|
387
|
+
addLog("Identity recovered via hardware key \u2014 reinitializing", "auth");
|
|
388
|
+
_teardown();
|
|
389
|
+
_initPromise = null;
|
|
390
|
+
_initPromise = init(currentServerUrl.value);
|
|
391
|
+
await _initPromise;
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
if (!client.value) throw new Error("Not connected to server");
|
|
395
|
+
const oldPubKey = publicKeyB64.value;
|
|
396
|
+
await client.value.loginWithKey(pubKey, (ch) => keystore.value.sign(ch));
|
|
397
|
+
publicKeyB64.value = pubKey;
|
|
398
|
+
isClaimed.value = true;
|
|
399
|
+
needsReauth.value = false;
|
|
400
|
+
localStorage.setItem(CLAIMED_FLAG_KEY, "1");
|
|
401
|
+
localStorage.setItem(PUBKEY_KEY, pubKey);
|
|
402
|
+
addLog("Re-authenticated via hardware key", "auth");
|
|
403
|
+
if (pubKey !== oldPubKey) {
|
|
404
|
+
_teardown();
|
|
405
|
+
_initPromise = null;
|
|
406
|
+
_initPromise = init(currentServerUrl.value);
|
|
407
|
+
await _initPromise;
|
|
408
|
+
} else if (_wsp) {
|
|
409
|
+
_wsp.disconnect();
|
|
410
|
+
setTimeout(() => _wsp?.connect(), 300);
|
|
411
|
+
}
|
|
412
|
+
return true;
|
|
413
|
+
} catch (e) {
|
|
414
|
+
addLog(`Hardware login failed: ${e.message}`, "auth");
|
|
415
|
+
throw e;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async function logout() {
|
|
419
|
+
addLog("Clearing identity...", "auth");
|
|
420
|
+
if (client.value) client.value.logout();
|
|
421
|
+
if (keystore.value) await keystore.value.clear();
|
|
422
|
+
localStorage.removeItem("abracadabra_privkey");
|
|
423
|
+
localStorage.removeItem("abracadabra_usercolor");
|
|
424
|
+
localStorage.removeItem("abracadabra_neutralcolor");
|
|
425
|
+
localStorage.removeItem("abracadabra_username");
|
|
426
|
+
localStorage.removeItem(CLAIMED_FLAG_KEY);
|
|
427
|
+
localStorage.removeItem(PUBKEY_KEY);
|
|
428
|
+
window.location.reload();
|
|
429
|
+
}
|
|
430
|
+
async function redeemInvite(code) {
|
|
431
|
+
if (!client.value) throw new Error("Not connected");
|
|
432
|
+
await client.value.redeemInvite(code);
|
|
433
|
+
addLog(`Invite redeemed: ${code}`, "auth");
|
|
434
|
+
const pubKey = publicKeyB64.value;
|
|
435
|
+
if (pubKey) {
|
|
436
|
+
try {
|
|
437
|
+
const ed = await import("@noble/ed25519");
|
|
438
|
+
const storedPrivKey = localStorage.getItem("abracadabra_privkey");
|
|
439
|
+
await client.value.loginWithKey(pubKey, async (ch) => {
|
|
440
|
+
if (isClaimed.value && keystore.value) return keystore.value.sign(ch);
|
|
441
|
+
if (storedPrivKey) {
|
|
442
|
+
const privKey = fromBase64Url(storedPrivKey);
|
|
443
|
+
const sig = await ed.sign(fromBase64Url(ch), privKey);
|
|
444
|
+
return toBase64Url(sig);
|
|
445
|
+
}
|
|
446
|
+
throw new Error("No signing key available");
|
|
447
|
+
});
|
|
448
|
+
if (_wsp) {
|
|
449
|
+
_wsp.disconnect();
|
|
450
|
+
setTimeout(() => _wsp?.connect(), 300);
|
|
451
|
+
}
|
|
452
|
+
} catch {
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async function createInvite(opts) {
|
|
457
|
+
if (!client.value) throw new Error("Not connected");
|
|
458
|
+
return client.value.createInvite(opts);
|
|
459
|
+
}
|
|
460
|
+
async function listInvites() {
|
|
461
|
+
if (!client.value) throw new Error("Not connected");
|
|
462
|
+
return client.value.listInvites();
|
|
463
|
+
}
|
|
464
|
+
async function revokeInvite(code) {
|
|
465
|
+
if (!client.value) throw new Error("Not connected");
|
|
466
|
+
return client.value.revokeInvite(code);
|
|
467
|
+
}
|
|
468
|
+
async function init(serverUrl) {
|
|
469
|
+
currentServerUrl.value = serverUrl;
|
|
470
|
+
addLog("Initializing Abracadabra...", "system");
|
|
471
|
+
if (!pendingInviteCode.value) {
|
|
472
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
473
|
+
const hashParams = new URLSearchParams(window.location.hash.replace(/^#/, ""));
|
|
474
|
+
const code = urlParams.get("invite") || hashParams.get("invite");
|
|
475
|
+
if (code) {
|
|
476
|
+
pendingInviteCode.value = code.toUpperCase();
|
|
477
|
+
addLog(`Invite code found in URL: ${pendingInviteCode.value}`, "auth");
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const [
|
|
482
|
+
{ AbracadabraClient, AbracadabraProvider, HocuspocusProviderWebsocket, CryptoIdentityKeystore },
|
|
483
|
+
ed,
|
|
484
|
+
{ sha512 }
|
|
485
|
+
] = await Promise.all([
|
|
486
|
+
import("@abraca/dabra"),
|
|
487
|
+
import("@noble/ed25519"),
|
|
488
|
+
import("@noble/hashes/sha512")
|
|
489
|
+
]);
|
|
490
|
+
ed.etc.sha512Sync = (m) => sha512(m);
|
|
491
|
+
const ks = new CryptoIdentityKeystore();
|
|
492
|
+
keystore.value = ks;
|
|
493
|
+
let privKey = null;
|
|
494
|
+
let pubKey;
|
|
495
|
+
const storedName = localStorage.getItem("abracadabra_username");
|
|
496
|
+
if (storedName) userName.value = storedName;
|
|
497
|
+
else localStorage.setItem("abracadabra_username", userName.value);
|
|
498
|
+
const wasPreviouslyClaimed = localStorage.getItem(CLAIMED_FLAG_KEY) === "1";
|
|
499
|
+
const storedPubKey = localStorage.getItem(PUBKEY_KEY);
|
|
500
|
+
requirePasskeyOnLogin.value = localStorage.getItem(REQUIRE_PASSKEY_KEY) === "1";
|
|
501
|
+
if (wasPreviouslyClaimed && storedPubKey) {
|
|
502
|
+
pubKey = storedPubKey;
|
|
503
|
+
isClaimed.value = true;
|
|
504
|
+
identityLost.value = false;
|
|
505
|
+
addLog("Restored claimed identity from cache", "auth");
|
|
506
|
+
} else if (wasPreviouslyClaimed && !storedPubKey) {
|
|
507
|
+
identityLost.value = true;
|
|
508
|
+
isClaimed.value = false;
|
|
509
|
+
addLog("Previously claimed account \u2014 cached key missing, waiting for recovery", "auth");
|
|
510
|
+
publicKeyB64.value = "";
|
|
511
|
+
return;
|
|
512
|
+
} else {
|
|
513
|
+
const storedPrivKey = localStorage.getItem("abracadabra_privkey");
|
|
514
|
+
if (storedPrivKey) {
|
|
515
|
+
privKey = fromBase64Url(storedPrivKey);
|
|
516
|
+
addLog("Using guest identity (soft key)", "auth");
|
|
517
|
+
} else {
|
|
518
|
+
privKey = ed.utils.randomPrivateKey();
|
|
519
|
+
localStorage.setItem("abracadabra_privkey", toBase64Url(privKey));
|
|
520
|
+
addLog("Created new guest identity (soft key)", "auth");
|
|
521
|
+
}
|
|
522
|
+
isClaimed.value = false;
|
|
523
|
+
identityLost.value = false;
|
|
524
|
+
const pubKeyBytes = ed.getPublicKey(privKey);
|
|
525
|
+
pubKey = toBase64Url(pubKeyBytes);
|
|
526
|
+
}
|
|
527
|
+
publicKeyB64.value = pubKey;
|
|
528
|
+
const storedColor = localStorage.getItem("abracadabra_usercolor");
|
|
529
|
+
const storedNeutral = localStorage.getItem("abracadabra_neutralcolor");
|
|
530
|
+
const derived = deriveColorFromPubKey(pubKey);
|
|
531
|
+
if (storedColor && UI_COLORS.includes(storedColor)) {
|
|
532
|
+
const h = COLOR_HUES[storedColor] ?? 0;
|
|
533
|
+
userColor.value = `hsl(${h}, 70%, 75%)`;
|
|
534
|
+
userColorName.value = storedColor;
|
|
535
|
+
} else {
|
|
536
|
+
userColor.value = derived.hsl;
|
|
537
|
+
userColorName.value = derived.name;
|
|
538
|
+
}
|
|
539
|
+
if (storedNeutral && UI_NEUTRALS.includes(storedNeutral)) {
|
|
540
|
+
userNeutralColorName.value = storedNeutral;
|
|
541
|
+
} else {
|
|
542
|
+
userNeutralColorName.value = derived.neutralName;
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
const appConfig = useAppConfig();
|
|
546
|
+
if (appConfig.ui?.colors) {
|
|
547
|
+
appConfig.ui.colors.primary = userColorName.value;
|
|
548
|
+
appConfig.ui.colors.neutral = userNeutralColorName.value;
|
|
549
|
+
}
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
addLog(`Public key: ${pubKey.slice(0, 16)}...`, "auth");
|
|
553
|
+
const signChallengeFn = async (challenge) => {
|
|
554
|
+
if (isClaimed.value) return await ks.sign(challenge);
|
|
555
|
+
if (privKey) {
|
|
556
|
+
const sig = await ed.sign(fromBase64Url(challenge), privKey);
|
|
557
|
+
return toBase64Url(sig);
|
|
558
|
+
}
|
|
559
|
+
throw new Error("No identity available for signing");
|
|
560
|
+
};
|
|
561
|
+
const _client = new AbracadabraClient({ url: serverUrl, persistAuth, storageKey: authStorageKey });
|
|
562
|
+
client.value = _client;
|
|
563
|
+
addLog(`Server: ${serverUrl}`, "connection");
|
|
564
|
+
if (_client.isAuthenticated && isClaimed.value) {
|
|
565
|
+
addLog("Resumed session from persisted token", "auth");
|
|
566
|
+
} else if (isClaimed.value && !requirePasskeyOnLogin.value) {
|
|
567
|
+
needsReauth.value = true;
|
|
568
|
+
addLog("Session expired \u2014 passkey sign-in available", "auth");
|
|
569
|
+
} else {
|
|
570
|
+
needsReauth.value = false;
|
|
571
|
+
try {
|
|
572
|
+
await _client.registerWithKey({
|
|
573
|
+
publicKey: pubKey,
|
|
574
|
+
displayName: userName.value,
|
|
575
|
+
inviteCode: pendingInviteCode.value ?? void 0
|
|
576
|
+
});
|
|
577
|
+
addLog("Registered with crypto identity", "auth");
|
|
578
|
+
if (pendingInviteCode.value) {
|
|
579
|
+
addLog("Invite code applied during registration", "auth");
|
|
580
|
+
pendingInviteCode.value = null;
|
|
581
|
+
}
|
|
582
|
+
} catch (e) {
|
|
583
|
+
if (!e.message?.includes("already exists")) addLog(`Register: ${e.message}`, "auth");
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
const token = await _client.loginWithKey(pubKey, (ch) => signChallengeFn(ch));
|
|
587
|
+
addLog("Authenticated via Ed25519 challenge-response", "auth");
|
|
588
|
+
addLog(`JWT obtained (${token.slice(0, 12)}...)`, "auth");
|
|
589
|
+
} catch (e) {
|
|
590
|
+
addLog(`Login failed: ${e.message}`, "auth");
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
_client.updateMe({ displayName: userName.value }).catch(() => {
|
|
594
|
+
});
|
|
595
|
+
const [info, spacesInfo] = await Promise.all([
|
|
596
|
+
fetchServerInfo(serverUrl),
|
|
597
|
+
fetchSpacesInfo(serverUrl)
|
|
598
|
+
]);
|
|
599
|
+
const idx = savedServers.value.findIndex((s) => s.url === serverUrl);
|
|
600
|
+
if (idx >= 0) {
|
|
601
|
+
const updates = {};
|
|
602
|
+
if (info.name && info.name !== savedServers.value[idx].label) updates.label = info.name;
|
|
603
|
+
if (spacesInfo) {
|
|
604
|
+
updates.spacesEnabled = true;
|
|
605
|
+
updates.cachedSpaces = spacesInfo.spaces;
|
|
606
|
+
if (spacesInfo.hubDocId) updates.hubDocId = spacesInfo.hubDocId;
|
|
607
|
+
}
|
|
608
|
+
if (Object.keys(updates).length) {
|
|
609
|
+
savedServers.value[idx] = { ...savedServers.value[idx], ...updates };
|
|
610
|
+
persistServers();
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const server = savedServers.value.find((s) => s.url === serverUrl);
|
|
614
|
+
let docId = configEntryDocId ?? server?.hubDocId ?? spacesInfo?.hubDocId ?? server?.cachedSpaces?.[0]?.doc_id ?? spacesInfo?.spaces?.[0]?.doc_id ?? server?.entryDocId ?? info.index_doc_id ?? void 0;
|
|
615
|
+
if (!docId) {
|
|
616
|
+
connectionError.value = "No entry document found. Configure entryDocId or ensure the server has spaces or an index document.";
|
|
617
|
+
addLog("No entry document \u2014 cannot connect", "system");
|
|
618
|
+
try {
|
|
619
|
+
const { useToast } = await import("#imports");
|
|
620
|
+
useToast().add({ title: "Server not configured", description: "No entry document found. Set entryDocId in your abracadabra module config.", color: "error" });
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
addLog(`Using document: ${docId}`, "data");
|
|
626
|
+
const wsUrl = _client.wsUrl;
|
|
627
|
+
addLog(`WebSocket: ${wsUrl}`, "connection");
|
|
628
|
+
const wsp = new HocuspocusProviderWebsocket({
|
|
629
|
+
url: wsUrl,
|
|
630
|
+
delay: 1e3,
|
|
631
|
+
minDelay: 1e3,
|
|
632
|
+
maxDelay: 3e4,
|
|
633
|
+
factor: 2,
|
|
634
|
+
jitter: true,
|
|
635
|
+
maxAttempts: 0
|
|
636
|
+
});
|
|
637
|
+
_wsp = wsp;
|
|
638
|
+
const _doc = new Y.Doc();
|
|
639
|
+
doc.value = _doc;
|
|
640
|
+
let wasConnected = false;
|
|
641
|
+
const _provider = new AbracadabraProvider({
|
|
642
|
+
name: docId,
|
|
643
|
+
document: _doc,
|
|
644
|
+
websocketProvider: wsp,
|
|
645
|
+
client: _client,
|
|
646
|
+
onStatus({ status: s }) {
|
|
647
|
+
if (s === status.value) return;
|
|
648
|
+
const oldStatus = status.value;
|
|
649
|
+
status.value = s;
|
|
650
|
+
if (s === "connected") {
|
|
651
|
+
connectionError.value = null;
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (s === "connecting" && oldStatus === "disconnected") return;
|
|
655
|
+
if (s === "disconnected" && oldStatus === "connecting") return;
|
|
656
|
+
addLog(`WebSocket: ${s}`, "connection");
|
|
657
|
+
},
|
|
658
|
+
onSynced({ state }) {
|
|
659
|
+
const wasSynced = synced.value;
|
|
660
|
+
synced.value = state;
|
|
661
|
+
if (state) {
|
|
662
|
+
addLog("Document synced", "sync");
|
|
663
|
+
isReady.value = true;
|
|
664
|
+
if (wasConnected && !wasSynced) {
|
|
665
|
+
addLog("Reconnected", "connection");
|
|
666
|
+
try {
|
|
667
|
+
import("#imports").then(({ useToast }) => {
|
|
668
|
+
useToast().add({ title: "Reconnected", color: "success", duration: 2e3 });
|
|
669
|
+
}).catch(() => {
|
|
670
|
+
});
|
|
671
|
+
} catch {
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
wasConnected = true;
|
|
675
|
+
if (!wasSynced && features.backgroundSync !== false) {
|
|
676
|
+
import("@abraca/dabra").then(({ BackgroundSyncManager }) => {
|
|
677
|
+
if (_client && _provider) {
|
|
678
|
+
const manager = new BackgroundSyncManager(_client, _provider);
|
|
679
|
+
_initBackgroundSync(manager);
|
|
680
|
+
if (features.search !== false) {
|
|
681
|
+
_initSearchIndex(serverUrl);
|
|
682
|
+
_initFileIndex();
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}).catch(() => {
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
_provider.sendStateless?.(JSON.stringify({ type: "notify:fetch", limit: 20 }));
|
|
690
|
+
} catch {
|
|
691
|
+
}
|
|
692
|
+
try {
|
|
693
|
+
const trashMap = _doc.getMap("doc-trash");
|
|
694
|
+
const settings = (() => {
|
|
695
|
+
try {
|
|
696
|
+
return JSON.parse(localStorage.getItem("abracadabra_trash_settings") || "{}");
|
|
697
|
+
} catch {
|
|
698
|
+
return {};
|
|
699
|
+
}
|
|
700
|
+
})();
|
|
701
|
+
const purgeDays = settings.autoPurgeDays ?? 30;
|
|
702
|
+
if (purgeDays > 0) {
|
|
703
|
+
const cutoff = Date.now() - purgeDays * 24 * 60 * 60 * 1e3;
|
|
704
|
+
const expired = [];
|
|
705
|
+
trashMap.forEach((val, key) => {
|
|
706
|
+
if (val?.deletedAt < cutoff) expired.push(key);
|
|
707
|
+
});
|
|
708
|
+
if (expired.length) _doc.transact(() => {
|
|
709
|
+
for (const id of expired) trashMap.delete(id);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
} catch {
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
onAuthenticated({ scope }) {
|
|
717
|
+
addLog(`Scope: ${scope}`, "auth");
|
|
718
|
+
connectionError.value = null;
|
|
719
|
+
authFailureCount = 0;
|
|
720
|
+
},
|
|
721
|
+
onAuthenticationFailed({ reason }) {
|
|
722
|
+
authFailureCount++;
|
|
723
|
+
const msg = reason || "Authentication failed";
|
|
724
|
+
const isPermanent = /disabled|not allowed|forbidden|revoked/i.test(msg);
|
|
725
|
+
if (isPermanent || authFailureCount >= AUTH_FAILURE_LIMIT) {
|
|
726
|
+
isConnectionBlocked.value = true;
|
|
727
|
+
connectionError.value = `${msg} \u2014 reconnection paused`;
|
|
728
|
+
addLog(`Auth denied \u2014 pausing reconnect: ${msg}`, "auth");
|
|
729
|
+
wsp.disconnect();
|
|
730
|
+
} else {
|
|
731
|
+
connectionError.value = msg;
|
|
732
|
+
addLog(`Denied: ${msg}`, "auth");
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
onAwarenessChange({ states }) {
|
|
736
|
+
const currentIds = new Set(states.map((s) => s.clientId));
|
|
737
|
+
for (const id of currentIds) {
|
|
738
|
+
const state = states.find((s) => s.clientId === id);
|
|
739
|
+
const name = state?.user?.name || "Anonymous";
|
|
740
|
+
clientNameCache.set(id, name);
|
|
741
|
+
if (!lastClientIds.has(id) && id !== _provider?.awareness?.clientID) {
|
|
742
|
+
addLog(`${name} joined (${states.length} online)`, "user");
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
for (const id of lastClientIds) {
|
|
746
|
+
if (!currentIds.has(id)) {
|
|
747
|
+
const name = clientNameCache.get(id) || "User";
|
|
748
|
+
addLog(`${name} left (${states.length} online)`, "user");
|
|
749
|
+
clientNameCache.delete(id);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
lastClientIds = currentIds;
|
|
753
|
+
userCount.value = states.length;
|
|
754
|
+
},
|
|
755
|
+
onDisconnect() {
|
|
756
|
+
if (status.value === "connected") {
|
|
757
|
+
addLog("Disconnected", "connection");
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
_provider.setAwarenessField("user", { name: userName.value, color: userColor.value, publicKey: publicKeyB64.value });
|
|
762
|
+
_provider.attach();
|
|
763
|
+
provider.value = _provider;
|
|
764
|
+
try {
|
|
765
|
+
const { FileBlobStore } = await import("@abraca/dabra");
|
|
766
|
+
const blobStore = new FileBlobStore(_client);
|
|
767
|
+
_initFileBlobStore(blobStore);
|
|
768
|
+
} catch {
|
|
769
|
+
}
|
|
770
|
+
_initNotifications(publicKeyB64);
|
|
771
|
+
_initChat(publicKeyB64, userName);
|
|
772
|
+
if (_provider.onStateless !== void 0) {
|
|
773
|
+
_provider.onStateless = (payload) => {
|
|
774
|
+
_handleStatelessNotification(payload);
|
|
775
|
+
_handleStatelessChat(payload);
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
_provider.ready.then(() => {
|
|
779
|
+
isReady.value = true;
|
|
780
|
+
}, () => null);
|
|
781
|
+
watch(
|
|
782
|
+
[userName, userColor, publicKeyB64],
|
|
783
|
+
() => {
|
|
784
|
+
_provider.setAwarenessField("user", { name: userName.value, color: userColor.value, publicKey: publicKeyB64.value });
|
|
785
|
+
},
|
|
786
|
+
{ immediate: true }
|
|
787
|
+
);
|
|
788
|
+
addLog("Provider attached, connecting...", "connection");
|
|
789
|
+
} catch (e) {
|
|
790
|
+
addLog(`Error: ${e.message}`, "system");
|
|
791
|
+
console.error("[abracadabra] init error:", e);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const abra = {
|
|
795
|
+
doc,
|
|
796
|
+
provider,
|
|
797
|
+
client,
|
|
798
|
+
keystore,
|
|
799
|
+
status,
|
|
800
|
+
synced,
|
|
801
|
+
isReady,
|
|
802
|
+
logs,
|
|
803
|
+
userName,
|
|
804
|
+
userColor,
|
|
805
|
+
userColorName,
|
|
806
|
+
userNeutralColorName,
|
|
807
|
+
userCount,
|
|
808
|
+
publicKeyB64,
|
|
809
|
+
connectionError,
|
|
810
|
+
isClaimed,
|
|
811
|
+
identityLost,
|
|
812
|
+
needsReauth,
|
|
813
|
+
requirePasskeyOnLogin,
|
|
814
|
+
isConnectionBlocked,
|
|
815
|
+
currentServerUrl,
|
|
816
|
+
savedServers,
|
|
817
|
+
currentServerSpaces,
|
|
818
|
+
currentServerSpacesEnabled,
|
|
819
|
+
pendingInviteCode,
|
|
820
|
+
registry,
|
|
821
|
+
addLog,
|
|
822
|
+
setUserName,
|
|
823
|
+
setUserColor,
|
|
824
|
+
setNeutralColor,
|
|
825
|
+
setRequirePasskey,
|
|
826
|
+
claimAccount,
|
|
827
|
+
loginWithHardware,
|
|
828
|
+
logout,
|
|
829
|
+
reconnect,
|
|
830
|
+
addServer,
|
|
831
|
+
removeServer,
|
|
832
|
+
switchServer,
|
|
833
|
+
switchSpace,
|
|
834
|
+
refreshSpaces,
|
|
835
|
+
createSpace,
|
|
836
|
+
updateSpace,
|
|
837
|
+
deleteSpace,
|
|
838
|
+
redeemInvite,
|
|
839
|
+
createInvite,
|
|
840
|
+
listInvites,
|
|
841
|
+
revokeInvite,
|
|
842
|
+
init
|
|
843
|
+
};
|
|
844
|
+
nuxtApp.provide("abracadabra", abra);
|
|
845
|
+
if (debug) {
|
|
846
|
+
watch(abra.status, (s) => console.log("[abracadabra] status \u2192", s));
|
|
847
|
+
watch(abra.synced, (s) => console.log("[abracadabra] synced \u2192", s));
|
|
848
|
+
}
|
|
849
|
+
loadServers();
|
|
850
|
+
const storedServer = localStorage.getItem(CURRENT_SERVER_KEY);
|
|
851
|
+
const initialUrl = storedServer && savedServers.value.some((s) => s.url === storedServer) ? storedServer : currentServerUrl.value || savedServers.value[0]?.url || defaultUrl;
|
|
852
|
+
if (initialUrl !== currentServerUrl.value) currentServerUrl.value = initialUrl;
|
|
853
|
+
_initPromise = init(initialUrl);
|
|
854
|
+
_initPromise.then(async () => {
|
|
855
|
+
if (!provider.value) return;
|
|
856
|
+
const ctx = { abracadabra: abra };
|
|
857
|
+
const teardowns = [];
|
|
858
|
+
for (const plugin of registry.getPlugins()) {
|
|
859
|
+
if (plugin.clientSetup) {
|
|
860
|
+
try {
|
|
861
|
+
const result = await plugin.clientSetup(ctx);
|
|
862
|
+
if (typeof result === "function") teardowns.push(result);
|
|
863
|
+
} catch (e) {
|
|
864
|
+
console.error(`[abracadabra] Plugin "${plugin.name}" clientSetup failed:`, e);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
const shortcuts = registry.getAllKeyboardShortcuts();
|
|
869
|
+
if (shortcuts.length > 0) {
|
|
870
|
+
const handleKey = (e) => {
|
|
871
|
+
for (const s of shortcuts) {
|
|
872
|
+
const parts = s.key.split("+");
|
|
873
|
+
const key = parts[parts.length - 1].toLowerCase();
|
|
874
|
+
const needsMeta = parts.includes("Meta") || parts.includes("Cmd");
|
|
875
|
+
const needsCtrl = parts.includes("Control") || parts.includes("Ctrl");
|
|
876
|
+
const needsShift = parts.includes("Shift");
|
|
877
|
+
const needsAlt = parts.includes("Alt");
|
|
878
|
+
if (e.key.toLowerCase() === key && (!needsMeta || e.metaKey || e.ctrlKey) && (!needsCtrl || e.ctrlKey) && (!needsShift || e.shiftKey) && (!needsAlt || e.altKey)) {
|
|
879
|
+
e.preventDefault();
|
|
880
|
+
s.handler();
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
document.addEventListener("keydown", handleKey);
|
|
885
|
+
nuxtApp.hook("app:beforeMount", () => {
|
|
886
|
+
nuxtApp.hook("app:suspense:resolve", () => {
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
const origUnmount = nuxtApp._cleanup;
|
|
890
|
+
nuxtApp._cleanup = () => {
|
|
891
|
+
document.removeEventListener("keydown", handleKey);
|
|
892
|
+
teardowns.forEach((fn) => fn());
|
|
893
|
+
origUnmount?.();
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
}).catch((e) => console.error("[abracadabra] boot error:", e));
|
|
897
|
+
}
|
|
898
|
+
});
|