jazz-vue 0.9.23 → 0.10.1

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.
@@ -1,77 +1,37 @@
1
- import { AgentSecret } from "cojson";
2
- import { BrowserDemoAuth } from "jazz-browser";
3
- import { Account, ID } from "jazz-tools";
4
- import { onUnmounted, reactive, ref } from "vue";
5
- import { logoutHandler } from "../provider.js";
1
+ import { DemoAuth } from "jazz-tools";
2
+ import { computed, ref, watch } from "vue";
3
+ import { useAuthSecretStorage, useJazzContext } from "../composables.js";
4
+ import { useIsAuthenticated } from "./useIsAuthenticated.js";
6
5
 
7
- export type DemoAuthState = (
8
- | {
9
- state: "uninitialized";
10
- }
11
- | {
12
- state: "loading";
13
- }
14
- | {
15
- state: "ready";
16
- existingUsers: string[];
17
- signUp: (username: string) => void;
18
- logInAs: (existingUser: string) => void;
19
- }
20
- | {
21
- state: "signedIn";
22
- logOut: () => void;
23
- }
24
- ) & {
25
- errors: string[];
26
- };
6
+ export function useDemoAuth() {
7
+ const context = useJazzContext();
8
+ const authSecretStorage = useAuthSecretStorage();
27
9
 
28
- /** @category Auth Providers */
29
- export function useDemoAuth({
30
- seedAccounts,
31
- }: {
32
- seedAccounts?: {
33
- [name: string]: { accountID: ID<Account>; accountSecret: AgentSecret };
34
- };
35
- } = {}) {
36
- const state = reactive<DemoAuthState>({
37
- state: "loading",
38
- errors: [],
39
- });
10
+ if ("guest" in context.value) {
11
+ throw new Error("Demo auth is not supported in guest mode");
12
+ }
40
13
 
41
- const authMethod = ref(
42
- new BrowserDemoAuth(
43
- {
44
- onReady: ({ signUp, existingUsers, logInAs }) => {
45
- state.state = "ready";
46
- (state as DemoAuthState & { state: "ready" }).signUp = signUp;
47
- (state as DemoAuthState & { state: "ready" }).existingUsers =
48
- existingUsers;
49
- (state as DemoAuthState & { state: "ready" }).logInAs = logInAs;
50
- state.errors = [];
51
- },
52
- onSignedIn: ({ logOut }) => {
53
- state.state = "signedIn";
54
- (state as DemoAuthState & { state: "signedIn" }).logOut = () => {
55
- logOut();
56
- state.state = "ready";
57
- state.errors = [];
58
- };
59
- state.errors = [];
60
- logoutHandler.value = (
61
- state as DemoAuthState & { state: "signedIn" }
62
- ).logOut;
63
- },
64
- onError: (error) => {
65
- state.errors.push(error.toString());
66
- },
67
- },
68
- seedAccounts,
69
- ),
14
+ const authMethod = computed(
15
+ () => new DemoAuth(context.value.authenticate, authSecretStorage),
70
16
  );
71
- onUnmounted(() => {
72
- if (state.state === "signedIn") {
73
- logoutHandler.value = undefined;
74
- }
17
+
18
+ const existingUsers = ref<string[]>([]);
19
+ const isAuthenticated = useIsAuthenticated();
20
+
21
+ watch(authMethod, () => {
22
+ authMethod.value.getExistingUsers().then((users) => {
23
+ existingUsers.value = users;
24
+ });
75
25
  });
76
- return { authMethod, state };
26
+
27
+ return computed(() => ({
28
+ state: isAuthenticated.value ? "signedIn" : "anonymous",
29
+ logIn(username: string) {
30
+ authMethod.value.logIn(username);
31
+ },
32
+ signUp(username: string) {
33
+ authMethod.value.signUp(username);
34
+ },
35
+ existingUsers: existingUsers.value,
36
+ }));
77
37
  }
@@ -0,0 +1,18 @@
1
+ import { onMounted, onUnmounted, ref } from "vue";
2
+ import { useAuthSecretStorage } from "../composables.js";
3
+
4
+ export function useIsAuthenticated() {
5
+ const authSecretStorage = useAuthSecretStorage();
6
+ const isAuthenticated = ref(authSecretStorage.isAuthenticated);
7
+
8
+ const handleUpdate = () => {
9
+ isAuthenticated.value = authSecretStorage.isAuthenticated;
10
+ };
11
+
12
+ onMounted(() => {
13
+ const cleanup = authSecretStorage.onUpdate(handleUpdate);
14
+ onUnmounted(cleanup);
15
+ });
16
+
17
+ return isAuthenticated;
18
+ }
@@ -0,0 +1,46 @@
1
+ import { BrowserPasskeyAuth } from "jazz-browser";
2
+ import { computed } from "vue";
3
+ import { useAuthSecretStorage, useJazzContext } from "../composables.js";
4
+ import { useIsAuthenticated } from "./useIsAuthenticated.js";
5
+
6
+ /**
7
+ * `usePasskeyAuth` composable provides a `JazzAuth` object for passkey authentication.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const auth = usePasskeyAuth({ appName, appHostname });
12
+ * ```
13
+ *
14
+ * @category Auth Providers
15
+ */
16
+ export function usePasskeyAuth({
17
+ appName,
18
+ appHostname,
19
+ }: {
20
+ appName: string;
21
+ appHostname?: string;
22
+ }) {
23
+ const context = useJazzContext();
24
+ const authSecretStorage = useAuthSecretStorage();
25
+ const isAuthenticated = useIsAuthenticated();
26
+
27
+ if ("guest" in context.value) {
28
+ throw new Error("Passkey auth is not supported in guest mode");
29
+ }
30
+
31
+ const authMethod = computed(() => {
32
+ return new BrowserPasskeyAuth(
33
+ context.value.node.crypto,
34
+ context.value.authenticate,
35
+ authSecretStorage,
36
+ appName,
37
+ appHostname,
38
+ );
39
+ });
40
+
41
+ return computed(() => ({
42
+ state: isAuthenticated.value ? "signedIn" : "anonymous",
43
+ logIn: authMethod.value.logIn,
44
+ signUp: authMethod.value.signUp,
45
+ }));
46
+ }
@@ -0,0 +1,56 @@
1
+ import { PassphraseAuth } from "jazz-tools";
2
+ import { computed, ref, watchEffect } from "vue";
3
+ import { useAuthSecretStorage, useJazzContext } from "../composables.js";
4
+ import { useIsAuthenticated } from "./useIsAuthenticated.js";
5
+
6
+ /**
7
+ * `usePassphraseAuth` composable provides a `JazzAuth` object for passphrase authentication.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const auth = usePassphraseAuth({ wordlist });
12
+ * ```
13
+ *
14
+ * @category Auth Providers
15
+ */
16
+ export function usePassphraseAuth({
17
+ wordlist,
18
+ }: {
19
+ wordlist: string[];
20
+ }) {
21
+ const context = useJazzContext();
22
+ const authSecretStorage = useAuthSecretStorage();
23
+ const isAuthenticated = useIsAuthenticated();
24
+
25
+ if ("guest" in context.value) {
26
+ throw new Error("Passphrase auth is not supported in guest mode");
27
+ }
28
+
29
+ const authMethod = computed(() => {
30
+ return new PassphraseAuth(
31
+ context.value.node.crypto,
32
+ context.value.authenticate,
33
+ authSecretStorage,
34
+ wordlist,
35
+ );
36
+ });
37
+
38
+ const passphrase = ref(authMethod.value.passphrase);
39
+
40
+ watchEffect((onCleanup) => {
41
+ authMethod.value.loadCurrentAccountPassphrase();
42
+
43
+ const unsubscribe = authMethod.value.subscribe(() => {
44
+ passphrase.value = authMethod.value.passphrase;
45
+ });
46
+
47
+ onCleanup(unsubscribe);
48
+ });
49
+
50
+ return computed(() => ({
51
+ state: isAuthenticated.value ? "signedIn" : "anonymous",
52
+ logIn: authMethod.value.logIn,
53
+ signUp: authMethod.value.signUp,
54
+ passphrase: passphrase.value,
55
+ }));
56
+ }
@@ -1,16 +1,16 @@
1
- import {
2
- BrowserContext,
3
- BrowserGuestContext,
4
- consumeInviteLinkFromWindowLocation,
5
- } from "jazz-browser";
1
+ import { consumeInviteLinkFromWindowLocation } from "jazz-browser";
6
2
  import {
7
3
  Account,
8
4
  AnonymousJazzAgent,
5
+ AuthSecretStorage,
9
6
  CoValue,
10
7
  CoValueClass,
11
8
  DeeplyLoaded,
12
9
  DepthsIn,
13
10
  ID,
11
+ JazzAuthContext,
12
+ JazzContextType,
13
+ JazzGuestContext,
14
14
  subscribeToCoValue,
15
15
  } from "jazz-tools";
16
16
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -29,21 +29,31 @@ import {
29
29
  unref,
30
30
  watch,
31
31
  } from "vue";
32
- import { JazzContextSymbol, RegisteredAccount } from "./provider.js";
32
+ import {
33
+ JazzAuthContextSymbol,
34
+ JazzContextSymbol,
35
+ RegisteredAccount,
36
+ } from "./provider.js";
33
37
 
34
38
  export const logoutHandler = ref<() => void>();
35
39
 
36
- function useJazzContext() {
40
+ export function useJazzContext() {
37
41
  const context =
38
- inject<Ref<BrowserContext<RegisteredAccount> | BrowserGuestContext>>(
39
- JazzContextSymbol,
40
- );
41
- if (!context) {
42
+ inject<Ref<JazzContextType<RegisteredAccount>>>(JazzContextSymbol);
43
+ if (!context?.value) {
42
44
  throw new Error("useJazzContext must be used within a JazzProvider");
43
45
  }
44
46
  return context;
45
47
  }
46
48
 
49
+ export function useAuthSecretStorage() {
50
+ const context = inject<AuthSecretStorage>(JazzAuthContextSymbol);
51
+ if (!context) {
52
+ throw new Error("useAuthSecretStorage must be used within a JazzProvider");
53
+ }
54
+ return context;
55
+ }
56
+
47
57
  export function createUseAccountComposables<Acc extends Account>() {
48
58
  function useAccount(): {
49
59
  me: ComputedRef<Acc>;
@@ -52,13 +62,13 @@ export function createUseAccountComposables<Acc extends Account>() {
52
62
  function useAccount<D extends DepthsIn<Acc>>(
53
63
  depth: D,
54
64
  ): {
55
- me: ComputedRef<DeeplyLoaded<Acc, D> | undefined>;
65
+ me: ComputedRef<DeeplyLoaded<Acc, D> | undefined | null>;
56
66
  logOut: () => void;
57
67
  };
58
68
  function useAccount<D extends DepthsIn<Acc>>(
59
69
  depth?: D,
60
70
  ): {
61
- me: ComputedRef<Acc | DeeplyLoaded<Acc, D> | undefined>;
71
+ me: ComputedRef<Acc | DeeplyLoaded<Acc, D> | undefined | null>;
62
72
  logOut: () => void;
63
73
  } {
64
74
  const context = useJazzContext();
@@ -85,7 +95,7 @@ export function createUseAccountComposables<Acc extends Account>() {
85
95
  me: computed(() => {
86
96
  const value =
87
97
  depth === undefined
88
- ? me.value || toRaw((context.value as BrowserContext<Acc>).me)
98
+ ? me.value || toRaw((context.value as JazzAuthContext<Acc>).me)
89
99
  : me.value;
90
100
 
91
101
  return value ? toRaw(value) : value;
@@ -100,13 +110,15 @@ export function createUseAccountComposables<Acc extends Account>() {
100
110
  function useAccountOrGuest<D extends DepthsIn<Acc>>(
101
111
  depth: D,
102
112
  ): {
103
- me: ComputedRef<DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent>;
113
+ me: ComputedRef<
114
+ DeeplyLoaded<Acc, D> | undefined | null | AnonymousJazzAgent
115
+ >;
104
116
  };
105
117
  function useAccountOrGuest<D extends DepthsIn<Acc>>(
106
118
  depth?: D,
107
119
  ): {
108
120
  me: ComputedRef<
109
- Acc | DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent
121
+ Acc | DeeplyLoaded<Acc, D> | undefined | null | AnonymousJazzAgent
110
122
  >;
111
123
  } {
112
124
  const context = useJazzContext();
@@ -129,13 +141,13 @@ export function createUseAccountComposables<Acc extends Account>() {
129
141
  return {
130
142
  me: computed(() =>
131
143
  depth === undefined
132
- ? me.value || toRaw((context.value as BrowserContext<Acc>).me)
144
+ ? me.value || toRaw((context.value as JazzAuthContext<Acc>).me)
133
145
  : me.value,
134
146
  ),
135
147
  };
136
148
  } else {
137
149
  return {
138
- me: computed(() => toRaw((context.value as BrowserGuestContext).guest)),
150
+ me: computed(() => toRaw((context.value as JazzGuestContext).guest)),
139
151
  };
140
152
  }
141
153
  }
@@ -155,8 +167,8 @@ export function useCoState<V extends CoValue, D>(
155
167
  Schema: CoValueClass<V>,
156
168
  id: MaybeRef<ID<V> | undefined>,
157
169
  depth: D & DepthsIn<V> = [] as D & DepthsIn<V>,
158
- ): Ref<DeeplyLoaded<V, D> | undefined> {
159
- const state: ShallowRef<DeeplyLoaded<V, D> | undefined> =
170
+ ): Ref<DeeplyLoaded<V, D> | undefined | null> {
171
+ const state: ShallowRef<DeeplyLoaded<V, D> | undefined | null> =
160
172
  shallowRef(undefined);
161
173
  const context = useJazzContext();
162
174
 
@@ -184,7 +196,9 @@ export function useCoState<V extends CoValue, D>(
184
196
  (value) => {
185
197
  state.value = value;
186
198
  },
187
- undefined,
199
+ () => {
200
+ state.value = null;
201
+ },
188
202
  true,
189
203
  );
190
204
  },
@@ -223,7 +237,7 @@ export function useAcceptInvite<V extends CoValue>({
223
237
 
224
238
  const runInviteAcceptance = () => {
225
239
  const result = consumeInviteLinkFromWindowLocation({
226
- as: toRaw((context.value as BrowserContext<RegisteredAccount>).me),
240
+ as: toRaw((context.value as JazzAuthContext<RegisteredAccount>).me),
227
241
  invitedObjectSchema,
228
242
  forValueHint,
229
243
  });
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from "./composables.js";
2
2
  export { JazzProvider } from "./provider.js";
3
3
  export * from "./auth/useDemoAuth.js";
4
+ export * from "./auth/usePassphraseAuth.js";
5
+ export * from "./auth/usePasskeyAuth.js";
4
6
  export { default as DemoAuthBasicUI } from "./auth/DemoAuthBasicUI.vue";
5
7
  export { default as ProgressiveImg } from "./ProgressiveImg.vue";
6
8
 
package/src/provider.ts CHANGED
@@ -1,12 +1,9 @@
1
- import {
2
- BrowserContext,
3
- BrowserGuestContext,
4
- createJazzBrowserContext,
5
- } from "jazz-browser";
6
- import { Account, AccountClass, AuthMethod } from "jazz-tools";
1
+ import { JazzBrowserContextManager } from "jazz-browser";
2
+ import { Account, AccountClass, JazzContextType, SyncConfig } from "jazz-tools";
7
3
  /* eslint-disable @typescript-eslint/no-explicit-any */
8
4
  import {
9
5
  PropType,
6
+ computed,
10
7
  defineComponent,
11
8
  onMounted,
12
9
  onUnmounted,
@@ -25,78 +22,80 @@ export type RegisteredAccount = Register extends { Account: infer Acc }
25
22
  : Account;
26
23
 
27
24
  export const JazzContextSymbol = Symbol("JazzContext");
28
-
25
+ export const JazzAuthContextSymbol = Symbol("JazzAuthContext");
29
26
  export const JazzProvider = defineComponent({
30
27
  name: "JazzProvider",
31
28
  props: {
32
- auth: {
33
- type: [String, Object] as PropType<AuthMethod | "guest">,
29
+ guestMode: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ sync: {
34
+ type: Object as PropType<SyncConfig>,
34
35
  required: true,
35
36
  },
36
37
  AccountSchema: {
37
- type: Object as PropType<AccountClass<RegisteredAccount>>,
38
+ type: Function as unknown as PropType<AccountClass<RegisteredAccount>>,
38
39
  required: false,
39
40
  },
40
- peer: {
41
- type: String as PropType<`wss://${string}` | `ws://${string}`>,
42
- required: true,
43
- },
44
41
  storage: {
45
42
  type: String as PropType<"indexedDB" | "singleTabOPFS">,
46
43
  default: undefined,
47
44
  },
45
+ defaultProfileName: {
46
+ type: String,
47
+ required: false,
48
+ },
49
+ onAnonymousAccountDiscarded: {
50
+ type: Function as PropType<
51
+ (anonymousAccount: RegisteredAccount) => Promise<void>
52
+ >,
53
+ required: false,
54
+ },
55
+ onLogOut: {
56
+ type: Function as PropType<() => void>,
57
+ required: false,
58
+ },
48
59
  },
49
60
  setup(props, { slots }) {
50
- const ctx = ref<
51
- BrowserContext<RegisteredAccount> | BrowserGuestContext | undefined
52
- >(undefined);
53
-
54
- const key = ref(0);
61
+ const contextManager = new JazzBrowserContextManager<RegisteredAccount>();
62
+ const ctx = ref<JazzContextType<RegisteredAccount>>();
55
63
 
56
64
  provide(JazzContextSymbol, ctx);
57
-
58
- const initializeContext = async () => {
59
- if (ctx.value) {
60
- ctx.value.done?.();
61
- ctx.value = undefined;
62
- }
63
-
64
- try {
65
- const context = await createJazzBrowserContext<RegisteredAccount>(
66
- props.auth === "guest"
67
- ? { peer: props.peer, storage: props.storage }
68
- : {
69
- AccountSchema: props.AccountSchema,
70
- auth: props.auth,
71
- peer: props.peer,
72
- storage: props.storage,
73
- },
74
- );
75
-
76
- ctx.value = {
77
- ...context,
78
- logOut: () => {
79
- logoutHandler.value?.();
80
- // context.logOut();
81
- key.value += 1;
82
- },
83
- };
84
- } catch (e) {
85
- console.error("Error creating Jazz browser context:", e);
86
- }
87
- };
88
-
89
- onMounted(() => {
90
- void initializeContext();
91
- });
65
+ provide(JazzAuthContextSymbol, contextManager.getAuthSecretStorage());
92
66
 
93
67
  watch(
94
- () => key.value,
68
+ () => ({
69
+ peer: props.sync.peer,
70
+ syncWhen: props.sync.when,
71
+ storage: props.storage,
72
+ guestMode: props.guestMode,
73
+ }),
95
74
  async () => {
96
- await initializeContext();
75
+ contextManager
76
+ .createContext({
77
+ sync: props.sync,
78
+ storage: props.storage,
79
+ guestMode: props.guestMode,
80
+ AccountSchema: props.AccountSchema,
81
+ defaultProfileName: props.defaultProfileName,
82
+ onAnonymousAccountDiscarded: props.onAnonymousAccountDiscarded,
83
+ onLogOut: props.onLogOut,
84
+ })
85
+ .catch((error) => {
86
+ console.error("Error creating Jazz browser context:", error);
87
+ });
97
88
  },
89
+ { immediate: true },
98
90
  );
99
91
 
92
+ onMounted(() => {
93
+ const cleanup = contextManager.subscribe(() => {
94
+ ctx.value = contextManager.getCurrentValue();
95
+ });
96
+ onUnmounted(cleanup);
97
+ });
98
+
100
99
  onUnmounted(() => {
101
100
  if (ctx.value) ctx.value.done?.();
102
101
  });
package/src/testing.ts CHANGED
@@ -1,20 +1,31 @@
1
1
  import { Account, AnonymousJazzAgent } from "jazz-tools";
2
- import { getJazzContextShape } from "jazz-tools/testing";
2
+ import { TestJazzContextManager } from "jazz-tools/testing";
3
3
  import { provide } from "vue";
4
4
  import { PropType, defineComponent, ref } from "vue";
5
- import { JazzContextSymbol } from "./provider.js";
5
+ import { JazzAuthContextSymbol, JazzContextSymbol } from "./provider.js";
6
6
 
7
7
  export const JazzTestProvider = defineComponent({
8
8
  name: "JazzTestProvider",
9
9
  props: {
10
10
  account: {
11
11
  type: Object as PropType<Account | { guest: AnonymousJazzAgent }>,
12
- required: true,
12
+ required: false,
13
+ },
14
+ isAuthenticated: {
15
+ type: Boolean,
16
+ required: false,
13
17
  },
14
18
  },
15
19
  setup(props, { slots }) {
16
- const ctx = ref(getJazzContextShape(props.account));
17
- provide(JazzContextSymbol, ctx);
20
+ const contextManager = TestJazzContextManager.fromAccountOrGuest(
21
+ props.account,
22
+ {
23
+ isAuthenticated: props.isAuthenticated,
24
+ },
25
+ );
26
+
27
+ provide(JazzContextSymbol, ref(contextManager.getCurrentValue()));
28
+ provide(JazzAuthContextSymbol, contextManager.getAuthSecretStorage());
18
29
 
19
30
  return () => slots.default?.();
20
31
  },