@abraca/nuxt 2.0.0 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/module.d.mts +18 -7
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +21 -5
  4. package/dist/runtime/assets/aware-tokens.css +1 -0
  5. package/dist/runtime/components/AAccountSwitcherModal.d.vue.ts +16 -1
  6. package/dist/runtime/components/AAccountSwitcherModal.vue +33 -4
  7. package/dist/runtime/components/AAccountSwitcherModal.vue.d.ts +16 -1
  8. package/dist/runtime/components/AAuthLinkLanding.d.vue.ts +3 -0
  9. package/dist/runtime/components/AAuthLinkLanding.vue +85 -0
  10. package/dist/runtime/components/AAuthLinkLanding.vue.d.ts +3 -0
  11. package/dist/runtime/components/AClaimAccountModal.d.vue.ts +7 -1
  12. package/dist/runtime/components/AClaimAccountModal.vue +28 -13
  13. package/dist/runtime/components/AClaimAccountModal.vue.d.ts +7 -1
  14. package/dist/runtime/components/AEditor.vue +5 -0
  15. package/dist/runtime/components/AEmailVerifyConfirmModal.d.vue.ts +30 -0
  16. package/dist/runtime/components/AEmailVerifyConfirmModal.vue +100 -0
  17. package/dist/runtime/components/AEmailVerifyConfirmModal.vue.d.ts +30 -0
  18. package/dist/runtime/components/AEmailVerifyRequestCard.d.vue.ts +22 -0
  19. package/dist/runtime/components/AEmailVerifyRequestCard.vue +65 -0
  20. package/dist/runtime/components/AEmailVerifyRequestCard.vue.d.ts +22 -0
  21. package/dist/runtime/components/AMnemonicLoginModal.d.vue.ts +1 -1
  22. package/dist/runtime/components/AMnemonicLoginModal.vue.d.ts +1 -1
  23. package/dist/runtime/components/ANodePanel.vue +2 -0
  24. package/dist/runtime/components/ANotificationBell.d.vue.ts +2 -2
  25. package/dist/runtime/components/ANotificationBell.vue.d.ts +2 -2
  26. package/dist/runtime/components/APasswordChangeModal.d.vue.ts +28 -0
  27. package/dist/runtime/components/APasswordChangeModal.vue +178 -0
  28. package/dist/runtime/components/APasswordChangeModal.vue.d.ts +28 -0
  29. package/dist/runtime/components/APasswordLoginModal.d.vue.ts +42 -0
  30. package/dist/runtime/components/APasswordLoginModal.vue +177 -0
  31. package/dist/runtime/components/APasswordLoginModal.vue.d.ts +42 -0
  32. package/dist/runtime/components/APasswordRegisterModal.d.vue.ts +49 -0
  33. package/dist/runtime/components/APasswordRegisterModal.vue +262 -0
  34. package/dist/runtime/components/APasswordRegisterModal.vue.d.ts +49 -0
  35. package/dist/runtime/components/APasswordResetConfirmModal.d.vue.ts +31 -0
  36. package/dist/runtime/components/APasswordResetConfirmModal.vue +154 -0
  37. package/dist/runtime/components/APasswordResetConfirmModal.vue.d.ts +31 -0
  38. package/dist/runtime/components/APasswordResetRequestModal.d.vue.ts +35 -0
  39. package/dist/runtime/components/APasswordResetRequestModal.vue +113 -0
  40. package/dist/runtime/components/APasswordResetRequestModal.vue.d.ts +35 -0
  41. package/dist/runtime/components/ASetPasswordCard.d.vue.ts +26 -0
  42. package/dist/runtime/components/ASetPasswordCard.vue +139 -0
  43. package/dist/runtime/components/ASetPasswordCard.vue.d.ts +26 -0
  44. package/dist/runtime/components/ASubPageList.d.vue.ts +66 -0
  45. package/dist/runtime/components/ASubPageList.vue +147 -0
  46. package/dist/runtime/components/ASubPageList.vue.d.ts +66 -0
  47. package/dist/runtime/components/aware/AAccordion.d.vue.ts +2 -0
  48. package/dist/runtime/components/aware/AAccordion.vue +11 -1
  49. package/dist/runtime/components/aware/AAccordion.vue.d.ts +2 -0
  50. package/dist/runtime/components/aware/AButton.vue +3 -3
  51. package/dist/runtime/components/aware/ACollapsible.d.vue.ts +2 -0
  52. package/dist/runtime/components/aware/ACollapsible.vue +9 -1
  53. package/dist/runtime/components/aware/ACollapsible.vue.d.ts +2 -0
  54. package/dist/runtime/components/aware/AGlobalFocusLayer.vue +1 -1
  55. package/dist/runtime/components/aware/AHoverItem.vue +28 -3
  56. package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
  57. package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
  58. package/dist/runtime/components/aware/AModal.d.vue.ts +2 -0
  59. package/dist/runtime/components/aware/AModal.vue +9 -1
  60. package/dist/runtime/components/aware/AModal.vue.d.ts +2 -0
  61. package/dist/runtime/components/aware/APresenceBlobs.vue +1 -1
  62. package/dist/runtime/components/aware/APresenceCursors.vue +1 -1
  63. package/dist/runtime/components/aware/AScroll.d.vue.ts +2 -0
  64. package/dist/runtime/components/aware/AScroll.vue +13 -3
  65. package/dist/runtime/components/aware/AScroll.vue.d.ts +2 -0
  66. package/dist/runtime/components/aware/ASlideover.d.vue.ts +2 -0
  67. package/dist/runtime/components/aware/ASlideover.vue +9 -1
  68. package/dist/runtime/components/aware/ASlideover.vue.d.ts +2 -0
  69. package/dist/runtime/components/aware/ASlider.vue +1 -0
  70. package/dist/runtime/components/aware/ATabs.d.vue.ts +2 -0
  71. package/dist/runtime/components/aware/ATabs.vue +9 -1
  72. package/dist/runtime/components/aware/ATabs.vue.d.ts +2 -0
  73. package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -0
  74. package/dist/runtime/components/editor/AEditorRedoButton.d.vue.ts +2 -2
  75. package/dist/runtime/components/editor/AEditorRedoButton.vue.d.ts +2 -2
  76. package/dist/runtime/components/editor/AEditorUndoButton.d.vue.ts +2 -2
  77. package/dist/runtime/components/editor/AEditorUndoButton.vue.d.ts +2 -2
  78. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +4 -4
  79. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +4 -4
  80. package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
  81. package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
  82. package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
  83. package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
  84. package/dist/runtime/composables/useAAField.js +7 -4
  85. package/dist/runtime/composables/useAAFocus.js +10 -5
  86. package/dist/runtime/composables/useAAFollowAnchor.js +68 -34
  87. package/dist/runtime/composables/useAAFollowPeer.d.ts +7 -4
  88. package/dist/runtime/composables/useAAFollowPeer.js +60 -11
  89. package/dist/runtime/composables/useAAViewport.d.ts +1 -1
  90. package/dist/runtime/composables/useAbracadabraAuth.d.ts +2 -0
  91. package/dist/runtime/composables/useAbracadabraAuth.js +2 -0
  92. package/dist/runtime/composables/useEditorSuggestions.js +2 -1
  93. package/dist/runtime/composables/useEmailVerification.d.ts +40 -26
  94. package/dist/runtime/composables/useEmailVerification.js +95 -43
  95. package/dist/runtime/composables/usePasswordAuth.d.ts +64 -0
  96. package/dist/runtime/composables/usePasswordAuth.js +126 -0
  97. package/dist/runtime/composables/useTiptapHistory.d.ts +2 -2
  98. package/dist/runtime/composables/useTiptapHistory.js +5 -5
  99. package/dist/runtime/extensions/svg-embed.d.ts +23 -0
  100. package/dist/runtime/extensions/svg-embed.js +33 -0
  101. package/dist/runtime/extensions/views/MetaFieldView.vue +23 -6
  102. package/dist/runtime/extensions/views/SvgEmbedView.d.vue.ts +4 -0
  103. package/dist/runtime/extensions/views/SvgEmbedView.vue +120 -0
  104. package/dist/runtime/extensions/views/SvgEmbedView.vue.d.ts +4 -0
  105. package/dist/runtime/plugin-abracadabra.client.js +58 -9
  106. package/dist/runtime/plugin-abracadabra.server.js +2 -0
  107. package/dist/runtime/plugins/core.plugin.js +8 -4
  108. package/dist/runtime/server/plugins/abracadabra-service.js +102 -13
  109. package/dist/runtime/types.d.ts +11 -0
  110. package/dist/runtime/utils/awareRingStyle.js +1 -1
  111. package/dist/runtime/utils/sanitizeSvg.d.ts +19 -0
  112. package/dist/runtime/utils/sanitizeSvg.js +87 -0
  113. package/package.json +7 -8
  114. package/dist/runtime/components/renderers/ASpatialRenderer.d.vue.ts +0 -19
  115. package/dist/runtime/components/renderers/ASpatialRenderer.vue +0 -459
  116. package/dist/runtime/components/renderers/ASpatialRenderer.vue.d.ts +0 -19
  117. package/dist/runtime/components/renderers/spatial/SpatialGround.d.vue.ts +0 -20
  118. package/dist/runtime/components/renderers/spatial/SpatialGround.vue +0 -26
  119. package/dist/runtime/components/renderers/spatial/SpatialGround.vue.d.ts +0 -20
  120. package/dist/runtime/components/renderers/spatial/SpatialObject.d.vue.ts +0 -17
  121. package/dist/runtime/components/renderers/spatial/SpatialObject.vue +0 -257
  122. package/dist/runtime/components/renderers/spatial/SpatialObject.vue.d.ts +0 -17
  123. package/dist/runtime/components/renderers/spatial/SpatialSceneBridge.d.vue.ts +0 -15
  124. package/dist/runtime/components/renderers/spatial/SpatialSceneBridge.vue +0 -18
  125. package/dist/runtime/components/renderers/spatial/SpatialSceneBridge.vue.d.ts +0 -15
  126. package/dist/runtime/components/renderers/spatial/SpatialTransformInputs.d.vue.ts +0 -16
  127. package/dist/runtime/components/renderers/spatial/SpatialTransformInputs.vue +0 -66
  128. package/dist/runtime/components/renderers/spatial/SpatialTransformInputs.vue.d.ts +0 -16
  129. package/dist/runtime/components/renderers/spatial/SpatialUserAvatar.d.vue.ts +0 -8
  130. package/dist/runtime/components/renderers/spatial/SpatialUserAvatar.vue +0 -53
  131. package/dist/runtime/components/renderers/spatial/SpatialUserAvatar.vue.d.ts +0 -8
  132. package/dist/runtime/composables/useSpatialCamera.d.ts +0 -16
  133. package/dist/runtime/composables/useSpatialCamera.js +0 -175
  134. package/dist/runtime/composables/useSpatialDrag.d.ts +0 -14
  135. package/dist/runtime/composables/useSpatialDrag.js +0 -137
@@ -70,6 +70,17 @@ const NUXT_UI_PRIMARY_COLORS = /* @__PURE__ */ new Set([
70
70
  ]);
71
71
  const NUXT_UI_NEUTRAL_COLORS = /* @__PURE__ */ new Set(["slate", "gray", "zinc", "neutral", "stone"]);
72
72
  const CUSTOM_TO_NUXT_UI_PRIMARY = {
73
+ // Identity-color names from `UI_COLORS` (types.ts) that aren't Nuxt UI standard.
74
+ // Without these, picking any of `coral`, `chartreuse`, `jade`, `sapphire`,
75
+ // `magenta`, `oxidized` would silently fall back to `blue` — and the app
76
+ // chrome would never reflect the user's chosen identity color.
77
+ coral: "orange",
78
+ chartreuse: "lime",
79
+ jade: "emerald",
80
+ sapphire: "blue",
81
+ magenta: "fuchsia",
82
+ oxidized: "teal",
83
+ // Legacy / themed names sometimes used as `primary` in app.config.ts.
73
84
  grass: "green",
74
85
  diamond: "cyan",
75
86
  gold: "amber",
@@ -78,7 +89,26 @@ const CUSTOM_TO_NUXT_UI_PRIMARY = {
78
89
  wood: "orange",
79
90
  discord: "indigo",
80
91
  steam: "lime",
81
- oxidized: "teal"
92
+ // Neutral names occasionally mis-used as primary (e.g. playground default
93
+ // `primary: 'mist'`) — pick a defensible warm-neutral primary instead of
94
+ // collapsing to plain blue.
95
+ mist: "sky",
96
+ mauve: "violet",
97
+ sand: "amber",
98
+ dusk: "indigo",
99
+ bark: "orange",
100
+ sage: "emerald",
101
+ moss: "green",
102
+ lavender: "violet",
103
+ blush: "pink",
104
+ cream: "amber",
105
+ wine: "rose",
106
+ cobblestone: "cyan",
107
+ bedrock: "sky",
108
+ copper: "orange",
109
+ mint: "emerald",
110
+ fog: "sky",
111
+ peach: "orange"
82
112
  };
83
113
  const CUSTOM_TO_NUXT_UI_NEUTRAL = {
84
114
  cobblestone: "stone",
@@ -241,6 +271,16 @@ export default defineNuxtPlugin({
241
271
  const userStatusIcon = ref(localStorage.getItem("abracadabra_status_icon") ?? "");
242
272
  const userStatusText = ref(localStorage.getItem("abracadabra_status_text") ?? "");
243
273
  const userStatusAsAvatar = ref(localStorage.getItem("abracadabra_status_as_avatar") === "1");
274
+ const hasPassword = ref(false);
275
+ async function refreshHasPassword() {
276
+ const c = client.value;
277
+ if (!c) return;
278
+ try {
279
+ const me = await c.getMe();
280
+ hasPassword.value = !!me?.hasPassword;
281
+ } catch {
282
+ }
283
+ }
244
284
  watch([status, synced], ([s, sy]) => {
245
285
  try {
246
286
  const ready = s === "connected" && sy;
@@ -597,6 +637,7 @@ export default defineNuxtPlugin({
597
637
  if (!client.value) throw new Error("Not connected");
598
638
  await client.value.login(opts);
599
639
  addLog(`Logged in as ${opts.username}`, "auth");
640
+ hasPassword.value = true;
600
641
  if (_wsp) {
601
642
  _wsp.disconnect();
602
643
  setTimeout(() => _wsp?.connect(), 300);
@@ -607,6 +648,7 @@ export default defineNuxtPlugin({
607
648
  await client.value.register(opts);
608
649
  await client.value.login({ username: opts.username, password: opts.password });
609
650
  addLog(`Registered + logged in as ${opts.username}`, "auth");
651
+ hasPassword.value = true;
610
652
  if (_wsp) {
611
653
  _wsp.disconnect();
612
654
  setTimeout(() => _wsp?.connect(), 300);
@@ -625,6 +667,7 @@ export default defineNuxtPlugin({
625
667
  async function setPassword(newPassword) {
626
668
  if (!client.value) throw new Error("Not connected");
627
669
  await client.value.setPassword(newPassword);
670
+ hasPassword.value = true;
628
671
  addLog("Password set", "auth");
629
672
  }
630
673
  async function redeemInvite(code) {
@@ -705,18 +748,21 @@ export default defineNuxtPlugin({
705
748
  try {
706
749
  const [
707
750
  sdkModule,
708
- ed,
751
+ edModule,
709
752
  { sha512 }
710
753
  ] = await Promise.all([
711
754
  import("@abraca/dabra"),
712
755
  import("@noble/ed25519"),
713
- import("@noble/hashes/sha512")
756
+ import("@noble/hashes/sha2.js")
714
757
  ]);
715
758
  setSdkModule(sdkModule);
716
759
  const { AbracadabraClient, AbracadabraProvider, AbracadabraWS, CryptoIdentityKeystore } = sdkModule;
717
- const edEtc = ed.etc;
718
- edEtc.sha512Sync = (...m) => sha512(edEtc.concatBytes(...m));
719
- edEtc.sha512Async = (...m) => Promise.resolve(edEtc.sha512Sync(...m));
760
+ const ed = edModule.default ?? edModule;
761
+ if (!ed.hashes) {
762
+ throw new Error("@noble/ed25519 v3: `hashes` export missing from imported namespace \u2014 check Vite optimizeDeps");
763
+ }
764
+ ed.hashes.sha512 = sha512;
765
+ ed.hashes.sha512Async = (m) => Promise.resolve(sha512(m));
720
766
  const ks = new CryptoIdentityKeystore();
721
767
  keystore.value = ks;
722
768
  let privKey = null;
@@ -742,7 +788,7 @@ export default defineNuxtPlugin({
742
788
  privKey = fromBase64Url(storedPrivKey);
743
789
  addLog("Using guest identity (soft key)", "auth");
744
790
  } else {
745
- privKey = ed.utils.randomPrivateKey();
791
+ privKey = ed.utils.randomSecretKey();
746
792
  localStorage.setItem("abracadabra_privkey", toBase64Url(privKey));
747
793
  addLog("Created new guest identity (soft key)", "auth");
748
794
  }
@@ -790,7 +836,8 @@ export default defineNuxtPlugin({
790
836
  let useExistingToken = _client.isTokenValid();
791
837
  if (useExistingToken) {
792
838
  try {
793
- await _client.getMe();
839
+ const me = await _client.getMe();
840
+ hasPassword.value = !!me?.hasPassword;
794
841
  } catch (e) {
795
842
  const status2 = e?.status ?? 0;
796
843
  if (status2 === 401) {
@@ -860,7 +907,7 @@ export default defineNuxtPlugin({
860
907
  }
861
908
  }
862
909
  const server = savedServers.value.find((s) => s.url === serverUrl);
863
- const docId = configEntryDocId ?? server?.entryDocId ?? server?.cachedSpaces?.[0]?.id ?? spacesInfo?.spaces?.[0]?.id ?? void 0;
910
+ const docId = configEntryDocId ?? server?.entryDocId ?? server?.cachedSpaces?.[0]?.id ?? spacesInfo?.spaces?.[0]?.id ?? info?.root_doc_id ?? void 0;
864
911
  if (!docId) {
865
912
  connectionError.value = "No entry document found. Configure entryDocId or ensure the server has spaces.";
866
913
  addLog("No entry document \u2014 cannot connect", "system");
@@ -1140,6 +1187,8 @@ export default defineNuxtPlugin({
1140
1187
  confirmPasswordReset,
1141
1188
  changePassword,
1142
1189
  setPassword,
1190
+ hasPassword,
1191
+ refreshHasPassword,
1143
1192
  addServer,
1144
1193
  removeServer,
1145
1194
  switchServer,
@@ -68,6 +68,8 @@ export default defineNuxtPlugin({
68
68
  createInvite: async () => ({}),
69
69
  listInvites: async () => [],
70
70
  revokeInvite: noopAsync,
71
+ hasPassword: ref(false),
72
+ refreshHasPassword: noopAsync,
71
73
  init: noopAsync
72
74
  });
73
75
  }
@@ -15,7 +15,8 @@ const OPTIONAL_BUILTIN_EXTENSIONS = /* @__PURE__ */ new Set([
15
15
  "colorSwatch",
16
16
  "mathBlock",
17
17
  "mathInline",
18
- "diff"
18
+ "diff",
19
+ "svgEmbed"
19
20
  ]);
20
21
  async function loadClientExtensions() {
21
22
  const [
@@ -71,7 +72,8 @@ async function loadClientExtensions() {
71
72
  { Spoiler },
72
73
  { ColorSwatch },
73
74
  { MathBlock, MathInline },
74
- { Diff }
75
+ { Diff },
76
+ { SvgEmbed }
75
77
  ] = await Promise.all([
76
78
  import("@tiptap/extension-task-list"),
77
79
  import("@tiptap/extension-task-item"),
@@ -125,7 +127,8 @@ async function loadClientExtensions() {
125
127
  import("../extensions/spoiler.js"),
126
128
  import("../extensions/color-swatch.js"),
127
129
  import("../extensions/math.js"),
128
- import("../extensions/diff.js")
130
+ import("../extensions/diff.js"),
131
+ import("../extensions/svg-embed.js")
129
132
  ]);
130
133
  const lowlight = createLowlight(common);
131
134
  const extensions = [
@@ -198,7 +201,8 @@ async function loadClientExtensions() {
198
201
  ColorSwatch,
199
202
  MathBlock,
200
203
  MathInline,
201
- Diff
204
+ Diff,
205
+ SvgEmbed
202
206
  ];
203
207
  try {
204
208
  const emojiPkg = "@tiptap/extension-emoji";
@@ -1,6 +1,8 @@
1
1
  import { defineNitroPlugin } from "nitropack/runtime/plugin";
2
2
  import { useRuntimeConfig } from "nitropack/runtime/config";
3
3
  import { useStorage } from "nitropack/runtime/storage";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
4
6
  import { registerServerPlugin, bootRunners, shutdownAllRunners } from "../utils/serverRunner.js";
5
7
  import { createDocCacheAPI } from "../utils/docCache.js";
6
8
  import { docTreeCacheRunner } from "../runners/doc-tree-cache.js";
@@ -16,39 +18,125 @@ function toBase64Url(bytes) {
16
18
  for (const byte of bytes) b += String.fromCharCode(byte);
17
19
  return btoa(b).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
18
20
  }
21
+ async function loadOrCreateAutoIdentity(opts) {
22
+ if (existsSync(opts.cachePath)) {
23
+ try {
24
+ const cached = JSON.parse(readFileSync(opts.cachePath, "utf-8"));
25
+ if (cached?.publicKey && cached?.privateKey && cached?.serverUrl === opts.serverUrl) {
26
+ console.log("[abracadabra-service] using cached auto-bootstrap identity:", cached.username);
27
+ return cached;
28
+ }
29
+ } catch {
30
+ }
31
+ }
32
+ let info = null;
33
+ try {
34
+ const r = await fetch(`${opts.serverUrl}/info`);
35
+ if (r.ok) info = await r.json();
36
+ } catch {
37
+ }
38
+ if (!info) {
39
+ console.warn(`[abracadabra-service] auto-bootstrap aborted: ${opts.serverUrl}/info unreachable`);
40
+ return null;
41
+ }
42
+ if (!info.registration_allowed) {
43
+ console.warn(
44
+ "[abracadabra-service] auto-bootstrap aborted: server has registration_allowed=false. Set abracadabra.service.{publicKey,privateKey} module options to use a pre-registered service account."
45
+ );
46
+ return null;
47
+ }
48
+ const sk = opts.ed.utils.randomSecretKey();
49
+ const pk = await opts.ed.getPublicKey(sk);
50
+ const publicKey = toBase64Url(pk);
51
+ const privateKey = toBase64Url(sk);
52
+ const username = `runner-${publicKey.replace(/[^a-zA-Z0-9]/g, "").slice(0, 16).toLowerCase()}`;
53
+ console.log(`[abracadabra-service] auto-registering ${username} on ${opts.serverUrl}\u2026`);
54
+ const res = await fetch(`${opts.serverUrl}/auth/register`, {
55
+ method: "POST",
56
+ headers: { "Content-Type": "application/json" },
57
+ body: JSON.stringify({
58
+ username,
59
+ identityPublicKey: publicKey,
60
+ deviceName: "nuxt-runner",
61
+ displayName: "Nuxt Runner"
62
+ })
63
+ });
64
+ if (!res.ok && res.status !== 409) {
65
+ console.error(`[abracadabra-service] auto-register failed: ${res.status} ${await res.text()}`);
66
+ return null;
67
+ }
68
+ const identity = {
69
+ username,
70
+ publicKey,
71
+ privateKey,
72
+ serverUrl: opts.serverUrl,
73
+ createdAt: Date.now()
74
+ };
75
+ try {
76
+ mkdirSync(dirname(opts.cachePath), { recursive: true });
77
+ writeFileSync(opts.cachePath, JSON.stringify(identity, null, 2), "utf-8");
78
+ console.log(`[abracadabra-service] cached identity at ${opts.cachePath}`);
79
+ } catch (e) {
80
+ console.warn("[abracadabra-service] failed to cache identity (will re-register on next boot):", e instanceof Error ? e.message : e);
81
+ }
82
+ return identity;
83
+ }
19
84
  export default defineNitroPlugin(async (nitroApp) => {
20
85
  const config = useRuntimeConfig();
21
86
  const abraConfig = config.abracadabra;
22
87
  const storage = useStorage();
23
88
  initSlugMap(storage);
24
89
  await loadPersistedSlugMap();
25
- const pubKeyB64 = abraConfig?.servicePublicKey ?? "";
26
- const privKeyB64 = abraConfig?.servicePrivateKey ?? "";
90
+ const explicitPubKey = abraConfig?.servicePublicKey ?? "";
91
+ const explicitPrivKey = abraConfig?.servicePrivateKey ?? "";
27
92
  const rootDocIdOverride = abraConfig?.serviceRootDocId ?? "";
28
93
  const disabled = abraConfig?.serviceDisabled ?? false;
29
- if (disabled || !pubKeyB64 || !privKeyB64) {
94
+ const serverUrl = config.public?.abracadabra?.url;
95
+ if (disabled) return;
96
+ if (!serverUrl) {
97
+ console.warn("[abracadabra-service] no abracadabra.url configured \u2014 service plugin disabled");
30
98
  return;
31
99
  }
32
100
  let wsp = null;
33
101
  try {
34
102
  const [
35
103
  { AbracadabraClient, AbracadabraProvider, AbracadabraWS },
36
- ed,
104
+ edModule,
37
105
  { sha512 },
38
- Y
106
+ Y,
107
+ wsModule
39
108
  ] = await Promise.all([
40
109
  import("@abraca/dabra"),
41
110
  import("@noble/ed25519"),
42
- import("@noble/hashes/sha512"),
43
- import("yjs")
111
+ import("@noble/hashes/sha2.js"),
112
+ import("yjs"),
113
+ import("ws")
44
114
  ]);
45
- const edEtc = ed.etc;
46
- edEtc.sha512Sync = (...m) => sha512(edEtc.concatBytes(...m));
47
- edEtc.sha512Async = (...m) => Promise.resolve(edEtc.sha512Sync(...m));
115
+ const ed = edModule.default ?? edModule;
116
+ const WSImpl = wsModule.WebSocket ?? wsModule.default;
117
+ if (typeof globalThis.WebSocket === "undefined" && WSImpl) {
118
+ ;
119
+ globalThis.WebSocket = WSImpl;
120
+ }
121
+ if (!ed.hashes) {
122
+ throw new Error("@noble/ed25519 v3: `hashes` export missing \u2014 check Vite optimizeDeps");
123
+ }
124
+ ed.hashes.sha512 = sha512;
125
+ ed.hashes.sha512Async = (m) => Promise.resolve(sha512(m));
126
+ let pubKeyB64 = explicitPubKey;
127
+ let privKeyB64 = explicitPrivKey;
128
+ if (!pubKeyB64 || !privKeyB64) {
129
+ const cachePath = join(process.cwd(), ".data", "abracadabra-runner-identity.json");
130
+ const id = await loadOrCreateAutoIdentity({ serverUrl, cachePath, ed });
131
+ if (!id) {
132
+ return;
133
+ }
134
+ pubKeyB64 = id.publicKey;
135
+ privKeyB64 = id.privateKey;
136
+ }
48
137
  const privKey = fromBase64Url(privKeyB64);
49
138
  const client = new AbracadabraClient({
50
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Nuxt runtime config augmentation not resolved in Nitro
51
- url: config.public?.abracadabra?.url,
139
+ url: serverUrl,
52
140
  persistAuth: false
53
141
  });
54
142
  await client.loginWithKey(pubKeyB64, async (challenge) => {
@@ -64,7 +152,8 @@ export default defineNitroPlugin(async (nitroApp) => {
64
152
  maxDelay: 3e4,
65
153
  factor: 2,
66
154
  jitter: true,
67
- maxAttempts: 50
155
+ maxAttempts: 50,
156
+ WebSocketPolyfill: WSImpl
68
157
  });
69
158
  let rootDocId = rootDocIdOverride;
70
159
  if (!rootDocId) {
@@ -500,6 +500,17 @@ export interface AbracadabraState {
500
500
  }) => Promise<void>;
501
501
  /** Add a password to a key-only account. 409s if one is already set. */
502
502
  setPassword: (newPassword: string) => Promise<void>;
503
+ /**
504
+ * Whether the current user has a password set on the server. Drives the
505
+ * "Set password" vs "Change password" branching in `<AClaimAccountModal>`,
506
+ * `<AAccountSwitcherModal>`, and consumer settings panels. Populated from
507
+ * `getMe().hasPassword` after each successful auth event.
508
+ */
509
+ hasPassword: Ref<boolean>;
510
+ /** Force-refresh `hasPassword` from `getMe()`. Call after a successful
511
+ * `setPassword`/`changePassword` if the consumer wants to read the
512
+ * server's authoritative state instead of trusting the optimistic flip. */
513
+ refreshHasPassword: () => Promise<void>;
503
514
  reconnect: () => void;
504
515
  addServer: (url: string) => Promise<void>;
505
516
  removeServer: (url: string) => void;
@@ -11,6 +11,6 @@ export function awareRingStyle(input) {
11
11
  return {
12
12
  boxShadow: layers.join(", "),
13
13
  borderRadius: radius ?? "var(--ui-radius, 0.375rem)",
14
- transition: "box-shadow 100ms ease, filter 120ms ease"
14
+ transition: "box-shadow var(--aa-state-fade-shadow), filter var(--aa-state-fade-filter)"
15
15
  };
16
16
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * sanitizeSvg — modular SVG sanitizer.
3
+ *
4
+ * Tries to use DOMPurify (if the consumer installed it) for industry-grade
5
+ * sanitization. Falls back to a strict built-in allowlist that strips:
6
+ * - <script>, <foreignObject>, <iframe>, <embed>, <object>
7
+ * - `on*` event-handler attributes
8
+ * - `href` / `xlink:href` values that aren't `#anchor` (blocks `javascript:`)
9
+ *
10
+ * The built-in is intentionally conservative — it errs on the side of
11
+ * removing benign content rather than allowing risky content through.
12
+ * Apps that need more permissive sanitization (CSS classes, animations,
13
+ * external refs they own) should install DOMPurify and configure it.
14
+ */
15
+ /**
16
+ * Sanitize an SVG string. Async because DOMPurify is loaded lazily.
17
+ * Returns the safe SVG markup, or empty string if nothing safe remained.
18
+ */
19
+ export declare function sanitizeSvg(svg: string): Promise<string>;
@@ -0,0 +1,87 @@
1
+ let domPurifyCache = null;
2
+ let loadingPromise = null;
3
+ let warnedMissing = false;
4
+ async function tryLoadDomPurify() {
5
+ if (domPurifyCache) return domPurifyCache;
6
+ if (loadingPromise) return loadingPromise;
7
+ loadingPromise = (async () => {
8
+ try {
9
+ const pkg = "dompurify";
10
+ const mod = await import(
11
+ /* @vite-ignore */
12
+ pkg
13
+ );
14
+ domPurifyCache = mod?.default ?? mod;
15
+ return domPurifyCache;
16
+ } catch {
17
+ if (import.meta.dev && !warnedMissing) {
18
+ warnedMissing = true;
19
+ console.warn(
20
+ "[abracadabra] svg-embed: `dompurify` peer dependency not installed. Falling back to a strict built-in SVG sanitizer. Install with `pnpm add dompurify` for richer SVG support (CSS, animations, etc.)."
21
+ );
22
+ }
23
+ return null;
24
+ } finally {
25
+ loadingPromise = null;
26
+ }
27
+ })();
28
+ return loadingPromise;
29
+ }
30
+ const DANGEROUS_TAGS = /* @__PURE__ */ new Set([
31
+ "script",
32
+ "foreignobject",
33
+ "iframe",
34
+ "embed",
35
+ "object",
36
+ "meta",
37
+ "link"
38
+ ]);
39
+ const ALLOWED_PROTOCOL_PREFIXES = ["#", "data:image/"];
40
+ function strictBuiltinSanitize(svg) {
41
+ const stripped = svg.replace(/<\?xml[^?]*\?>/gi, "").replace(/<!DOCTYPE[^>]*>/gi, "").trim();
42
+ if (!stripped) return "";
43
+ if (typeof DOMParser === "undefined") {
44
+ return "";
45
+ }
46
+ const doc = new DOMParser().parseFromString(stripped, "image/svg+xml");
47
+ if (doc.querySelector("parsererror")) return "";
48
+ const walker = doc.createTreeWalker(doc.documentElement, NodeFilter.SHOW_ELEMENT);
49
+ const toRemove = [];
50
+ let node = doc.documentElement;
51
+ while (node) {
52
+ if (DANGEROUS_TAGS.has(node.tagName.toLowerCase())) {
53
+ toRemove.push(node);
54
+ } else {
55
+ for (const attr of Array.from(node.attributes)) {
56
+ const name = attr.name.toLowerCase();
57
+ if (name.startsWith("on")) {
58
+ node.removeAttribute(attr.name);
59
+ continue;
60
+ }
61
+ if (name === "href" || name === "xlink:href") {
62
+ const v = attr.value.trim().toLowerCase();
63
+ if (!ALLOWED_PROTOCOL_PREFIXES.some((p) => v.startsWith(p))) {
64
+ node.removeAttribute(attr.name);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ node = walker.nextNode();
70
+ }
71
+ for (const el of toRemove) el.remove();
72
+ return doc.documentElement.outerHTML;
73
+ }
74
+ export async function sanitizeSvg(svg) {
75
+ if (!svg) return "";
76
+ const purify = await tryLoadDomPurify();
77
+ if (purify) {
78
+ const stripped = svg.replace(/<\?xml[^?]*\?>/gi, "").replace(/<!DOCTYPE[^>]*>/gi, "").trim();
79
+ return purify.sanitize(stripped, {
80
+ USE_PROFILES: { svg: true, svgFilters: true },
81
+ ADD_TAGS: ["use", "style"],
82
+ FORBID_TAGS: ["script", "foreignObject", "iframe", "embed", "object"],
83
+ FORBID_ATTR: ["onload", "onerror", "onclick", "onmouseover", "onfocus", "onblur"]
84
+ });
85
+ }
86
+ return strictBuiltinSanitize(svg);
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/nuxt",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "description": "First-class Nuxt module for the Abracadabra CRDT collaboration platform",
5
5
  "repository": "abracadabra/abracadabra-nuxt",
6
6
  "license": "MIT",
@@ -26,7 +26,6 @@
26
26
  "access": "public"
27
27
  },
28
28
  "scripts": {
29
- "seed": "node --experimental-strip-types scripts/seed-playground.ts",
30
29
  "prepack": "nuxt-module-build build",
31
30
  "dev": "npm run dev:prepare && nuxt dev playground",
32
31
  "dev:build": "nuxt build playground",
@@ -49,9 +48,9 @@
49
48
  "nanoevents": "^9.1.0"
50
49
  },
51
50
  "peerDependencies": {
52
- "@abraca/dabra": "^2.0.0",
53
- "@noble/ed25519": "~2.3.0",
54
- "@noble/hashes": "^1.8.0",
51
+ "@abraca/dabra": "^2.0.3",
52
+ "@noble/ed25519": "^3.1.0",
53
+ "@noble/hashes": "^2.2.0",
55
54
  "@nuxt/ui": "^3.0.0",
56
55
  "@tanstack/vue-virtual": "^3.10.0",
57
56
  "@tiptap/core": "^3.0.0",
@@ -97,10 +96,10 @@
97
96
  }
98
97
  },
99
98
  "devDependencies": {
100
- "@abraca/dabra": "^2.0.0",
99
+ "@abraca/dabra": "^2.0.3",
101
100
  "@iconify-json/lucide": "^1.2.105",
102
- "@noble/ed25519": "~2.3.0",
103
- "@noble/hashes": "^1.8.0",
101
+ "@noble/ed25519": "^3.1.0",
102
+ "@noble/hashes": "^2.2.0",
104
103
  "@nuxt/eslint-config": "^1.15.2",
105
104
  "@nuxt/module-builder": "^1.0.2",
106
105
  "@nuxt/schema": "^4.4.4",
@@ -1,19 +0,0 @@
1
- import { type RendererBaseProps } from '../../composables/useRendererBase.js';
2
- import type { AbracadabraLocale } from '../../locale.js';
3
- type __VLS_Props = RendererBaseProps & {
4
- labels?: Partial<AbracadabraLocale['renderers']>;
5
- editable?: boolean;
6
- };
7
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
8
- connectedUsers: import("vue").ComputedRef<{
9
- clientId: number;
10
- name: string;
11
- color: string;
12
- avatar: string | undefined;
13
- publicKey: any;
14
- }[]>;
15
- }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
16
- editable: boolean;
17
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
- declare const _default: typeof __VLS_export;
19
- export default _default;