@abraca/nuxt 2.0.1 → 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 (116) 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/AEmailVerifyConfirmModal.d.vue.ts +30 -0
  15. package/dist/runtime/components/AEmailVerifyConfirmModal.vue +100 -0
  16. package/dist/runtime/components/AEmailVerifyConfirmModal.vue.d.ts +30 -0
  17. package/dist/runtime/components/AEmailVerifyRequestCard.d.vue.ts +22 -0
  18. package/dist/runtime/components/AEmailVerifyRequestCard.vue +65 -0
  19. package/dist/runtime/components/AEmailVerifyRequestCard.vue.d.ts +22 -0
  20. package/dist/runtime/components/AMnemonicLoginModal.d.vue.ts +1 -1
  21. package/dist/runtime/components/AMnemonicLoginModal.vue.d.ts +1 -1
  22. package/dist/runtime/components/ANodePanel.vue +2 -0
  23. package/dist/runtime/components/ANotificationBell.d.vue.ts +2 -2
  24. package/dist/runtime/components/ANotificationBell.vue.d.ts +2 -2
  25. package/dist/runtime/components/APasswordChangeModal.d.vue.ts +28 -0
  26. package/dist/runtime/components/APasswordChangeModal.vue +178 -0
  27. package/dist/runtime/components/APasswordChangeModal.vue.d.ts +28 -0
  28. package/dist/runtime/components/APasswordLoginModal.d.vue.ts +42 -0
  29. package/dist/runtime/components/APasswordLoginModal.vue +177 -0
  30. package/dist/runtime/components/APasswordLoginModal.vue.d.ts +42 -0
  31. package/dist/runtime/components/APasswordRegisterModal.d.vue.ts +49 -0
  32. package/dist/runtime/components/APasswordRegisterModal.vue +262 -0
  33. package/dist/runtime/components/APasswordRegisterModal.vue.d.ts +49 -0
  34. package/dist/runtime/components/APasswordResetConfirmModal.d.vue.ts +31 -0
  35. package/dist/runtime/components/APasswordResetConfirmModal.vue +154 -0
  36. package/dist/runtime/components/APasswordResetConfirmModal.vue.d.ts +31 -0
  37. package/dist/runtime/components/APasswordResetRequestModal.d.vue.ts +35 -0
  38. package/dist/runtime/components/APasswordResetRequestModal.vue +113 -0
  39. package/dist/runtime/components/APasswordResetRequestModal.vue.d.ts +35 -0
  40. package/dist/runtime/components/ASetPasswordCard.d.vue.ts +26 -0
  41. package/dist/runtime/components/ASetPasswordCard.vue +139 -0
  42. package/dist/runtime/components/ASetPasswordCard.vue.d.ts +26 -0
  43. package/dist/runtime/components/aware/AAccordion.d.vue.ts +2 -0
  44. package/dist/runtime/components/aware/AAccordion.vue +11 -1
  45. package/dist/runtime/components/aware/AAccordion.vue.d.ts +2 -0
  46. package/dist/runtime/components/aware/AButton.vue +3 -3
  47. package/dist/runtime/components/aware/ACollapsible.d.vue.ts +2 -0
  48. package/dist/runtime/components/aware/ACollapsible.vue +9 -1
  49. package/dist/runtime/components/aware/ACollapsible.vue.d.ts +2 -0
  50. package/dist/runtime/components/aware/AGlobalFocusLayer.vue +1 -1
  51. package/dist/runtime/components/aware/AHoverItem.vue +28 -3
  52. package/dist/runtime/components/aware/AModal.d.vue.ts +2 -0
  53. package/dist/runtime/components/aware/AModal.vue +9 -1
  54. package/dist/runtime/components/aware/AModal.vue.d.ts +2 -0
  55. package/dist/runtime/components/aware/APresenceBlobs.vue +1 -1
  56. package/dist/runtime/components/aware/APresenceCursors.vue +1 -1
  57. package/dist/runtime/components/aware/AScroll.d.vue.ts +2 -0
  58. package/dist/runtime/components/aware/AScroll.vue +13 -3
  59. package/dist/runtime/components/aware/AScroll.vue.d.ts +2 -0
  60. package/dist/runtime/components/aware/ASlideover.d.vue.ts +2 -0
  61. package/dist/runtime/components/aware/ASlideover.vue +9 -1
  62. package/dist/runtime/components/aware/ASlideover.vue.d.ts +2 -0
  63. package/dist/runtime/components/aware/ASlider.vue +1 -0
  64. package/dist/runtime/components/aware/ATabs.d.vue.ts +2 -0
  65. package/dist/runtime/components/aware/ATabs.vue +9 -1
  66. package/dist/runtime/components/aware/ATabs.vue.d.ts +2 -0
  67. package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -0
  68. package/dist/runtime/components/editor/AEditorRedoButton.d.vue.ts +2 -2
  69. package/dist/runtime/components/editor/AEditorRedoButton.vue.d.ts +2 -2
  70. package/dist/runtime/components/editor/AEditorUndoButton.d.vue.ts +2 -2
  71. package/dist/runtime/components/editor/AEditorUndoButton.vue.d.ts +2 -2
  72. package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
  73. package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
  74. package/dist/runtime/composables/useAAField.js +7 -4
  75. package/dist/runtime/composables/useAAFocus.js +10 -5
  76. package/dist/runtime/composables/useAAFollowAnchor.js +68 -34
  77. package/dist/runtime/composables/useAAFollowPeer.d.ts +7 -4
  78. package/dist/runtime/composables/useAAFollowPeer.js +60 -11
  79. package/dist/runtime/composables/useAAViewport.d.ts +1 -1
  80. package/dist/runtime/composables/useAbracadabraAuth.d.ts +2 -0
  81. package/dist/runtime/composables/useAbracadabraAuth.js +2 -0
  82. package/dist/runtime/composables/useEmailVerification.d.ts +40 -26
  83. package/dist/runtime/composables/useEmailVerification.js +95 -43
  84. package/dist/runtime/composables/usePasswordAuth.d.ts +64 -0
  85. package/dist/runtime/composables/usePasswordAuth.js +126 -0
  86. package/dist/runtime/composables/useTiptapHistory.d.ts +2 -2
  87. package/dist/runtime/composables/useTiptapHistory.js +5 -5
  88. package/dist/runtime/extensions/views/MetaFieldView.vue +23 -6
  89. package/dist/runtime/plugin-abracadabra.client.js +57 -8
  90. package/dist/runtime/plugin-abracadabra.server.js +2 -0
  91. package/dist/runtime/server/plugins/abracadabra-service.js +20 -9
  92. package/dist/runtime/types.d.ts +11 -0
  93. package/dist/runtime/utils/awareRingStyle.js +1 -1
  94. package/package.json +7 -7
  95. package/dist/runtime/components/renderers/ASpatialRenderer.d.vue.ts +0 -19
  96. package/dist/runtime/components/renderers/ASpatialRenderer.vue +0 -459
  97. package/dist/runtime/components/renderers/ASpatialRenderer.vue.d.ts +0 -19
  98. package/dist/runtime/components/renderers/spatial/SpatialGround.d.vue.ts +0 -20
  99. package/dist/runtime/components/renderers/spatial/SpatialGround.vue +0 -26
  100. package/dist/runtime/components/renderers/spatial/SpatialGround.vue.d.ts +0 -20
  101. package/dist/runtime/components/renderers/spatial/SpatialObject.d.vue.ts +0 -17
  102. package/dist/runtime/components/renderers/spatial/SpatialObject.vue +0 -257
  103. package/dist/runtime/components/renderers/spatial/SpatialObject.vue.d.ts +0 -17
  104. package/dist/runtime/components/renderers/spatial/SpatialSceneBridge.d.vue.ts +0 -15
  105. package/dist/runtime/components/renderers/spatial/SpatialSceneBridge.vue +0 -18
  106. package/dist/runtime/components/renderers/spatial/SpatialSceneBridge.vue.d.ts +0 -15
  107. package/dist/runtime/components/renderers/spatial/SpatialTransformInputs.d.vue.ts +0 -16
  108. package/dist/runtime/components/renderers/spatial/SpatialTransformInputs.vue +0 -66
  109. package/dist/runtime/components/renderers/spatial/SpatialTransformInputs.vue.d.ts +0 -16
  110. package/dist/runtime/components/renderers/spatial/SpatialUserAvatar.d.vue.ts +0 -8
  111. package/dist/runtime/components/renderers/spatial/SpatialUserAvatar.vue +0 -53
  112. package/dist/runtime/components/renderers/spatial/SpatialUserAvatar.vue.d.ts +0 -8
  113. package/dist/runtime/composables/useSpatialCamera.d.ts +0 -16
  114. package/dist/runtime/composables/useSpatialCamera.js +0 -175
  115. package/dist/runtime/composables/useSpatialDrag.d.ts +0 -14
  116. package/dist/runtime/composables/useSpatialDrag.js +0 -137
@@ -2,17 +2,42 @@ import { computed, onScopeDispose, ref, watch } from "vue";
2
2
  import { useAbracadabra } from "./useAbracadabra.js";
3
3
  import { useSyncedMap } from "./useYDoc.js";
4
4
  import { useAwareness } from "./useAwareness.js";
5
- const _followingClientId = ref(null);
5
+ const _followingPublicKey = ref(null);
6
+ const _followingClientIdFallback = ref(null);
6
7
  export function useAAFollowPeer(options = {}) {
7
8
  const { doc, provider } = useAbracadabra();
8
9
  const { data, set } = useSyncedMap(doc, "aware-ui");
9
10
  const { states } = useAwareness();
10
- const followingClientId = _followingClientId;
11
11
  let unsubInteraction = null;
12
+ const followingClientId = computed(() => {
13
+ const pk = _followingPublicKey.value;
14
+ if (pk) {
15
+ for (const [cid, st] of states.value.entries()) {
16
+ const peerPk = st?.user?.publicKey;
17
+ if (peerPk === pk) return cid;
18
+ }
19
+ return null;
20
+ }
21
+ return _followingClientIdFallback.value;
22
+ });
23
+ const isFollowing = computed(
24
+ () => _followingPublicKey.value != null || _followingClientIdFallback.value != null
25
+ );
26
+ const followingPublicKey = computed(() => _followingPublicKey.value);
27
+ const followingUserCache = ref(null);
12
28
  const followingUser = computed(() => {
13
- if (followingClientId.value == null) return null;
14
- const state = states.value.get(followingClientId.value);
15
- return state?.user ?? null;
29
+ const cid = followingClientId.value;
30
+ if (cid != null) {
31
+ const state = states.value.get(cid);
32
+ if (state?.user) return state.user;
33
+ }
34
+ return followingUserCache.value;
35
+ });
36
+ watch(followingUser, (user) => {
37
+ if (user) followingUserCache.value = { ...user };
38
+ }, { flush: "post" });
39
+ watch(isFollowing, (active) => {
40
+ if (!active) followingUserCache.value = null;
16
41
  });
17
42
  function detachInteraction() {
18
43
  unsubInteraction?.();
@@ -31,14 +56,31 @@ export function useAAFollowPeer(options = {}) {
31
56
  window.removeEventListener("wheel", onBreak);
32
57
  };
33
58
  }
34
- function follow(clientId) {
35
- if (followingClientId.value === clientId) return;
36
- followingClientId.value = clientId;
59
+ function follow(clientIdOrPublicKey) {
60
+ if (typeof clientIdOrPublicKey === "string") {
61
+ if (_followingPublicKey.value === clientIdOrPublicKey) return;
62
+ _followingPublicKey.value = clientIdOrPublicKey;
63
+ _followingClientIdFallback.value = null;
64
+ } else {
65
+ const cid = clientIdOrPublicKey;
66
+ const state = states.value.get(cid);
67
+ const pk = state?.user?.publicKey;
68
+ if (pk) {
69
+ if (_followingPublicKey.value === pk) return;
70
+ _followingPublicKey.value = pk;
71
+ _followingClientIdFallback.value = null;
72
+ } else {
73
+ if (_followingClientIdFallback.value === cid) return;
74
+ _followingPublicKey.value = null;
75
+ _followingClientIdFallback.value = cid;
76
+ }
77
+ }
37
78
  attachInteractionBreaker();
38
79
  }
39
80
  function unfollow() {
40
- if (followingClientId.value == null) return;
41
- followingClientId.value = null;
81
+ if (_followingPublicKey.value == null && _followingClientIdFallback.value == null) return;
82
+ _followingPublicKey.value = null;
83
+ _followingClientIdFallback.value = null;
42
84
  detachInteraction();
43
85
  options.onBreak?.();
44
86
  }
@@ -71,5 +113,12 @@ export function useAAFollowPeer(options = {}) {
71
113
  }
72
114
  });
73
115
  onScopeDispose(detachInteraction);
74
- return { follow, unfollow, followingClientId, followingUser };
116
+ return {
117
+ follow,
118
+ unfollow,
119
+ followingClientId,
120
+ followingPublicKey,
121
+ followingUser,
122
+ isFollowing
123
+ };
75
124
  }
@@ -6,7 +6,7 @@ import type { MaybeRefOrGetter } from 'vue';
6
6
  * scoped container as `peer:viewport:<scope>` ephemeral state.
7
7
  *
8
8
  * Use case: render "Janis is reading the API section" overlays in long docs.
9
- * Pair with `<APresenceCursors>` for a complete spatial-presence layer.
9
+ * Pair with `<APresenceCursors>` for a complete presence layer.
10
10
  */
11
11
  export interface PeerViewport {
12
12
  clientId: number;
@@ -21,6 +21,8 @@ export declare function useAbracadabraAuth(): {
21
21
  confirmPasswordReset: any;
22
22
  changePassword: any;
23
23
  setPassword: any;
24
+ hasPassword: any;
25
+ refreshHasPassword: any;
24
26
  recoverIdentity: any;
25
27
  unblockConnection: any;
26
28
  setUserName: any;
@@ -24,6 +24,8 @@ export function useAbracadabraAuth() {
24
24
  confirmPasswordReset: abra.confirmPasswordReset,
25
25
  changePassword: abra.changePassword,
26
26
  setPassword: abra.setPassword,
27
+ hasPassword: abra.hasPassword,
28
+ refreshHasPassword: abra.refreshHasPassword,
27
29
  recoverIdentity: abra.recoverIdentity,
28
30
  unblockConnection: abra.unblockConnection,
29
31
  setUserName: abra.setUserName,
@@ -1,33 +1,47 @@
1
- /**
2
- * useEmailVerification
3
- *
4
- * Wraps the email-verification half of the auth lifecycle.
5
- * - `request()` triggers a verification email (rate-limited 1/min, 10/day).
6
- * - `confirm(token)` consumes the token from the email link; no auth needed.
7
- *
8
- * `verified` mirrors the most recent local result of `confirm()` — the
9
- * server doesn't currently expose `email_verified_at` on `getMe()`, so
10
- * the canonical signal is "did `confirm()` succeed in this session".
11
- *
12
- * Usage:
13
- * const { verified, isRequesting, isConfirming, error, request, confirm } = useEmailVerification()
14
- */
15
- declare function request(): Promise<void>;
16
- declare function confirm(token: string): Promise<void>;
1
+ /** Known error codes raised by `useEmailVerification()` actions. */
2
+ export type EmailVerificationErrorCode = 'rate_limited' | 'token_expired' | 'token_invalid' | 'verification_disabled' | 'already_verified' | 'email_not_set' | 'unauthorized' | 'forbidden' | 'network_error' | 'server_error' | 'unknown';
3
+ export interface EmailVerificationError {
4
+ code: EmailVerificationErrorCode;
5
+ message: string;
6
+ status?: number;
7
+ retryAfterSeconds?: number;
8
+ }
9
+ export type EmailVerificationAction = 'request' | 'confirm';
10
+ export interface EmailVerificationResult {
11
+ ok: boolean;
12
+ error?: EmailVerificationError;
13
+ }
14
+ declare function classifyError(err: unknown): EmailVerificationError;
17
15
  export declare function useEmailVerification(): {
18
- /** True after a successful `confirm()` in the current session. */
16
+ /** True after a successful `confirm()` in this session. */
19
17
  verified: import("vue").Ref<boolean, boolean>;
20
- /** Whether a `request()` is in flight. */
21
- isRequesting: import("vue").Ref<boolean, boolean>;
22
- /** Whether a `confirm()` is in flight. */
23
- isConfirming: import("vue").Ref<boolean, boolean>;
24
- /** Last error message, or null. */
25
- error: import("vue").Ref<string | null, string | null>;
26
18
  /** Epoch ms of the last successful `request()`, or null. */
27
19
  lastSentAt: import("vue").Ref<number | null, number | null>;
28
- /** Send a verification email to the current user. Requires auth. */
29
- request: typeof request;
20
+ /** Currently-running action, or null. */
21
+ busy: import("vue").Ref<EmailVerificationAction | null, EmailVerificationAction | null>;
22
+ /** Most recent typed error, or null. Cleared at the start of every action. */
23
+ error: import("vue").Ref<{
24
+ code: EmailVerificationErrorCode;
25
+ message: string;
26
+ status?: number | undefined;
27
+ retryAfterSeconds?: number | undefined;
28
+ } | null, EmailVerificationError | {
29
+ code: EmailVerificationErrorCode;
30
+ message: string;
31
+ status?: number | undefined;
32
+ retryAfterSeconds?: number | undefined;
33
+ } | null>;
34
+ /** Most recent successful action, or null. */
35
+ lastSuccess: import("vue").Ref<EmailVerificationAction | null, EmailVerificationAction | null>;
36
+ /** Wipe `error` without re-running anything. */
37
+ clearError: () => void;
38
+ /** Send a verification email to the current user. Auth required. */
39
+ request: () => Promise<EmailVerificationResult>;
30
40
  /** Confirm a verification token from the email link. No auth required. */
31
- confirm: typeof confirm;
41
+ confirm: (opts: {
42
+ token: string;
43
+ }) => Promise<EmailVerificationResult>;
44
+ /** Internal helper exposed for testing. */
45
+ _classifyError: typeof classifyError;
32
46
  };
33
47
  export {};
@@ -1,58 +1,110 @@
1
1
  import { ref } from "vue";
2
- import { useAbracadabra } from "#imports";
2
+ import { useAbracadabra } from "./useAbracadabra.js";
3
3
  const verified = ref(false);
4
- const isRequesting = ref(false);
5
- const isConfirming = ref(false);
6
- const error = ref(null);
7
4
  const lastSentAt = ref(null);
8
- async function request() {
9
- const abra = useAbracadabra();
10
- const client = abra.client.value;
11
- if (!client) throw new Error("Not connected");
12
- isRequesting.value = true;
13
- error.value = null;
14
- try {
15
- await client.requestEmailVerification();
16
- lastSentAt.value = Date.now();
17
- } catch (e) {
18
- error.value = e instanceof Error ? e.message : "Failed to send verification email";
19
- throw e;
20
- } finally {
21
- isRequesting.value = false;
22
- }
5
+ const busy = ref(null);
6
+ const error = ref(null);
7
+ const lastSuccess = ref(null);
8
+ function extractServerMessage(raw) {
9
+ const trailing = raw.replace(/ \(\d+\)$/, "").trim();
10
+ const colon = trailing.indexOf(": ");
11
+ if (colon === -1) return trailing;
12
+ const head = trailing.slice(0, colon);
13
+ if (/^[A-Z]+ \//.test(head)) return trailing.slice(colon + 2).trim();
14
+ return trailing;
23
15
  }
24
- async function confirm(token) {
25
- const abra = useAbracadabra();
26
- const client = abra.client.value;
27
- if (!client) throw new Error("Not connected");
28
- isConfirming.value = true;
29
- error.value = null;
30
- try {
31
- await client.confirmEmailVerification(token);
32
- verified.value = true;
33
- } catch (e) {
34
- error.value = e instanceof Error ? e.message : "Verification failed";
35
- verified.value = false;
36
- throw e;
37
- } finally {
38
- isConfirming.value = false;
16
+ function classifyError(err) {
17
+ if (err instanceof TypeError && /fetch|network/i.test(err.message)) {
18
+ return { code: "network_error", message: err.message };
19
+ }
20
+ if (!(err instanceof Error)) {
21
+ return { code: "unknown", message: String(err) };
22
+ }
23
+ const status = err.status;
24
+ const serverMsg = extractServerMessage(err.message);
25
+ const lower = serverMsg.toLowerCase();
26
+ const retryMatch = lower.match(/retry(?:[-\s]?after)?[:\s]+(\d+)/);
27
+ const retryAfterSeconds = retryMatch ? Number(retryMatch[1]) : void 0;
28
+ if (status === 429) {
29
+ return { code: "rate_limited", message: serverMsg, status, retryAfterSeconds };
30
+ }
31
+ if (status === 410 || /token (?:expired|invalid)/.test(lower)) {
32
+ return {
33
+ code: lower.includes("expired") ? "token_expired" : "token_invalid",
34
+ message: serverMsg,
35
+ status
36
+ };
37
+ }
38
+ if (status === 404 || /verification (?:is )?disabled/.test(lower)) {
39
+ return { code: "verification_disabled", message: serverMsg, status };
40
+ }
41
+ if (lower.includes("already verified") || lower.includes("already_verified")) {
42
+ return { code: "already_verified", message: serverMsg, status };
39
43
  }
44
+ if (lower.includes("no email") || lower.includes("email not set") || lower.includes("email_not_set")) {
45
+ return { code: "email_not_set", message: serverMsg, status };
46
+ }
47
+ if (status === 401) return { code: "unauthorized", message: serverMsg, status };
48
+ if (status === 403) return { code: "forbidden", message: serverMsg, status };
49
+ if (typeof status === "number" && status >= 500) {
50
+ return { code: "server_error", message: serverMsg, status };
51
+ }
52
+ return { code: "unknown", message: serverMsg, status };
40
53
  }
41
54
  export function useEmailVerification() {
55
+ const abra = useAbracadabra();
56
+ function clearError() {
57
+ error.value = null;
58
+ }
59
+ async function run(action, fn) {
60
+ const client = abra.client.value;
61
+ if (!client) {
62
+ const err = { code: "network_error", message: "Not connected" };
63
+ error.value = err;
64
+ return { ok: false, error: err };
65
+ }
66
+ busy.value = action;
67
+ error.value = null;
68
+ try {
69
+ await fn(client);
70
+ lastSuccess.value = action;
71
+ return { ok: true };
72
+ } catch (e) {
73
+ const err = classifyError(e);
74
+ error.value = err;
75
+ return { ok: false, error: err };
76
+ } finally {
77
+ busy.value = null;
78
+ }
79
+ }
80
+ async function request() {
81
+ const res = await run("request", (client) => client.requestEmailVerification());
82
+ if (res.ok) lastSentAt.value = Date.now();
83
+ return res;
84
+ }
85
+ async function confirm(opts) {
86
+ const res = await run("confirm", (client) => client.confirmEmailVerification(opts.token));
87
+ if (res.ok) verified.value = true;
88
+ return res;
89
+ }
42
90
  return {
43
- /** True after a successful `confirm()` in the current session. */
91
+ /** True after a successful `confirm()` in this session. */
44
92
  verified,
45
- /** Whether a `request()` is in flight. */
46
- isRequesting,
47
- /** Whether a `confirm()` is in flight. */
48
- isConfirming,
49
- /** Last error message, or null. */
50
- error,
51
93
  /** Epoch ms of the last successful `request()`, or null. */
52
94
  lastSentAt,
53
- /** Send a verification email to the current user. Requires auth. */
95
+ /** Currently-running action, or null. */
96
+ busy,
97
+ /** Most recent typed error, or null. Cleared at the start of every action. */
98
+ error,
99
+ /** Most recent successful action, or null. */
100
+ lastSuccess,
101
+ /** Wipe `error` without re-running anything. */
102
+ clearError,
103
+ /** Send a verification email to the current user. Auth required. */
54
104
  request,
55
105
  /** Confirm a verification token from the email link. No auth required. */
56
- confirm
106
+ confirm,
107
+ /** Internal helper exposed for testing. */
108
+ _classifyError: classifyError
57
109
  };
58
110
  }
@@ -0,0 +1,64 @@
1
+ /** Known error codes raised by `usePasswordAuth()` actions. */
2
+ export type PasswordAuthErrorCode = 'rate_limited' | 'locked_out' | 'password_too_short' | 'invalid_credentials' | 'token_expired' | 'token_invalid' | 'password_already_set' | 'email_taken' | 'username_taken' | 'password_login_disabled' | 'password_reset_disabled' | 'invite_required' | 'invite_invalid' | 'network_error' | 'forbidden' | 'unauthorized' | 'server_error' | 'unknown';
3
+ export interface PasswordAuthError {
4
+ code: PasswordAuthErrorCode;
5
+ message: string;
6
+ /** HTTP status code, when the error came from the REST API. */
7
+ status?: number;
8
+ /** Seconds the caller should wait before retrying — only set for `rate_limited` / `locked_out`. */
9
+ retryAfterSeconds?: number;
10
+ }
11
+ export type PasswordAuthAction = 'login' | 'register' | 'reset-request' | 'reset-confirm' | 'change' | 'set';
12
+ export interface PasswordAuthResult {
13
+ ok: boolean;
14
+ error?: PasswordAuthError;
15
+ }
16
+ declare function classifyError(err: unknown): PasswordAuthError;
17
+ export declare function usePasswordAuth(): {
18
+ busy: import("vue").Ref<PasswordAuthAction | null, PasswordAuthAction | null>;
19
+ error: import("vue").Ref<{
20
+ code: PasswordAuthErrorCode;
21
+ message: string;
22
+ status?: number
23
+ /** Seconds the caller should wait before retrying — only set for `rate_limited` / `locked_out`. */
24
+ | undefined;
25
+ retryAfterSeconds?: number | undefined;
26
+ } | null, PasswordAuthError | {
27
+ code: PasswordAuthErrorCode;
28
+ message: string;
29
+ status?: number
30
+ /** Seconds the caller should wait before retrying — only set for `rate_limited` / `locked_out`. */
31
+ | undefined;
32
+ retryAfterSeconds?: number | undefined;
33
+ } | null>;
34
+ lastSuccess: import("vue").Ref<PasswordAuthAction | null, PasswordAuthAction | null>;
35
+ clearError: () => void;
36
+ login: (opts: {
37
+ username: string;
38
+ password: string;
39
+ }) => Promise<PasswordAuthResult>;
40
+ register: (opts: {
41
+ username: string;
42
+ password: string;
43
+ email?: string;
44
+ displayName?: string;
45
+ inviteCode?: string;
46
+ }) => Promise<PasswordAuthResult>;
47
+ requestReset: (opts: {
48
+ identifier: string;
49
+ }) => Promise<PasswordAuthResult>;
50
+ confirmReset: (opts: {
51
+ token: string;
52
+ newPassword: string;
53
+ }) => Promise<PasswordAuthResult>;
54
+ changePassword: (opts: {
55
+ currentPassword: string;
56
+ newPassword: string;
57
+ }) => Promise<PasswordAuthResult>;
58
+ setPassword: (opts: {
59
+ password: string;
60
+ }) => Promise<PasswordAuthResult>;
61
+ /** Internal helper exposed for testing — classify any error to its code. */
62
+ _classifyError: typeof classifyError;
63
+ };
64
+ export {};
@@ -0,0 +1,126 @@
1
+ import { ref } from "vue";
2
+ import { useAbracadabraAuth } from "./useAbracadabraAuth.js";
3
+ function extractServerMessage(raw) {
4
+ const trailing = raw.replace(/ \(\d+\)$/, "").trim();
5
+ const colon = trailing.indexOf(": ");
6
+ if (colon === -1) return trailing;
7
+ const head = trailing.slice(0, colon);
8
+ if (/^[A-Z]+ \//.test(head)) return trailing.slice(colon + 2).trim();
9
+ return trailing;
10
+ }
11
+ function classifyError(err) {
12
+ if (err instanceof TypeError && /fetch|network/i.test(err.message)) {
13
+ return { code: "network_error", message: err.message };
14
+ }
15
+ if (!(err instanceof Error)) {
16
+ return { code: "unknown", message: String(err) };
17
+ }
18
+ const status = err.status;
19
+ const serverMsg = extractServerMessage(err.message);
20
+ const lower = serverMsg.toLowerCase();
21
+ const retryMatch = lower.match(/retry(?:[-\s]?after)?[:\s]+(\d+)/);
22
+ const retryAfterSeconds = retryMatch ? Number(retryMatch[1]) : void 0;
23
+ if (status === 423) {
24
+ return { code: "locked_out", message: serverMsg, status, retryAfterSeconds };
25
+ }
26
+ if (status === 429) {
27
+ return { code: "rate_limited", message: serverMsg, status, retryAfterSeconds };
28
+ }
29
+ if (status === 410 || /token (?:expired|invalid)/.test(lower)) {
30
+ return {
31
+ code: lower.includes("expired") ? "token_expired" : "token_invalid",
32
+ message: serverMsg,
33
+ status
34
+ };
35
+ }
36
+ if (lower.includes("password too short")) {
37
+ return { code: "password_too_short", message: serverMsg, status };
38
+ }
39
+ if (lower.includes("password_already_set")) {
40
+ return { code: "password_already_set", message: serverMsg, status };
41
+ }
42
+ if (lower.includes("password login is disabled")) {
43
+ return { code: "password_login_disabled", message: serverMsg, status };
44
+ }
45
+ if (lower.includes("password reset disabled") || lower.includes("password reset is disabled")) {
46
+ return { code: "password_reset_disabled", message: serverMsg, status };
47
+ }
48
+ if (lower.includes("username") && (lower.includes("taken") || lower.includes("exists") || lower.includes("in use"))) {
49
+ return { code: "username_taken", message: serverMsg, status };
50
+ }
51
+ if (lower.includes("email") && (lower.includes("taken") || lower.includes("exists") || lower.includes("in use"))) {
52
+ return { code: "email_taken", message: serverMsg, status };
53
+ }
54
+ if (lower.includes("invite required") || lower.includes("invite is required")) {
55
+ return { code: "invite_required", message: serverMsg, status };
56
+ }
57
+ if (lower.includes("invite") && (lower.includes("invalid") || lower.includes("expired") || lower.includes("used"))) {
58
+ return { code: "invite_invalid", message: serverMsg, status };
59
+ }
60
+ if (status === 401 || lower.includes("unauthorized") || lower.includes("invalid credentials") || lower.includes("bad password")) {
61
+ return { code: status === 401 ? "unauthorized" : "invalid_credentials", message: serverMsg, status };
62
+ }
63
+ if (status === 403) {
64
+ return { code: "forbidden", message: serverMsg, status };
65
+ }
66
+ if (typeof status === "number" && status >= 500) {
67
+ return { code: "server_error", message: serverMsg, status };
68
+ }
69
+ return { code: "unknown", message: serverMsg, status };
70
+ }
71
+ export function usePasswordAuth() {
72
+ const auth = useAbracadabraAuth();
73
+ const busy = ref(null);
74
+ const error = ref(null);
75
+ const lastSuccess = ref(null);
76
+ function clearError() {
77
+ error.value = null;
78
+ }
79
+ async function run(action, fn) {
80
+ busy.value = action;
81
+ error.value = null;
82
+ try {
83
+ await fn();
84
+ lastSuccess.value = action;
85
+ return { ok: true };
86
+ } catch (e) {
87
+ const err = classifyError(e);
88
+ error.value = err;
89
+ return { ok: false, error: err };
90
+ } finally {
91
+ busy.value = null;
92
+ }
93
+ }
94
+ async function login(opts) {
95
+ return run("login", () => auth.loginWithPassword(opts));
96
+ }
97
+ async function register(opts) {
98
+ return run("register", () => auth.registerWithPassword(opts));
99
+ }
100
+ async function requestReset(opts) {
101
+ return run("reset-request", () => auth.requestPasswordReset(opts));
102
+ }
103
+ async function confirmReset(opts) {
104
+ return run("reset-confirm", () => auth.confirmPasswordReset(opts));
105
+ }
106
+ async function changePassword(opts) {
107
+ return run("change", () => auth.changePassword(opts));
108
+ }
109
+ async function setPassword(opts) {
110
+ return run("set", () => auth.setPassword(opts.password));
111
+ }
112
+ return {
113
+ busy,
114
+ error,
115
+ lastSuccess,
116
+ clearError,
117
+ login,
118
+ register,
119
+ requestReset,
120
+ confirmReset,
121
+ changePassword,
122
+ setPassword,
123
+ /** Internal helper exposed for testing — classify any error to its code. */
124
+ _classifyError: classifyError
125
+ };
126
+ }
@@ -12,9 +12,9 @@
12
12
  * const history = useTiptapHistory(editorRef)
13
13
  * <UButton :disabled="!history.canUndo.value" @click="history.undo" />
14
14
  */
15
- import { type MaybeRef } from 'vue';
15
+ import { type MaybeRefOrGetter } from 'vue';
16
16
  import type { Editor } from '@tiptap/vue-3';
17
- export declare function useTiptapHistory(editor: MaybeRef<Editor | null | undefined>): {
17
+ export declare function useTiptapHistory(editor: MaybeRefOrGetter<Editor | null | undefined>): {
18
18
  canUndo: import("vue").ComputedRef<boolean>;
19
19
  canRedo: import("vue").ComputedRef<boolean>;
20
20
  undo: () => void;
@@ -1,12 +1,12 @@
1
- import { computed, unref } from "vue";
1
+ import { computed, toValue } from "vue";
2
2
  export function useTiptapHistory(editor) {
3
- const canUndo = computed(() => !!unref(editor)?.can?.().undo?.());
4
- const canRedo = computed(() => !!unref(editor)?.can?.().redo?.());
3
+ const canUndo = computed(() => !!toValue(editor)?.can?.().undo?.());
4
+ const canRedo = computed(() => !!toValue(editor)?.can?.().redo?.());
5
5
  function undo() {
6
- unref(editor)?.commands?.undo?.();
6
+ toValue(editor)?.commands?.undo?.();
7
7
  }
8
8
  function redo() {
9
- unref(editor)?.commands?.redo?.();
9
+ toValue(editor)?.commands?.redo?.();
10
10
  }
11
11
  return { canUndo, canRedo, undo, redo };
12
12
  }
@@ -140,12 +140,23 @@ function onDateTimeRange(range) {
140
140
  [endKey.value]: `${dvToIso(range.end)}T${et}:00.000Z`
141
141
  });
142
142
  }
143
- function onStartTime(t2) {
143
+ function asSingleTime(t2) {
144
+ if (t2 && typeof t2 === "object" && "hour" in t2 && "minute" in t2) {
145
+ const v = t2;
146
+ if (typeof v.hour === "number" && typeof v.minute === "number") {
147
+ return { hour: v.hour, minute: v.minute };
148
+ }
149
+ }
150
+ return null;
151
+ }
152
+ function onStartTime(raw) {
153
+ const t2 = asSingleTime(raw);
144
154
  if (!t2) return;
145
155
  const date = getStr(startKey.value).slice(0, 10) || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
146
156
  patch({ [startKey.value]: `${date}T${timeToHHMM(t2)}:00.000Z` });
147
157
  }
148
- function onEndTime(t2) {
158
+ function onEndTime(raw) {
159
+ const t2 = asSingleTime(raw);
149
160
  if (!t2) return;
150
161
  const date = getStr(endKey.value).slice(0, 10) || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
151
162
  patch({ [endKey.value]: `${date}T${timeToHHMM(t2)}:00.000Z` });
@@ -257,15 +268,18 @@ function onSingleDatetimeDate(d) {
257
268
  const time = getStr(metaKey.value).slice(11, 16) || "00:00";
258
269
  patch({ [metaKey.value]: `${date}T${time}:00.000Z` });
259
270
  }
260
- function onSingleTime(t2) {
271
+ function onSingleTime(raw) {
272
+ const t2 = asSingleTime(raw);
261
273
  if (!t2) return;
262
274
  patch({ [metaKey.value]: timeToHHMM(t2) });
263
275
  }
264
- function onTimeRangeStart(t2) {
276
+ function onTimeRangeStart(raw) {
277
+ const t2 = asSingleTime(raw);
265
278
  if (!t2) return;
266
279
  patch({ [startKey.value]: timeToHHMM(t2) });
267
280
  }
268
- function onTimeRangeEnd(t2) {
281
+ function onTimeRangeEnd(raw) {
282
+ const t2 = asSingleTime(raw);
269
283
  if (!t2) return;
270
284
  patch({ [endKey.value]: timeToHHMM(t2) });
271
285
  }
@@ -1089,7 +1103,10 @@ function removeOption(opt) {
1089
1103
  <UInputTime
1090
1104
  size="sm"
1091
1105
  :model-value="isoToTime(getStr(metaKey).slice(11, 16) || '00:00')"
1092
- @update:model-value="(t2) => t2 && patch({ [metaKey]: `${getStr(metaKey).slice(0, 10) || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}T${timeToHHMM(t2)}:00.000Z` })"
1106
+ @update:model-value="(raw) => {
1107
+ const t2 = asSingleTime(raw);
1108
+ if (t2) patch({ [metaKey]: `${getStr(metaKey).slice(0, 10) || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}T${timeToHHMM(t2)}:00.000Z` });
1109
+ }"
1093
1110
  />
1094
1111
  </div>
1095
1112
  </template>