jazz-tools 0.18.5 → 0.18.7
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/.turbo/turbo-build.log +57 -57
- package/CHANGELOG.md +33 -0
- package/dist/better-auth/auth/client.d.ts.map +1 -1
- package/dist/better-auth/auth/client.js +7 -1
- package/dist/better-auth/auth/client.js.map +1 -1
- package/dist/better-auth/auth/react.d.ts +0 -2145
- package/dist/better-auth/auth/react.d.ts.map +1 -1
- package/dist/better-auth/auth/react.js +2 -14
- package/dist/better-auth/auth/react.js.map +1 -1
- package/dist/better-auth/auth/server.d.ts.map +1 -1
- package/dist/better-auth/auth/server.js +77 -22
- package/dist/better-auth/auth/server.js.map +1 -1
- package/dist/better-auth/auth/tests/react.test.d.ts +2 -0
- package/dist/better-auth/auth/tests/react.test.d.ts.map +1 -0
- package/dist/{chunk-3LE7N6TH.js → chunk-CFAY3FMQ.js} +192 -101
- package/dist/chunk-CFAY3FMQ.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/inspector/{custom-element-WCY6D3QJ.js → custom-element-G6SPZEBR.js} +308 -97
- package/dist/inspector/custom-element-G6SPZEBR.js.map +1 -0
- package/dist/inspector/index.d.ts +5 -1
- package/dist/inspector/index.d.ts.map +1 -1
- package/dist/inspector/index.js +318 -56
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/ui/button.d.ts +1 -1
- package/dist/inspector/ui/button.d.ts.map +1 -1
- package/dist/inspector/ui/heading.d.ts +2 -1
- package/dist/inspector/ui/heading.d.ts.map +1 -1
- package/dist/inspector/ui/input.d.ts.map +1 -1
- package/dist/inspector/ui/modal.d.ts +16 -0
- package/dist/inspector/ui/modal.d.ts.map +1 -0
- package/dist/inspector/viewer/delete-local-data.d.ts +2 -0
- package/dist/inspector/viewer/delete-local-data.d.ts.map +1 -0
- package/dist/inspector/viewer/{inpsector-button.d.ts → inspector-button.d.ts} +1 -1
- package/dist/inspector/viewer/{inpsector-button.d.ts.map → inspector-button.d.ts.map} +1 -1
- package/dist/inspector/viewer/new-app.d.ts +1 -4
- package/dist/inspector/viewer/new-app.d.ts.map +1 -1
- package/dist/react/hooks.d.ts +1 -1
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react-core/hooks.d.ts +133 -0
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +83 -17
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-core/tests/useCoStateWithSelector.test.d.ts +2 -0
- package/dist/react-core/tests/useCoStateWithSelector.test.d.ts.map +1 -0
- package/dist/react-native-core/hooks.d.ts +1 -1
- package/dist/react-native-core/hooks.d.ts.map +1 -1
- package/dist/react-native-core/index.js +3 -1
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/dist/tools/coValues/CoValueBase.d.ts +14 -0
- package/dist/tools/coValues/CoValueBase.d.ts.map +1 -1
- package/dist/tools/coValues/coMap.d.ts +0 -12
- package/dist/tools/coValues/coMap.d.ts.map +1 -1
- package/dist/tools/coValues/inbox.d.ts +5 -5
- package/dist/tools/coValues/inbox.d.ts.map +1 -1
- package/dist/tools/implementation/createContext.d.ts +2 -1
- package/dist/tools/implementation/createContext.d.ts.map +1 -1
- package/dist/tools/tests/utils.d.ts.map +1 -1
- package/dist/worker/index.d.ts +12 -2
- package/dist/worker/index.d.ts.map +1 -1
- package/dist/worker/index.js +10 -4
- package/dist/worker/index.js.map +1 -1
- package/package.json +6 -4
- package/src/better-auth/auth/client.ts +8 -2
- package/src/better-auth/auth/react.tsx +2 -51
- package/src/better-auth/auth/server.ts +98 -24
- package/src/better-auth/auth/tests/client.test.ts +92 -4
- package/src/better-auth/auth/tests/react.test.tsx +43 -0
- package/src/better-auth/auth/tests/server.test.ts +276 -98
- package/src/inspector/custom-element.tsx +1 -1
- package/src/inspector/index.tsx +44 -0
- package/src/inspector/ui/button.tsx +15 -1
- package/src/inspector/ui/heading.tsx +7 -2
- package/src/inspector/ui/input.tsx +6 -2
- package/src/inspector/ui/modal.tsx +158 -0
- package/src/inspector/viewer/delete-local-data.tsx +101 -0
- package/src/inspector/viewer/new-app.tsx +3 -19
- package/src/react/hooks.tsx +1 -0
- package/src/react/index.ts +1 -0
- package/src/react-core/hooks.ts +162 -0
- package/src/react-core/tests/useCoStateWithSelector.test.ts +149 -0
- package/src/react-native-core/hooks.tsx +1 -0
- package/src/tools/coValues/CoValueBase.ts +32 -0
- package/src/tools/coValues/coList.ts +35 -0
- package/src/tools/coValues/coMap.ts +0 -18
- package/src/tools/coValues/inbox.ts +190 -108
- package/src/tools/implementation/createContext.ts +9 -2
- package/src/tools/testing.ts +1 -1
- package/src/tools/tests/coFeed.test.ts +33 -22
- package/src/tools/tests/coList.test.ts +47 -4
- package/src/tools/tests/coMap.test.ts +13 -5
- package/src/tools/tests/coPlainText.test.ts +24 -0
- package/src/tools/tests/createContext.test.ts +24 -0
- package/src/tools/tests/deepLoading.test.ts +2 -0
- package/src/tools/tests/exportImport.test.ts +3 -1
- package/src/tools/tests/groupsAndAccounts.test.ts +56 -44
- package/src/tools/tests/inbox.test.ts +293 -31
- package/src/tools/tests/patterns/requestToJoin.test.ts +14 -6
- package/src/tools/tests/utils.ts +1 -0
- package/src/worker/index.ts +21 -5
- package/tsup.config.ts +1 -1
- package/dist/chunk-3LE7N6TH.js.map +0 -1
- package/dist/inspector/custom-element-WCY6D3QJ.js.map +0 -1
- package/src/inspector/index.ts +0 -23
- /package/src/inspector/viewer/{inpsector-button.tsx → inspector-button.tsx} +0 -0
package/dist/worker/index.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../src/worker/index.ts"],"sourcesContent":["import { AgentSecret, CryptoProvider, LocalNode, Peer } from \"cojson\";\nimport {\n type AnyWebSocketConstructor,\n WebSocketPeerWithReconnection,\n} from \"cojson-transport-ws\";\nimport { WasmCrypto } from \"cojson/crypto/WasmCrypto\";\nimport {\n Account,\n AccountClass,\n AnyAccountSchema,\n CoValueFromRaw,\n Inbox,\n InstanceOfSchema,\n createJazzContextFromExistingCredentials,\n randomSessionProvider,\n} from \"jazz-tools\";\n\ntype WorkerOptions<\n S extends\n | (AccountClass<Account> & CoValueFromRaw<Account>)\n | AnyAccountSchema,\n> = {\n accountID?: string;\n accountSecret?: string;\n syncServer?: string;\n WebSocket?: AnyWebSocketConstructor;\n AccountSchema?: S;\n crypto?: CryptoProvider;\n};\n\n/** @category Context Creation */\nexport async function startWorker<\n S extends\n | (AccountClass<Account> & CoValueFromRaw<Account>)\n | AnyAccountSchema,\n>(options: WorkerOptions<S>) {\n const {\n accountID = process.env.JAZZ_WORKER_ACCOUNT,\n accountSecret = process.env.JAZZ_WORKER_SECRET,\n syncServer = \"wss://cloud.jazz.tools\",\n AccountSchema = Account as unknown as S,\n } = options;\n\n let node: LocalNode | undefined = undefined;\n\n const peersToLoadFrom: Peer[] = [];\n\n const wsPeer = new WebSocketPeerWithReconnection({\n peer: syncServer,\n reconnectionTimeout: 100,\n addPeer: (peer) => {\n if (node) {\n node.syncManager.addPeer(peer);\n } else {\n peersToLoadFrom.push(peer);\n }\n },\n removePeer: () => {},\n WebSocketConstructor: options.WebSocket,\n });\n\n wsPeer.enable();\n\n if (!accountID) {\n throw new Error(\"No accountID provided\");\n }\n if (!accountSecret) {\n throw new Error(\"No accountSecret provided\");\n }\n if (!accountID.startsWith(\"co_\")) {\n throw new Error(\"Invalid accountID\");\n }\n if (!accountSecret?.startsWith(\"sealerSecret_\")) {\n throw new Error(\"Invalid accountSecret\");\n }\n\n const context = await createJazzContextFromExistingCredentials({\n credentials: {\n accountID: accountID,\n secret: accountSecret as AgentSecret,\n },\n AccountSchema,\n // TODO: locked sessions similar to browser\n sessionProvider: randomSessionProvider,\n peersToLoadFrom,\n crypto: options.crypto ?? (await WasmCrypto.create()),\n });\n\n const account = context.account as InstanceOfSchema<S>;\n node = account.$jazz.localNode;\n\n if (!account.$jazz.refs.profile?.id) {\n throw new Error(\"Account has no profile\");\n }\n\n const inbox = await Inbox.load(account);\n\n async function done() {\n await context.account.$jazz.waitForAllCoValuesSync();\n\n wsPeer.disable();\n context.done();\n }\n\n const inboxPublicApi = {\n
|
1
|
+
{"version":3,"sources":["../../src/worker/index.ts"],"sourcesContent":["import { AgentSecret, CryptoProvider, LocalNode, Peer } from \"cojson\";\nimport {\n type AnyWebSocketConstructor,\n WebSocketPeerWithReconnection,\n} from \"cojson-transport-ws\";\nimport { WasmCrypto } from \"cojson/crypto/WasmCrypto\";\nimport {\n Account,\n AccountClass,\n AnyAccountSchema,\n CoValueFromRaw,\n Inbox,\n InstanceOfSchema,\n Loaded,\n createJazzContextFromExistingCredentials,\n randomSessionProvider,\n} from \"jazz-tools\";\n\ntype WorkerOptions<\n S extends\n | (AccountClass<Account> & CoValueFromRaw<Account>)\n | AnyAccountSchema,\n> = {\n accountID?: string;\n accountSecret?: string;\n syncServer?: string;\n WebSocket?: AnyWebSocketConstructor;\n AccountSchema?: S;\n crypto?: CryptoProvider;\n /**\n * If true, the inbox will not be loaded.\n */\n skipInboxLoad?: boolean;\n /**\n * If false, the worker will not set in the global account context\n */\n asActiveAccount?: boolean;\n};\n\n/** @category Context Creation */\nexport async function startWorker<\n S extends\n | (AccountClass<Account> & CoValueFromRaw<Account>)\n | AnyAccountSchema,\n>(options: WorkerOptions<S>) {\n const {\n accountID = process.env.JAZZ_WORKER_ACCOUNT,\n accountSecret = process.env.JAZZ_WORKER_SECRET,\n syncServer = \"wss://cloud.jazz.tools\",\n AccountSchema = Account as unknown as S,\n skipInboxLoad = false,\n asActiveAccount = true,\n } = options;\n\n let node: LocalNode | undefined = undefined;\n\n const peersToLoadFrom: Peer[] = [];\n\n const wsPeer = new WebSocketPeerWithReconnection({\n peer: syncServer,\n reconnectionTimeout: 100,\n addPeer: (peer) => {\n if (node) {\n node.syncManager.addPeer(peer);\n } else {\n peersToLoadFrom.push(peer);\n }\n },\n removePeer: () => {},\n WebSocketConstructor: options.WebSocket,\n });\n\n wsPeer.enable();\n\n if (!accountID) {\n throw new Error(\"No accountID provided\");\n }\n if (!accountSecret) {\n throw new Error(\"No accountSecret provided\");\n }\n if (!accountID.startsWith(\"co_\")) {\n throw new Error(\"Invalid accountID\");\n }\n if (!accountSecret?.startsWith(\"sealerSecret_\")) {\n throw new Error(\"Invalid accountSecret\");\n }\n\n const context = await createJazzContextFromExistingCredentials({\n credentials: {\n accountID: accountID,\n secret: accountSecret as AgentSecret,\n },\n AccountSchema,\n // TODO: locked sessions similar to browser\n sessionProvider: randomSessionProvider,\n peersToLoadFrom,\n crypto: options.crypto ?? (await WasmCrypto.create()),\n asActiveAccount,\n });\n\n const account = context.account as InstanceOfSchema<S>;\n node = account.$jazz.localNode;\n\n if (!account.$jazz.refs.profile?.id) {\n throw new Error(\"Account has no profile\");\n }\n\n const inbox = skipInboxLoad ? undefined : await Inbox.load(account);\n\n async function done() {\n await context.account.$jazz.waitForAllCoValuesSync();\n\n wsPeer.disable();\n context.done();\n }\n\n const inboxPublicApi = inbox\n ? {\n subscribe: inbox.subscribe.bind(inbox) as Inbox[\"subscribe\"],\n }\n : {\n subscribe: () => {},\n };\n\n return {\n worker: context.account as Loaded<S>,\n experimental: {\n inbox: inboxPublicApi,\n },\n waitForConnection() {\n return wsPeer.waitUntilConnected();\n },\n subscribeToConnectionChange(listener: (connected: boolean) => void) {\n wsPeer.subscribe(listener);\n\n return () => {\n wsPeer.unsubscribe(listener);\n };\n },\n done,\n };\n}\n"],"mappings":";AACA;AAAA,EAEE;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EAIA;AAAA,EAGA;AAAA,EACA;AAAA,OACK;AAwBP,eAAsB,YAIpB,SAA2B;AAC3B,QAAM;AAAA,IACJ,YAAY,QAAQ,IAAI;AAAA,IACxB,gBAAgB,QAAQ,IAAI;AAAA,IAC5B,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,kBAAkB;AAAA,EACpB,IAAI;AAEJ,MAAI,OAA8B;AAElC,QAAM,kBAA0B,CAAC;AAEjC,QAAM,SAAS,IAAI,8BAA8B;AAAA,IAC/C,MAAM;AAAA,IACN,qBAAqB;AAAA,IACrB,SAAS,CAAC,SAAS;AACjB,UAAI,MAAM;AACR,aAAK,YAAY,QAAQ,IAAI;AAAA,MAC/B,OAAO;AACL,wBAAgB,KAAK,IAAI;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,YAAY,MAAM;AAAA,IAAC;AAAA,IACnB,sBAAsB,QAAQ;AAAA,EAChC,CAAC;AAED,SAAO,OAAO;AAEd,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,uBAAuB;AAAA,EACzC;AACA,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,2BAA2B;AAAA,EAC7C;AACA,MAAI,CAAC,UAAU,WAAW,KAAK,GAAG;AAChC,UAAM,IAAI,MAAM,mBAAmB;AAAA,EACrC;AACA,MAAI,CAAC,eAAe,WAAW,eAAe,GAAG;AAC/C,UAAM,IAAI,MAAM,uBAAuB;AAAA,EACzC;AAEA,QAAM,UAAU,MAAM,yCAAyC;AAAA,IAC7D,aAAa;AAAA,MACX;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,IACA;AAAA;AAAA,IAEA,iBAAiB;AAAA,IACjB;AAAA,IACA,QAAQ,QAAQ,UAAW,MAAM,WAAW,OAAO;AAAA,IACnD;AAAA,EACF,CAAC;AAED,QAAM,UAAU,QAAQ;AACxB,SAAO,QAAQ,MAAM;AAErB,MAAI,CAAC,QAAQ,MAAM,KAAK,SAAS,IAAI;AACnC,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC1C;AAEA,QAAM,QAAQ,gBAAgB,SAAY,MAAM,MAAM,KAAK,OAAO;AAElE,iBAAe,OAAO;AACpB,UAAM,QAAQ,QAAQ,MAAM,uBAAuB;AAEnD,WAAO,QAAQ;AACf,YAAQ,KAAK;AAAA,EACf;AAEA,QAAM,iBAAiB,QACnB;AAAA,IACE,WAAW,MAAM,UAAU,KAAK,KAAK;AAAA,EACvC,IACA;AAAA,IACE,WAAW,MAAM;AAAA,IAAC;AAAA,EACpB;AAEJ,SAAO;AAAA,IACL,QAAQ,QAAQ;AAAA,IAChB,cAAc;AAAA,MACZ,OAAO;AAAA,IACT;AAAA,IACA,oBAAoB;AAClB,aAAO,OAAO,mBAAmB;AAAA,IACnC;AAAA,IACA,4BAA4B,UAAwC;AAClE,aAAO,UAAU,QAAQ;AAEzB,aAAO,MAAM;AACX,eAAO,YAAY,QAAQ;AAAA,MAC7B;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
@@ -167,7 +167,7 @@
|
|
167
167
|
},
|
168
168
|
"type": "module",
|
169
169
|
"license": "MIT",
|
170
|
-
"version": "0.18.
|
170
|
+
"version": "0.18.7",
|
171
171
|
"dependencies": {
|
172
172
|
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
|
173
173
|
"@scure/base": "1.2.1",
|
@@ -182,10 +182,11 @@
|
|
182
182
|
"prosemirror-schema-basic": "^1.2.2",
|
183
183
|
"prosemirror-state": "^1.4.3",
|
184
184
|
"prosemirror-transform": "^1.9.0",
|
185
|
+
"use-sync-external-store": "^1.5.0",
|
185
186
|
"zod": "3.25.76",
|
186
|
-
"cojson": "0.18.
|
187
|
-
"cojson-storage-indexeddb": "0.18.
|
188
|
-
"cojson-transport-ws": "0.18.
|
187
|
+
"cojson": "0.18.7",
|
188
|
+
"cojson-storage-indexeddb": "0.18.7",
|
189
|
+
"cojson-transport-ws": "0.18.7"
|
189
190
|
},
|
190
191
|
"devDependencies": {
|
191
192
|
"@scure/bip39": "^1.3.0",
|
@@ -197,6 +198,7 @@
|
|
197
198
|
"@testing-library/svelte": "^5.2.6",
|
198
199
|
"@types/react": "19.1.0",
|
199
200
|
"@types/react-dom": "19.1.0",
|
201
|
+
"@types/use-sync-external-store": "^1.5.0",
|
200
202
|
"@vitest/browser": "^3.2.4",
|
201
203
|
"msw": "^2.10.3",
|
202
204
|
"oauth2-mock-server": "^8.1.0",
|
@@ -7,6 +7,13 @@ import type {
|
|
7
7
|
} from "jazz-tools";
|
8
8
|
import type { jazzPlugin } from "./server.js";
|
9
9
|
|
10
|
+
const SIGNUP_URLS = [
|
11
|
+
"/sign-up",
|
12
|
+
"/sign-in/social",
|
13
|
+
"/sign-in/oauth2",
|
14
|
+
"/email-otp/send-verification-otp",
|
15
|
+
];
|
16
|
+
|
10
17
|
/**
|
11
18
|
* @example
|
12
19
|
* ```ts
|
@@ -83,8 +90,7 @@ export const jazzPluginClient = () => {
|
|
83
90
|
hooks: {
|
84
91
|
async onRequest(context) {
|
85
92
|
if (
|
86
|
-
context.url.toString().includes(
|
87
|
-
context.url.toString().includes("/sign-in/social")
|
93
|
+
SIGNUP_URLS.some((url) => context.url.toString().includes(url))
|
88
94
|
) {
|
89
95
|
const credentials = await authSecretStorage.get();
|
90
96
|
|
@@ -1,21 +1,9 @@
|
|
1
1
|
"use client";
|
2
2
|
|
3
|
-
import type { ClientOptions } from "better-auth";
|
4
3
|
import { createAuthClient } from "better-auth/client";
|
5
|
-
import
|
6
|
-
Account,
|
7
|
-
AccountClass,
|
8
|
-
AnyAccountSchema,
|
9
|
-
CoValueFromRaw,
|
10
|
-
} from "jazz-tools";
|
11
|
-
import {
|
12
|
-
type JazzProviderProps,
|
13
|
-
JazzReactProvider,
|
14
|
-
useAuthSecretStorage,
|
15
|
-
useJazzContext,
|
16
|
-
} from "jazz-tools/react";
|
4
|
+
import { useAuthSecretStorage, useJazzContext } from "jazz-tools/react-core";
|
17
5
|
import { useEffect } from "react";
|
18
|
-
import { type PropsWithChildren
|
6
|
+
import { type PropsWithChildren } from "react";
|
19
7
|
import { jazzPluginClient } from "./client.js";
|
20
8
|
|
21
9
|
type AuthClient = ReturnType<
|
@@ -24,8 +12,6 @@ type AuthClient = ReturnType<
|
|
24
12
|
}>
|
25
13
|
>;
|
26
14
|
|
27
|
-
export const AuthContext = createContext<AuthClient | null>(null);
|
28
|
-
|
29
15
|
/**
|
30
16
|
* @param props.children - The children to render.
|
31
17
|
* @param props.betterAuthClient - The BetterAuth client with the Jazz plugin.
|
@@ -68,38 +54,3 @@ export function AuthProvider({
|
|
68
54
|
|
69
55
|
return children;
|
70
56
|
}
|
71
|
-
|
72
|
-
/**
|
73
|
-
* @param props - The props for the JazzReactProvider.
|
74
|
-
* @param props.betterAuth - The options for the BetterAuth client.
|
75
|
-
* @returns The JazzReactProvider with the BetterAuth plugin.
|
76
|
-
*
|
77
|
-
* @example
|
78
|
-
* ```ts
|
79
|
-
* <JazzReactProviderWithBetterAuth
|
80
|
-
* betterAuth={{
|
81
|
-
* baseURL: "http://localhost:3000",
|
82
|
-
* }}
|
83
|
-
* sync={{
|
84
|
-
* peer: "ws://localhost:4200",
|
85
|
-
* }}
|
86
|
-
* >
|
87
|
-
* <App />
|
88
|
-
* </JazzReactProviderWithBetterAuth>
|
89
|
-
* ```
|
90
|
-
*/
|
91
|
-
export const JazzReactProviderWithBetterAuth = <
|
92
|
-
S extends
|
93
|
-
| (AccountClass<Account> & CoValueFromRaw<Account>)
|
94
|
-
| AnyAccountSchema,
|
95
|
-
>(
|
96
|
-
props: { betterAuthClient: AuthClient } & JazzProviderProps<S>,
|
97
|
-
) => {
|
98
|
-
return (
|
99
|
-
<JazzReactProvider {...props}>
|
100
|
-
<AuthProvider betterAuthClient={props.betterAuthClient}>
|
101
|
-
{props.children}
|
102
|
-
</AuthProvider>
|
103
|
-
</JazzReactProvider>
|
104
|
-
);
|
105
|
-
};
|
@@ -85,20 +85,27 @@ export const jazzPlugin: () => JazzPlugin = () => {
|
|
85
85
|
},
|
86
86
|
verification: {
|
87
87
|
create: {
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
88
|
+
after: async (verification, context) => {
|
89
|
+
/**
|
90
|
+
* For: Email OTP plugin
|
91
|
+
* After a verification is created, if it is from the EmailOTP plugin,
|
92
|
+
* create a new verification value with the jazzAuth with the same expiration.
|
93
|
+
*/
|
94
|
+
if (
|
95
|
+
contextContainsJazzAuth(context) &&
|
96
|
+
verification.identifier.startsWith("sign-in-otp-")
|
97
|
+
) {
|
98
|
+
const identifier = `jazz-auth-${verification.identifier}`;
|
99
|
+
await context.context.internalAdapter.deleteVerificationValue(
|
100
|
+
identifier,
|
101
|
+
);
|
102
|
+
await context.context.internalAdapter.createVerificationValue(
|
103
|
+
{
|
104
|
+
value: JSON.stringify({ jazzAuth: context.jazzAuth }),
|
105
|
+
identifier: identifier,
|
106
|
+
expiresAt: verification.expiresAt,
|
100
107
|
},
|
101
|
-
|
108
|
+
);
|
102
109
|
}
|
103
110
|
},
|
104
111
|
},
|
@@ -147,6 +154,7 @@ export const jazzPlugin: () => JazzPlugin = () => {
|
|
147
154
|
},
|
148
155
|
|
149
156
|
/**
|
157
|
+
* For: Social / OAuth2 plugin
|
150
158
|
* /callback is the endpoint that BetterAuth uses to authenticate the user coming from a social provider.
|
151
159
|
* 1. Catch the state
|
152
160
|
* 2. Find the verification value
|
@@ -162,17 +170,12 @@ export const jazzPlugin: () => JazzPlugin = () => {
|
|
162
170
|
handler: createAuthMiddleware(async (ctx) => {
|
163
171
|
const state = ctx.query?.state || ctx.body?.state;
|
164
172
|
|
165
|
-
const
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
value: state,
|
172
|
-
},
|
173
|
-
],
|
174
|
-
select: ["value"],
|
175
|
-
});
|
173
|
+
const identifier = `jazz-auth-${state}`;
|
174
|
+
|
175
|
+
const data =
|
176
|
+
await ctx.context.internalAdapter.findVerificationValue(
|
177
|
+
identifier,
|
178
|
+
);
|
176
179
|
|
177
180
|
// if not found, the social plugin will throw later anyway
|
178
181
|
if (!data) {
|
@@ -197,6 +200,45 @@ export const jazzPlugin: () => JazzPlugin = () => {
|
|
197
200
|
}
|
198
201
|
}),
|
199
202
|
},
|
203
|
+
/**
|
204
|
+
* For: Email OTP plugin
|
205
|
+
* When the user sends an OTP, we try to find the jazzAuth.
|
206
|
+
* If it isn't a sign-up, we expect to not find a verification value.
|
207
|
+
*/
|
208
|
+
{
|
209
|
+
matcher: (context) => {
|
210
|
+
return context.path.startsWith("/sign-in/email-otp");
|
211
|
+
},
|
212
|
+
handler: createAuthMiddleware(async (ctx) => {
|
213
|
+
const email = ctx.body.email;
|
214
|
+
const identifier = `jazz-auth-sign-in-otp-${email}`;
|
215
|
+
|
216
|
+
const data =
|
217
|
+
await ctx.context.internalAdapter.findVerificationValue(
|
218
|
+
identifier,
|
219
|
+
);
|
220
|
+
|
221
|
+
// if not found, it isn't a sign-up
|
222
|
+
if (!data || data.expiresAt < new Date()) {
|
223
|
+
return;
|
224
|
+
}
|
225
|
+
|
226
|
+
const parsed = JSON.parse(data.value);
|
227
|
+
|
228
|
+
if (parsed && "jazzAuth" in parsed) {
|
229
|
+
return {
|
230
|
+
context: {
|
231
|
+
...ctx,
|
232
|
+
jazzAuth: parsed.jazzAuth,
|
233
|
+
},
|
234
|
+
};
|
235
|
+
} else {
|
236
|
+
throw new APIError(500, {
|
237
|
+
message: "JazzAuth not found in verification value",
|
238
|
+
});
|
239
|
+
}
|
240
|
+
}),
|
241
|
+
},
|
200
242
|
],
|
201
243
|
after: [
|
202
244
|
/**
|
@@ -227,6 +269,38 @@ export const jazzPlugin: () => JazzPlugin = () => {
|
|
227
269
|
});
|
228
270
|
}),
|
229
271
|
},
|
272
|
+
|
273
|
+
/**
|
274
|
+
* For: Social / OAuth2 plugin
|
275
|
+
* When the user sign-in via social, we create a verification value with the jazzAuth.
|
276
|
+
*/
|
277
|
+
{
|
278
|
+
matcher: (context) => {
|
279
|
+
return context.path.startsWith("/sign-in/social");
|
280
|
+
},
|
281
|
+
handler: createAuthMiddleware(async (ctx) => {
|
282
|
+
if (!contextContainsJazzAuth(ctx)) {
|
283
|
+
throw new APIError(500, {
|
284
|
+
message: "JazzAuth not found in context",
|
285
|
+
});
|
286
|
+
}
|
287
|
+
|
288
|
+
const returned = ctx.context.returned as { url: string };
|
289
|
+
|
290
|
+
const url = new URL(returned.url);
|
291
|
+
const state = url.searchParams.get("state");
|
292
|
+
|
293
|
+
const value = JSON.stringify({ jazzAuth: ctx.jazzAuth });
|
294
|
+
const expiresAt = new Date();
|
295
|
+
expiresAt.setMinutes(expiresAt.getMinutes() + 10);
|
296
|
+
|
297
|
+
await ctx.context.internalAdapter.createVerificationValue({
|
298
|
+
value,
|
299
|
+
identifier: `jazz-auth-${state}`,
|
300
|
+
expiresAt,
|
301
|
+
});
|
302
|
+
}),
|
303
|
+
},
|
230
304
|
],
|
231
305
|
},
|
232
306
|
} satisfies JazzPlugin;
|
@@ -7,14 +7,19 @@ import {
|
|
7
7
|
} from "jazz-tools/testing";
|
8
8
|
import { assert, beforeEach, describe, expect, it, vi } from "vitest";
|
9
9
|
import { jazzPluginClient } from "../client.js";
|
10
|
+
import { emailOTPClient, genericOAuthClient } from "better-auth/client/plugins";
|
10
11
|
|
11
|
-
describe("
|
12
|
+
describe("Better-Auth client plugin", () => {
|
12
13
|
let account: Account;
|
13
14
|
let jazzContextManager: TestJazzContextManager<Account>;
|
14
15
|
let authSecretStorage: AuthSecretStorage;
|
15
16
|
let authClient: ReturnType<
|
16
17
|
typeof createAuthClient<{
|
17
|
-
plugins: ReturnType<
|
18
|
+
plugins: ReturnType<
|
19
|
+
| typeof jazzPluginClient
|
20
|
+
| typeof emailOTPClient
|
21
|
+
| typeof genericOAuthClient
|
22
|
+
>[];
|
18
23
|
}>
|
19
24
|
>;
|
20
25
|
let customFetchImpl = vi.fn();
|
@@ -31,7 +36,7 @@ describe("auth client", () => {
|
|
31
36
|
|
32
37
|
authClient = createAuthClient({
|
33
38
|
baseURL: "http://localhost:3000",
|
34
|
-
plugins: [jazzPluginClient()],
|
39
|
+
plugins: [jazzPluginClient(), emailOTPClient(), genericOAuthClient()],
|
35
40
|
fetchOptions: {
|
36
41
|
customFetchImpl,
|
37
42
|
},
|
@@ -245,5 +250,88 @@ describe("auth client", () => {
|
|
245
250
|
expect(anonymousCredentials).not.toMatchObject(credentials!);
|
246
251
|
});
|
247
252
|
|
248
|
-
it
|
253
|
+
it("should send Jazz credentials using social login", async () => {
|
254
|
+
const credentials = await authSecretStorage.get();
|
255
|
+
assert(credentials, "Jazz credentials are not available");
|
256
|
+
|
257
|
+
customFetchImpl.mockResolvedValue(new Response(JSON.stringify({})));
|
258
|
+
|
259
|
+
// Sign up
|
260
|
+
await authClient.signIn.social({
|
261
|
+
provider: "github",
|
262
|
+
});
|
263
|
+
|
264
|
+
expect(customFetchImpl).toHaveBeenCalledTimes(1);
|
265
|
+
expect(customFetchImpl.mock.calls[0]![0].toString()).toBe(
|
266
|
+
"http://localhost:3000/api/auth/sign-in/social",
|
267
|
+
);
|
268
|
+
|
269
|
+
// Verify the credentials have been injected in the request body
|
270
|
+
expect(
|
271
|
+
customFetchImpl.mock.calls[0]![1].headers.get("x-jazz-auth")!,
|
272
|
+
).toEqual(
|
273
|
+
JSON.stringify({
|
274
|
+
accountID: credentials!.accountID,
|
275
|
+
secretSeed: credentials!.secretSeed,
|
276
|
+
accountSecret: credentials!.accountSecret,
|
277
|
+
}),
|
278
|
+
);
|
279
|
+
});
|
280
|
+
|
281
|
+
it("should send Jazz credentials using oauth generic plugin", async () => {
|
282
|
+
const credentials = await authSecretStorage.get();
|
283
|
+
assert(credentials, "Jazz credentials are not available");
|
284
|
+
|
285
|
+
customFetchImpl.mockResolvedValue(new Response(JSON.stringify({})));
|
286
|
+
|
287
|
+
// Sign up
|
288
|
+
await authClient.signIn.oauth2({
|
289
|
+
providerId: "github",
|
290
|
+
});
|
291
|
+
|
292
|
+
expect(customFetchImpl).toHaveBeenCalledTimes(1);
|
293
|
+
expect(customFetchImpl.mock.calls[0]![0].toString()).toBe(
|
294
|
+
"http://localhost:3000/api/auth/sign-in/oauth2",
|
295
|
+
);
|
296
|
+
|
297
|
+
// Verify the credentials have been injected in the request body
|
298
|
+
expect(
|
299
|
+
customFetchImpl.mock.calls[0]![1].headers.get("x-jazz-auth")!,
|
300
|
+
).toEqual(
|
301
|
+
JSON.stringify({
|
302
|
+
accountID: credentials!.accountID,
|
303
|
+
secretSeed: credentials!.secretSeed,
|
304
|
+
accountSecret: credentials!.accountSecret,
|
305
|
+
}),
|
306
|
+
);
|
307
|
+
});
|
308
|
+
|
309
|
+
it("should send Jazz credentials using email OTP", async () => {
|
310
|
+
const credentials = await authSecretStorage.get();
|
311
|
+
assert(credentials, "Jazz credentials are not available");
|
312
|
+
|
313
|
+
customFetchImpl.mockResolvedValue(new Response(JSON.stringify({})));
|
314
|
+
|
315
|
+
// Sign up
|
316
|
+
await authClient.emailOtp.sendVerificationOtp({
|
317
|
+
email: "test@jazz.dev",
|
318
|
+
type: "sign-in",
|
319
|
+
});
|
320
|
+
|
321
|
+
expect(customFetchImpl).toHaveBeenCalledTimes(1);
|
322
|
+
expect(customFetchImpl.mock.calls[0]![0].toString()).toBe(
|
323
|
+
"http://localhost:3000/api/auth/email-otp/send-verification-otp",
|
324
|
+
);
|
325
|
+
|
326
|
+
// Verify the credentials have been injected in the request body
|
327
|
+
expect(
|
328
|
+
customFetchImpl.mock.calls[0]![1].headers.get("x-jazz-auth")!,
|
329
|
+
).toEqual(
|
330
|
+
JSON.stringify({
|
331
|
+
accountID: credentials!.accountID,
|
332
|
+
secretSeed: credentials!.secretSeed,
|
333
|
+
accountSecret: credentials!.accountSecret,
|
334
|
+
}),
|
335
|
+
);
|
336
|
+
});
|
249
337
|
});
|
@@ -0,0 +1,43 @@
|
|
1
|
+
// @vitest-environment jsdom
|
2
|
+
import { render } from "@testing-library/react";
|
3
|
+
import { describe, expect, it } from "vitest";
|
4
|
+
import { AuthProvider } from "../react";
|
5
|
+
import { createAuthClient } from "better-auth/client";
|
6
|
+
import { jazzPluginClient } from "../client";
|
7
|
+
import { JazzReactProvider } from "jazz-tools/react";
|
8
|
+
|
9
|
+
describe("AuthProvider", () => {
|
10
|
+
it("should throw if no JazzContext is set", () => {
|
11
|
+
const betterAuthClient = createAuthClient({
|
12
|
+
plugins: [jazzPluginClient()],
|
13
|
+
});
|
14
|
+
|
15
|
+
expect(() => {
|
16
|
+
render(
|
17
|
+
<AuthProvider betterAuthClient={betterAuthClient}>
|
18
|
+
<div />
|
19
|
+
</AuthProvider>,
|
20
|
+
);
|
21
|
+
}).toThrow(
|
22
|
+
"You need to set up a JazzProvider on top of your app to use this hook.",
|
23
|
+
);
|
24
|
+
});
|
25
|
+
|
26
|
+
it("should render with JazzReactProvider", () => {
|
27
|
+
const betterAuthClient = createAuthClient({
|
28
|
+
plugins: [jazzPluginClient()],
|
29
|
+
});
|
30
|
+
|
31
|
+
render(
|
32
|
+
<JazzReactProvider
|
33
|
+
// @ts-expect-error - no memory storage
|
34
|
+
storage={["memory"]}
|
35
|
+
sync={{ peer: "ws://", when: "never" }}
|
36
|
+
>
|
37
|
+
<AuthProvider betterAuthClient={betterAuthClient}>
|
38
|
+
<div />
|
39
|
+
</AuthProvider>
|
40
|
+
</JazzReactProvider>,
|
41
|
+
);
|
42
|
+
});
|
43
|
+
});
|