@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.
- package/dist/module.d.mts +18 -7
- package/dist/module.json +1 -1
- package/dist/module.mjs +21 -5
- package/dist/runtime/assets/aware-tokens.css +1 -0
- package/dist/runtime/components/AAccountSwitcherModal.d.vue.ts +16 -1
- package/dist/runtime/components/AAccountSwitcherModal.vue +33 -4
- package/dist/runtime/components/AAccountSwitcherModal.vue.d.ts +16 -1
- package/dist/runtime/components/AAuthLinkLanding.d.vue.ts +3 -0
- package/dist/runtime/components/AAuthLinkLanding.vue +85 -0
- package/dist/runtime/components/AAuthLinkLanding.vue.d.ts +3 -0
- package/dist/runtime/components/AClaimAccountModal.d.vue.ts +7 -1
- package/dist/runtime/components/AClaimAccountModal.vue +28 -13
- package/dist/runtime/components/AClaimAccountModal.vue.d.ts +7 -1
- package/dist/runtime/components/AEditor.vue +5 -0
- package/dist/runtime/components/AEmailVerifyConfirmModal.d.vue.ts +30 -0
- package/dist/runtime/components/AEmailVerifyConfirmModal.vue +100 -0
- package/dist/runtime/components/AEmailVerifyConfirmModal.vue.d.ts +30 -0
- package/dist/runtime/components/AEmailVerifyRequestCard.d.vue.ts +22 -0
- package/dist/runtime/components/AEmailVerifyRequestCard.vue +65 -0
- package/dist/runtime/components/AEmailVerifyRequestCard.vue.d.ts +22 -0
- package/dist/runtime/components/AMnemonicLoginModal.d.vue.ts +1 -1
- package/dist/runtime/components/AMnemonicLoginModal.vue.d.ts +1 -1
- package/dist/runtime/components/ANodePanel.vue +2 -0
- package/dist/runtime/components/ANotificationBell.d.vue.ts +2 -2
- package/dist/runtime/components/ANotificationBell.vue.d.ts +2 -2
- package/dist/runtime/components/APasswordChangeModal.d.vue.ts +28 -0
- package/dist/runtime/components/APasswordChangeModal.vue +178 -0
- package/dist/runtime/components/APasswordChangeModal.vue.d.ts +28 -0
- package/dist/runtime/components/APasswordLoginModal.d.vue.ts +42 -0
- package/dist/runtime/components/APasswordLoginModal.vue +177 -0
- package/dist/runtime/components/APasswordLoginModal.vue.d.ts +42 -0
- package/dist/runtime/components/APasswordRegisterModal.d.vue.ts +49 -0
- package/dist/runtime/components/APasswordRegisterModal.vue +262 -0
- package/dist/runtime/components/APasswordRegisterModal.vue.d.ts +49 -0
- package/dist/runtime/components/APasswordResetConfirmModal.d.vue.ts +31 -0
- package/dist/runtime/components/APasswordResetConfirmModal.vue +154 -0
- package/dist/runtime/components/APasswordResetConfirmModal.vue.d.ts +31 -0
- package/dist/runtime/components/APasswordResetRequestModal.d.vue.ts +35 -0
- package/dist/runtime/components/APasswordResetRequestModal.vue +113 -0
- package/dist/runtime/components/APasswordResetRequestModal.vue.d.ts +35 -0
- package/dist/runtime/components/ASetPasswordCard.d.vue.ts +26 -0
- package/dist/runtime/components/ASetPasswordCard.vue +139 -0
- package/dist/runtime/components/ASetPasswordCard.vue.d.ts +26 -0
- package/dist/runtime/components/ASubPageList.d.vue.ts +66 -0
- package/dist/runtime/components/ASubPageList.vue +147 -0
- package/dist/runtime/components/ASubPageList.vue.d.ts +66 -0
- package/dist/runtime/components/aware/AAccordion.d.vue.ts +2 -0
- package/dist/runtime/components/aware/AAccordion.vue +11 -1
- package/dist/runtime/components/aware/AAccordion.vue.d.ts +2 -0
- package/dist/runtime/components/aware/AButton.vue +3 -3
- package/dist/runtime/components/aware/ACollapsible.d.vue.ts +2 -0
- package/dist/runtime/components/aware/ACollapsible.vue +9 -1
- package/dist/runtime/components/aware/ACollapsible.vue.d.ts +2 -0
- package/dist/runtime/components/aware/AGlobalFocusLayer.vue +1 -1
- package/dist/runtime/components/aware/AHoverItem.vue +28 -3
- package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
- package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
- package/dist/runtime/components/aware/AModal.d.vue.ts +2 -0
- package/dist/runtime/components/aware/AModal.vue +9 -1
- package/dist/runtime/components/aware/AModal.vue.d.ts +2 -0
- package/dist/runtime/components/aware/APresenceBlobs.vue +1 -1
- package/dist/runtime/components/aware/APresenceCursors.vue +1 -1
- package/dist/runtime/components/aware/AScroll.d.vue.ts +2 -0
- package/dist/runtime/components/aware/AScroll.vue +13 -3
- package/dist/runtime/components/aware/AScroll.vue.d.ts +2 -0
- package/dist/runtime/components/aware/ASlideover.d.vue.ts +2 -0
- package/dist/runtime/components/aware/ASlideover.vue +9 -1
- package/dist/runtime/components/aware/ASlideover.vue.d.ts +2 -0
- package/dist/runtime/components/aware/ASlider.vue +1 -0
- package/dist/runtime/components/aware/ATabs.d.vue.ts +2 -0
- package/dist/runtime/components/aware/ATabs.vue +9 -1
- package/dist/runtime/components/aware/ATabs.vue.d.ts +2 -0
- package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -0
- package/dist/runtime/components/editor/AEditorRedoButton.d.vue.ts +2 -2
- package/dist/runtime/components/editor/AEditorRedoButton.vue.d.ts +2 -2
- package/dist/runtime/components/editor/AEditorUndoButton.d.vue.ts +2 -2
- package/dist/runtime/components/editor/AEditorUndoButton.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +4 -4
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +4 -4
- package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
- package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
- package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
- package/dist/runtime/composables/useAAField.js +7 -4
- package/dist/runtime/composables/useAAFocus.js +10 -5
- package/dist/runtime/composables/useAAFollowAnchor.js +68 -34
- package/dist/runtime/composables/useAAFollowPeer.d.ts +7 -4
- package/dist/runtime/composables/useAAFollowPeer.js +60 -11
- package/dist/runtime/composables/useAAViewport.d.ts +1 -1
- package/dist/runtime/composables/useAbracadabraAuth.d.ts +2 -0
- package/dist/runtime/composables/useAbracadabraAuth.js +2 -0
- package/dist/runtime/composables/useEditorSuggestions.js +2 -1
- package/dist/runtime/composables/useEmailVerification.d.ts +40 -26
- package/dist/runtime/composables/useEmailVerification.js +95 -43
- package/dist/runtime/composables/usePasswordAuth.d.ts +64 -0
- package/dist/runtime/composables/usePasswordAuth.js +126 -0
- package/dist/runtime/composables/useTiptapHistory.d.ts +2 -2
- package/dist/runtime/composables/useTiptapHistory.js +5 -5
- package/dist/runtime/extensions/svg-embed.d.ts +23 -0
- package/dist/runtime/extensions/svg-embed.js +33 -0
- package/dist/runtime/extensions/views/MetaFieldView.vue +23 -6
- package/dist/runtime/extensions/views/SvgEmbedView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/SvgEmbedView.vue +120 -0
- package/dist/runtime/extensions/views/SvgEmbedView.vue.d.ts +4 -0
- package/dist/runtime/plugin-abracadabra.client.js +58 -9
- package/dist/runtime/plugin-abracadabra.server.js +2 -0
- package/dist/runtime/plugins/core.plugin.js +8 -4
- package/dist/runtime/server/plugins/abracadabra-service.js +102 -13
- package/dist/runtime/types.d.ts +11 -0
- package/dist/runtime/utils/awareRingStyle.js +1 -1
- package/dist/runtime/utils/sanitizeSvg.d.ts +19 -0
- package/dist/runtime/utils/sanitizeSvg.js +87 -0
- package/package.json +7 -8
- package/dist/runtime/components/renderers/ASpatialRenderer.d.vue.ts +0 -19
- package/dist/runtime/components/renderers/ASpatialRenderer.vue +0 -459
- package/dist/runtime/components/renderers/ASpatialRenderer.vue.d.ts +0 -19
- package/dist/runtime/components/renderers/spatial/SpatialGround.d.vue.ts +0 -20
- package/dist/runtime/components/renderers/spatial/SpatialGround.vue +0 -26
- package/dist/runtime/components/renderers/spatial/SpatialGround.vue.d.ts +0 -20
- package/dist/runtime/components/renderers/spatial/SpatialObject.d.vue.ts +0 -17
- package/dist/runtime/components/renderers/spatial/SpatialObject.vue +0 -257
- package/dist/runtime/components/renderers/spatial/SpatialObject.vue.d.ts +0 -17
- package/dist/runtime/components/renderers/spatial/SpatialSceneBridge.d.vue.ts +0 -15
- package/dist/runtime/components/renderers/spatial/SpatialSceneBridge.vue +0 -18
- package/dist/runtime/components/renderers/spatial/SpatialSceneBridge.vue.d.ts +0 -15
- package/dist/runtime/components/renderers/spatial/SpatialTransformInputs.d.vue.ts +0 -16
- package/dist/runtime/components/renderers/spatial/SpatialTransformInputs.vue +0 -66
- package/dist/runtime/components/renderers/spatial/SpatialTransformInputs.vue.d.ts +0 -16
- package/dist/runtime/components/renderers/spatial/SpatialUserAvatar.d.vue.ts +0 -8
- package/dist/runtime/components/renderers/spatial/SpatialUserAvatar.vue +0 -53
- package/dist/runtime/components/renderers/spatial/SpatialUserAvatar.vue.d.ts +0 -8
- package/dist/runtime/composables/useSpatialCamera.d.ts +0 -16
- package/dist/runtime/composables/useSpatialCamera.js +0 -175
- package/dist/runtime/composables/useSpatialDrag.d.ts +0 -14
- 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
|
-
|
|
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
|
-
|
|
751
|
+
edModule,
|
|
709
752
|
{ sha512 }
|
|
710
753
|
] = await Promise.all([
|
|
711
754
|
import("@abraca/dabra"),
|
|
712
755
|
import("@noble/ed25519"),
|
|
713
|
-
import("@noble/hashes/
|
|
756
|
+
import("@noble/hashes/sha2.js")
|
|
714
757
|
]);
|
|
715
758
|
setSdkModule(sdkModule);
|
|
716
759
|
const { AbracadabraClient, AbracadabraProvider, AbracadabraWS, CryptoIdentityKeystore } = sdkModule;
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
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.
|
|
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,
|
|
@@ -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
|
|
26
|
-
const
|
|
90
|
+
const explicitPubKey = abraConfig?.servicePublicKey ?? "";
|
|
91
|
+
const explicitPrivKey = abraConfig?.servicePrivateKey ?? "";
|
|
27
92
|
const rootDocIdOverride = abraConfig?.serviceRootDocId ?? "";
|
|
28
93
|
const disabled = abraConfig?.serviceDisabled ?? false;
|
|
29
|
-
|
|
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
|
-
|
|
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/
|
|
43
|
-
import("yjs")
|
|
111
|
+
import("@noble/hashes/sha2.js"),
|
|
112
|
+
import("yjs"),
|
|
113
|
+
import("ws")
|
|
44
114
|
]);
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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) {
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
53
|
-
"@noble/ed25519": "
|
|
54
|
-
"@noble/hashes": "^
|
|
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.
|
|
99
|
+
"@abraca/dabra": "^2.0.3",
|
|
101
100
|
"@iconify-json/lucide": "^1.2.105",
|
|
102
|
-
"@noble/ed25519": "
|
|
103
|
-
"@noble/hashes": "^
|
|
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;
|