Compare commits
84 Commits
jazz-brows
...
jazz-tools
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eee2330325 | ||
|
|
b83ec05ccc | ||
|
|
386525db48 | ||
|
|
a8809d840c | ||
|
|
005fc1f8c9 | ||
|
|
3129982582 | ||
|
|
8da6f3a897 | ||
|
|
dca9293ae7 | ||
|
|
2b362fd331 | ||
|
|
f03f1b6de8 | ||
|
|
bd57177586 | ||
|
|
9e0e2709a5 | ||
|
|
01b2ab7148 | ||
|
|
44b7d39467 | ||
|
|
5373ee2858 | ||
|
|
b19cab78d3 | ||
|
|
3f86dfce4f | ||
|
|
12f8bfa28f | ||
|
|
53211a4fca | ||
|
|
0a6cd4e9b2 | ||
|
|
69954caee6 | ||
|
|
6a5bd28d07 | ||
|
|
ba58bc3ace | ||
|
|
c3dd099ee1 | ||
|
|
882fd55d69 | ||
|
|
b2fdb8b9e5 | ||
|
|
e879ec981e | ||
|
|
980609ca87 | ||
|
|
71cd7396b7 | ||
|
|
dedc3e277c | ||
|
|
656866729d | ||
|
|
f93dd1f779 | ||
|
|
80d499f002 | ||
|
|
c9a87e52f3 | ||
|
|
3f98d9ab73 | ||
|
|
abb0e8fada | ||
|
|
be2e1f3c61 | ||
|
|
75f20d8176 | ||
|
|
e6bef5275b | ||
|
|
9813db1603 | ||
|
|
a3143f20a9 | ||
|
|
f54beb2d88 | ||
|
|
b7ce1e2da0 | ||
|
|
6dba138ec7 | ||
|
|
32f59a618f | ||
|
|
9b2de387ed | ||
|
|
b612258c5e | ||
|
|
09b59ed18b | ||
|
|
b7b186b67e | ||
|
|
f2ba925db6 | ||
|
|
bc9488241f | ||
|
|
4fc36779dd | ||
|
|
bc008aeb23 | ||
|
|
8ad45a421e | ||
|
|
197317efbf | ||
|
|
775ad975f3 | ||
|
|
eaedf455d4 | ||
|
|
017f6c8074 | ||
|
|
48dd922712 | ||
|
|
202b320ad1 | ||
|
|
ea094ae64b | ||
|
|
9412aeb938 | ||
|
|
4f5e471667 | ||
|
|
fc6b20d370 | ||
|
|
e4886d1b03 | ||
|
|
dfb2b19209 | ||
|
|
1a92d6b1e5 | ||
|
|
d5c1f49cc5 | ||
|
|
7d6ce843de | ||
|
|
0298f0eb29 | ||
|
|
794c56dfac | ||
|
|
6b9382b5e9 | ||
|
|
917e8a21d8 | ||
|
|
595e3c89df | ||
|
|
b9afa42662 | ||
|
|
5246a54118 | ||
|
|
a036391f69 | ||
|
|
6ad24315bb | ||
|
|
48a83c356d | ||
|
|
756d52d106 | ||
|
|
83876a3523 | ||
|
|
b77c6d4edc | ||
|
|
c74fc11b25 | ||
|
|
7a636bd8c2 |
2
.github/workflows/playwright.yml
vendored
2
.github/workflows/playwright.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
project: ["tests/e2e", "examples/chat", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/pets", "starters/react-passkey-auth"]
|
||||
project: ["tests/e2e", "examples/chat", "examples/clerk", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/pets", "starters/react-passkey-auth"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# chat-rn-expo-clerk
|
||||
|
||||
## 1.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-expo@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-react-native-media-images@0.13.4
|
||||
|
||||
## 1.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-expo@0.13.3
|
||||
- jazz-react-native-media-images@0.13.3
|
||||
|
||||
## 1.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -2,28 +2,39 @@ import "../global.css";
|
||||
import { ClerkLoaded, ClerkProvider } from "@clerk/clerk-expo";
|
||||
import { secureStore } from "@clerk/clerk-expo/secure-store";
|
||||
import { useFonts } from "expo-font";
|
||||
import { Slot } from "expo-router";
|
||||
import { Slot, useRouter, useSegments } from "expo-router";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
import { useIsAuthenticated, useJazzContext } from "jazz-expo";
|
||||
import React, { useEffect } from "react";
|
||||
import { tokenCache } from "../cache";
|
||||
import { JazzAndAuth } from "../src/auth-context";
|
||||
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
export default function RootLayout() {
|
||||
const [loaded] = useFonts({
|
||||
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||
});
|
||||
function InitialLayout() {
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const segments = useSegments();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}, [loaded]);
|
||||
const inAuthGroup = segments[0] === "(auth)";
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
if (isAuthenticated && inAuthGroup) {
|
||||
router.replace("/chat");
|
||||
} else if (!isAuthenticated && !inAuthGroup) {
|
||||
router.replace("/");
|
||||
}
|
||||
|
||||
SplashScreen.hideAsync();
|
||||
}, [isAuthenticated, segments, router]);
|
||||
|
||||
return <Slot />;
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
const [fontsLoaded] = useFonts({
|
||||
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||
});
|
||||
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
@@ -33,6 +44,17 @@ export default function RootLayout() {
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (fontsLoaded) {
|
||||
} else {
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
}
|
||||
}, [fontsLoaded]);
|
||||
|
||||
if (!fontsLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider
|
||||
tokenCache={tokenCache}
|
||||
@@ -41,7 +63,7 @@ export default function RootLayout() {
|
||||
>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
<InitialLayout />
|
||||
</JazzAndAuth>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-expo-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.95",
|
||||
"version": "1.0.97",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# chat-rn-expo
|
||||
|
||||
## 1.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-expo@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 1.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-expo@0.13.3
|
||||
|
||||
## 1.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn-expo",
|
||||
"version": "1.0.82",
|
||||
"version": "1.0.84",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-react-native@0.13.4
|
||||
|
||||
## 1.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [b19cab7]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- cojson-transport-ws@0.13.3
|
||||
- jazz-react-native@0.13.3
|
||||
|
||||
## 1.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.91",
|
||||
"version": "1.0.93",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-browser@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-vue@0.13.4
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-browser@0.13.3
|
||||
- jazz-vue@0.13.3
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.75",
|
||||
"version": "0.0.77",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.174
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.173
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- Updated dependencies [017f6c8]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.172
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.172",
|
||||
"version": "0.0.174",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
2
examples/clerk/.gitignore
vendored
2
examples/clerk/.gitignore
vendored
@@ -22,3 +22,5 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
playwright-report
|
||||
@@ -1,5 +1,25 @@
|
||||
# minimal-auth-clerk
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react-auth-clerk@0.13.4
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
- jazz-react-auth-clerk@0.13.3
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"name": "clerk",
|
||||
"private": true,
|
||||
"version": "0.0.71",
|
||||
"version": "0.0.73",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
@@ -19,6 +21,7 @@
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
|
||||
53
examples/clerk/playwright.config.ts
Normal file
53
examples/clerk/playwright.config.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import isCI from "is-ci";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// import dotenv from 'dotenv';
|
||||
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: isCI,
|
||||
/* Retry on CI only */
|
||||
retries: isCI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: isCI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:5173/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
permissions: ["clipboard-read", "clipboard-write"],
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm preview --port 5173",
|
||||
url: "http://localhost:5173/",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import { SignInButton } from "@clerk/clerk-react";
|
||||
import { SignInButton, SignOutButton } from "@clerk/clerk-react";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
function App() {
|
||||
const { me, logOut } = useAccount();
|
||||
const { me } = useAccount();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
@@ -11,7 +11,7 @@ function App() {
|
||||
<div className="container">
|
||||
<h1>You're logged in</h1>
|
||||
<p>Welcome back, {me?.profile?.name}</p>
|
||||
<button onClick={() => logOut()}>Logout</button>
|
||||
<SignOutButton>Logout</SignOutButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ClerkProvider, useClerk } from "@clerk/clerk-react";
|
||||
import { ClerkProvider, SignOutButton, useClerk } from "@clerk/clerk-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
@@ -28,12 +28,23 @@ function JazzProvider({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
// Route to test that when the Clerk user expires, the app is logged out
|
||||
if (location.search.includes("expirationTest")) {
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<SignOutButton>Simulate expiration</SignOutButton>
|
||||
</ClerkProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
} else {
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
}
|
||||
|
||||
36
examples/clerk/tests/expiration.spec.ts
Normal file
36
examples/clerk/tests/expiration.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("login & expiration", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
expect(page.getByText("You're not logged in")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Email address" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Password" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page.waitForURL("/");
|
||||
|
||||
await page.getByText("You're logged in").waitFor({ state: "visible" });
|
||||
|
||||
expect(page.getByText("You're logged in")).toBeVisible();
|
||||
|
||||
await page.goto("/?expirationTest");
|
||||
|
||||
// Simulate expiration by logging out outside of Jazz
|
||||
await page.getByRole("button", { name: "Simulate expiration" }).click();
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByText("You're not logged in").waitFor({ state: "visible" });
|
||||
});
|
||||
33
examples/clerk/tests/logout.spec.ts
Normal file
33
examples/clerk/tests/logout.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("login & logout", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
expect(page.getByText("You're not logged in")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Email address" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Password" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page.waitForURL("/");
|
||||
|
||||
await page.getByText("You're logged in").waitFor({ state: "visible" });
|
||||
|
||||
expect(page.getByText("You're logged in")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Logout" }).click();
|
||||
|
||||
await page.getByText("You're not logged in").waitFor({ state: "visible" });
|
||||
|
||||
expect(page.getByText("You're not logged in")).toBeVisible();
|
||||
});
|
||||
31
examples/clerk/tests/reload.spec.ts
Normal file
31
examples/clerk/tests/reload.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("login & reload", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
expect(page.getByText("You're not logged in")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Email address" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Password" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page.waitForURL("/");
|
||||
|
||||
await page.getByText("You're logged in").waitFor({ state: "visible" });
|
||||
|
||||
expect(page.getByText("You're logged in")).toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
|
||||
expect(page.getByText("You're logged in")).toBeVisible();
|
||||
});
|
||||
@@ -1,5 +1,22 @@
|
||||
# file-share-svelte
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-svelte@0.13.4
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-svelte@0.13.3
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "file-share-svelte",
|
||||
"version": "0.0.55",
|
||||
"version": "0.0.57",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# jazz-tailwind-demo-auth-starter
|
||||
|
||||
## 0.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- Updated dependencies [017f6c8]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "filestream",
|
||||
"private": true,
|
||||
"version": "0.0.11",
|
||||
"version": "0.0.13",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# form
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.1.13",
|
||||
"version": "0.1.15",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# image-upload
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.69",
|
||||
"version": "0.0.71",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# jazz-example-inspector
|
||||
|
||||
## 0.0.124
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.123
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b19cab7]
|
||||
- Updated dependencies [017f6c8]
|
||||
- cojson-transport-ws@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
|
||||
## 0.0.122
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-inspector-app",
|
||||
"private": true,
|
||||
"version": "0.0.122",
|
||||
"version": "0.0.124",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# multi-cursors
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multi-cursors",
|
||||
"private": true,
|
||||
"version": "0.0.65",
|
||||
"version": "0.0.67",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# multiauth
|
||||
|
||||
## 0.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react-auth-clerk@0.13.4
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
- jazz-react-auth-clerk@0.13.3
|
||||
|
||||
## 0.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multiauth",
|
||||
"private": true,
|
||||
"version": "0.0.12",
|
||||
"version": "0.0.14",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- Updated dependencies [017f6c8]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.93",
|
||||
"version": "0.0.95",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
54
examples/music-player/tests/auth.spec.ts
Normal file
54
examples/music-player/tests/auth.spec.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { BrowserContext, test } from "@playwright/test";
|
||||
import { HomePage } from "./pages/HomePage";
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
async function mockAuthenticator(context: BrowserContext) {
|
||||
await context.addInitScript(() => {
|
||||
Object.defineProperty(window.navigator, "credentials", {
|
||||
value: {
|
||||
...window.navigator.credentials,
|
||||
create: async () => ({
|
||||
type: "public-key",
|
||||
id: new Uint8Array([1, 2, 3, 4]),
|
||||
rawId: new Uint8Array([1, 2, 3, 4]),
|
||||
response: {
|
||||
clientDataJSON: new Uint8Array([1]),
|
||||
attestationObject: new Uint8Array([2]),
|
||||
},
|
||||
}),
|
||||
get: async () => ({
|
||||
type: "public-key",
|
||||
id: new Uint8Array([1, 2, 3, 4]),
|
||||
rawId: new Uint8Array([1, 2, 3, 4]),
|
||||
response: {
|
||||
authenticatorData: new Uint8Array([1]),
|
||||
clientDataJSON: new Uint8Array([2]),
|
||||
signature: new Uint8Array([3]),
|
||||
},
|
||||
}),
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Configure the authenticator
|
||||
test.beforeEach(async ({ context }) => {
|
||||
// Enable virtual authenticator environment
|
||||
await mockAuthenticator(context);
|
||||
});
|
||||
|
||||
test("sign up and log out", async ({ page: marioPage }) => {
|
||||
await marioPage.goto("/");
|
||||
|
||||
const marioHome = new HomePage(marioPage);
|
||||
|
||||
await marioHome.signUp("Mario");
|
||||
|
||||
await marioHome.logoutButton.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await marioHome.logOut();
|
||||
});
|
||||
@@ -11,8 +11,12 @@ export class HomePage {
|
||||
name: "Playlist title",
|
||||
});
|
||||
|
||||
loginButton = this.page.getByRole("button", {
|
||||
name: "Sign in",
|
||||
});
|
||||
|
||||
logoutButton = this.page.getByRole("button", {
|
||||
name: "Logout",
|
||||
name: "Sign out",
|
||||
});
|
||||
|
||||
async expectActiveTrackPlaying() {
|
||||
@@ -131,12 +135,12 @@ export class HomePage {
|
||||
await this.page
|
||||
.getByRole("button", { name: "Sign up with passkey" })
|
||||
.click();
|
||||
await expect(
|
||||
this.page.getByRole("button", { name: "Sign out" }),
|
||||
).toBeVisible();
|
||||
await expect(this.logoutButton).toBeVisible();
|
||||
}
|
||||
|
||||
async logout() {
|
||||
async logOut() {
|
||||
await this.logoutButton.click();
|
||||
|
||||
await expect(this.loginButton).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# organization
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.65",
|
||||
"version": "0.0.67",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.4
|
||||
|
||||
## 0.0.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.3
|
||||
|
||||
## 0.0.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.59",
|
||||
"version": "0.0.61",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# minimal-auth-passkey
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.70",
|
||||
"version": "0.0.72",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# passphrase
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passphrase",
|
||||
"private": true,
|
||||
"version": "0.0.67",
|
||||
"version": "0.0.69",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.91",
|
||||
"version": "0.0.93",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.191
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.190
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.189
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.189",
|
||||
"version": "0.0.191",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# reactions
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.69",
|
||||
"version": "0.0.71",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-browser@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-vue@0.13.4
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-browser@0.13.3
|
||||
- jazz-vue@0.13.3
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.73",
|
||||
"version": "0.0.75",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.190
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.189
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.188
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.188",
|
||||
"version": "0.0.190",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# version-history
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- Updated dependencies [017f6c8]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "version-history",
|
||||
"private": true,
|
||||
"version": "0.0.66",
|
||||
"version": "0.0.68",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
export function Icon404({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 556 517"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={clsx(className)}
|
||||
>
|
||||
<path
|
||||
d="M449.244 300.893C406.744 291.059 313.398 281.691 246.244 308.893C167.244 340.893 125.077 395.393 108.244 417.893"
|
||||
stroke="currentColor"
|
||||
strokeWidth="20"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M137.404 194H118.997V295.03C117.252 293.83 115.333 292.831 113.284 292.066C108.621 290.324 103.49 289.869 98.54 290.756C93.5899 291.644 89.043 293.836 85.4742 297.053C81.9054 300.271 79.475 304.371 78.4903 308.834C77.5057 313.297 78.0111 317.924 79.9425 322.128C81.8739 326.332 85.1447 329.925 89.3411 332.454C93.5376 334.982 98.4713 336.331 103.518 336.331C122.233 336.331 137.403 321.161 137.403 302.446L137.404 301.191V194Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M468.404 100H449.997V201.03C448.252 199.83 446.333 198.831 444.284 198.066C439.621 196.324 434.49 195.869 429.54 196.756C424.59 197.644 420.043 199.836 416.474 203.053C412.905 206.271 410.475 210.371 409.49 214.834C408.506 219.297 409.011 223.924 410.942 228.128C412.874 232.332 416.145 235.925 420.341 238.454C424.538 240.982 429.471 242.331 434.518 242.331C453.233 242.331 468.403 227.161 468.403 208.446L468.404 207.191V100Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export function JazzLogo({ className }: { className?: string }) {
|
||||
export function JazzLogo({
|
||||
className,
|
||||
width = undefined,
|
||||
height = undefined,
|
||||
}: { className?: string; width?: number; height?: number }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 386 146"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={clsx(className, "text-black dark:text-white")}
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
<path
|
||||
d="M176.725 33.865H188.275V22.7H176.725V33.865ZM164.9 129.4H172.875C182.72 129.4 188.275 123.9 188.275 114.22V43.6H176.725V109.545C176.725 115.65 173.975 118.51 167.925 118.51H164.9V129.4ZM245.298 53.28C241.613 45.47 233.363 41.95 222.748 41.95C208.998 41.95 200.748 48.44 197.888 58.615L208.613 61.915C210.648 55.315 216.368 52.565 222.638 52.565C231.933 52.565 235.673 56.415 236.058 64.61C226.433 65.93 216.643 67.195 209.768 69.23C200.583 72.145 195.743 77.865 195.743 86.83C195.743 96.51 202.673 104.65 215.818 104.65C225.443 104.65 232.318 101.35 237.213 94.365V103H247.388V66.425C247.388 61.475 247.168 57.185 245.298 53.28ZM217.853 95.245C210.483 95.245 207.128 91.34 207.128 86.72C207.128 82.045 210.593 79.515 215.323 77.92C220.328 76.435 226.983 75.5 235.948 74.18C235.893 76.93 235.673 80.725 234.738 83.475C233.418 89.25 227.643 95.245 217.853 95.245ZM251.22 103H301.545V92.715H269.535L303.195 45.47V43.6H254.3V53.885H284.935L251.22 101.185V103ZM304.815 103H355.14V92.715H323.13L356.79 45.47V43.6H307.895V53.885H338.53L304.815 101.185V103Z"
|
||||
|
||||
@@ -57,7 +57,9 @@ export function Footer({
|
||||
key={index}
|
||||
className="flex flex-col gap-2 text-sm col-span-6 md:col-span-2"
|
||||
>
|
||||
<h2 className="font-medium">{section.title}</h2>
|
||||
<h2 className="font-medium dark:text-stone-700 cursor-default">
|
||||
{section.title}
|
||||
</h2>
|
||||
{section.links.map((link, linkIndex) => (
|
||||
<FooterLink
|
||||
key={linkIndex}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { ImageResponse } from "next/og";
|
||||
import { JazzLogo } from "../atoms/logos/JazzLogo";
|
||||
|
||||
export const imageSize = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export const imageContentType = "image/png";
|
||||
|
||||
export default async function OpenGraphImage({ title }: { title: string }) {
|
||||
const manropeSemiBold = await readFile(
|
||||
join(process.cwd(), "public/fonts/Manrope-SemiBold.ttf"),
|
||||
);
|
||||
|
||||
return new ImageResponse(
|
||||
<div
|
||||
style={{
|
||||
fontSize: "7em",
|
||||
background: "white",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
padding: "77px",
|
||||
letterSpacing: "-0.05em",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
<div
|
||||
style={{ display: "flex", position: "absolute", bottom: 35, right: 45 }}
|
||||
>
|
||||
<JazzLogo width={193} height={73} />
|
||||
</div>
|
||||
</div>,
|
||||
{
|
||||
...imageSize,
|
||||
fonts: [
|
||||
{
|
||||
name: "Manrope",
|
||||
data: manropeSemiBold,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
export const metadata = { title: "Authentication States" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Authentication States
|
||||
|
||||
Jazz provides three distinct authentication states that determine how users interact with your app: **Anonymous Authentication**, **Guest Mode**, and **Authenticated Account**.
|
||||
|
||||
## Anonymous Authentication
|
||||
|
||||
When a user loads a Jazz application for the first time, we create a new Account by generating keys and storing them locally:
|
||||
|
||||
- Users have full accounts with unique IDs
|
||||
- Data persists between sessions on the same device
|
||||
- Can be upgraded to a full account (passkey, passphrase, etc.)
|
||||
- Data syncs across the network (if enabled)
|
||||
|
||||
## Authenticated Account
|
||||
|
||||
**Authenticated Account** provides full multi-device functionality:
|
||||
|
||||
- Persistent identity across multiple devices
|
||||
- Full access to all application features
|
||||
- Data can sync across all user devices
|
||||
- Multiple authentication methods available
|
||||
|
||||
## Guest Mode
|
||||
|
||||
**Guest Mode** provides a completely accountless context:
|
||||
|
||||
- No persistent identity or account
|
||||
- Only provides access to publicly readable content
|
||||
- Cannot save or sync user-specific data
|
||||
- Suitable for read-only access to public resources
|
||||
|
||||
## Detecting Authentication State
|
||||
|
||||
You can detect the current authentication state using `useAccountOrGuest` and `useIsAuthenticated`.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
// ---cut---
|
||||
import { useAccountOrGuest, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
function AuthStateIndicator() {
|
||||
const { me } = useAccountOrGuest();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
// Check if guest mode is enabled in JazzProvider
|
||||
const isGuest = me._type !== "Account"
|
||||
|
||||
// Anonymous authentication: has an account but not fully authenticated
|
||||
const isAnonymous = me._type === "Account" && !isAuthenticated;
|
||||
return (
|
||||
<div>
|
||||
{isGuest && <span>Guest Mode</span>}
|
||||
{isAnonymous && <span>Anonymous Account</span>}
|
||||
{isAuthenticated && <span>Authenticated</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Migrating data from anonymous to authenticated account
|
||||
|
||||
When a user signs up, their anonymous account is transparently upgraded to an authenticated account, preserving all their data.
|
||||
|
||||
However, if a user has been using your app anonymously and later logs in with an existing account, their anonymous account data would normally be discarded. To prevent data loss, you can use the `onAnonymousAccountDiscarded` handler.
|
||||
|
||||
This example from our [music player example app](https://github.com/garden-co/jazz/tree/main/examples/music-player) shows how to migrate data:
|
||||
|
||||
<CodeGroup>
|
||||
```ts twoslash
|
||||
import { Account, Group, CoMap, co, CoList } from "jazz-tools";
|
||||
|
||||
class MusicTrack extends CoMap {
|
||||
title = co.string;
|
||||
duration = co.number;
|
||||
isExampleTrack = co.optional.boolean;
|
||||
}
|
||||
class ListOfTracks extends CoList.Of(co.ref(MusicTrack)) {}
|
||||
class Playlist extends CoMap {
|
||||
title = co.string;
|
||||
tracks = co.ref(ListOfTracks);
|
||||
}
|
||||
class MusicaAccountRoot extends CoMap {
|
||||
rootPlaylist = co.ref(Playlist);
|
||||
}
|
||||
class MusicaAccount extends Account {
|
||||
root = co.ref(MusicaAccountRoot);
|
||||
}
|
||||
|
||||
// ---cut---
|
||||
export async function onAnonymousAccountDiscarded(
|
||||
anonymousAccount: MusicaAccount,
|
||||
) {
|
||||
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
|
||||
resolve: {
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: {
|
||||
$each: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
resolve: {
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const track of anonymousAccountRoot.rootPlaylist.tracks) {
|
||||
if (track.isExampleTrack) continue;
|
||||
|
||||
const trackGroup = track._owner.castAs(Group);
|
||||
trackGroup.addMember(me, "admin");
|
||||
|
||||
me.root.rootPlaylist.tracks.push(track);
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To see how this works, try uploading a song in the [music player demo](https://music-demo.jazz.tools/) and then log in with an existing account.
|
||||
|
||||
## Controlling sync for different authentication states
|
||||
|
||||
You can control network sync with [Providers](/docs/project-setup/providers) based on authentication state:
|
||||
|
||||
- `when: "always"`: Sync is enabled for both Anonymous Authentication and Authenticated Account
|
||||
- `when: "signedUp"`: Sync is enabled when the user is authenticated
|
||||
- `when: "never"`: Sync is disabled, content stays local
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
const apiKey = "you@example.com";
|
||||
function App() {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
// ---cut---
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// Controls when sync is enabled for
|
||||
// both Anonymous Authentication and Authenticated Account
|
||||
when: "always", // or "signedUp" or "never"
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
### Disable sync for Anonymous Authentication
|
||||
|
||||
You can disable network sync to make your app local-only under specific circumstances.
|
||||
|
||||
For example, you may want to give users with Anonymous Authentication the opportunity to try your app locally-only (incurring no sync traffic), then enable network sync only when the user is fully authenticated.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
const apiKey = "you@example.com";
|
||||
function App() {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
// ---cut---
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// This makes the app work in local mode when using Anonymous Authentication
|
||||
when: "signedUp",
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
### Configuring Guest Mode Access
|
||||
|
||||
You can configure Guest Mode access with the `guestMode` prop for [Providers](/docs/project-setup/providers).
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
const apiKey = "you@example.com";
|
||||
function App() {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
// ---cut---
|
||||
<JazzProvider
|
||||
// Enable Guest Mode for public content
|
||||
guestMode={true}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// Only sync for authenticated users
|
||||
when: "signedUp",
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
For more complex behaviours, you can manually control sync by statefully switching when between `"always"` and `"never"`.
|
||||
@@ -0,0 +1,227 @@
|
||||
export const metadata = { title: "Clerk Authentication" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Clerk Authentication
|
||||
|
||||
Jazz can be integrated with [Clerk](https://clerk.com/) to authenticate users. This method combines Clerk's comprehensive authentication services with Jazz's local-first capabilities.
|
||||
|
||||
<ContentByFramework framework="react-native">
|
||||
We do not currently support Clerk in React Native, but we do have support for [React Native Expo](/docs/react-native-expo/authentication/clerk).
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework={["react", "react-native-expo", "svelte", "vue", "vanilla"]}>
|
||||
## How it works
|
||||
|
||||
When using Clerk authentication:
|
||||
1. Users sign up or sign in through Clerk's authentication system
|
||||
2. Jazz securely stores the user's account keys with Clerk
|
||||
3. When logging in, Jazz retrieves these keys from Clerk
|
||||
4. Once authenticated, users can work offline with full Jazz functionality
|
||||
|
||||
This authentication method is not fully local-first, as login and signup need to be done online, but once authenticated, users can use all of Jazz's features without needing to be online.
|
||||
|
||||
## Key benefits
|
||||
|
||||
- **Rich auth options**: Email/password, social logins, multi-factor authentication
|
||||
- **User management**: Complete user administration dashboard
|
||||
- **Familiar sign-in**: Standard auth flows users already know
|
||||
- **OAuth providers**: Google, GitHub, and other popular providers
|
||||
- **Enterprise features**: SSO, SAML, and other advanced options
|
||||
|
||||
## Implementation
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
We offer Clerk integration through our package: [`jazz-react-auth-clerk`](https://npmjs.com/package/jazz-react-auth-clerk).
|
||||
|
||||
After installing, use `<JazzProviderWithClerk />` to wrap your app:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
const apiKey = "you@example.com";
|
||||
const PUBLISHABLE_KEY = "fake_key";
|
||||
function App() {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
// ---cut---
|
||||
import { useClerk, ClerkProvider } from '@clerk/clerk-react';
|
||||
import { JazzProviderWithClerk } from "jazz-react-auth-clerk";
|
||||
|
||||
function JazzProvider({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
);
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
We offer Clerk integration through our package: [`jazz-expo`](https://npmjs.com/package/jazz-expo).
|
||||
|
||||
After installing, use `<JazzProviderWithClerk />` to wrap your app.
|
||||
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { Slot } from "expo-router";
|
||||
const apiKey = "you@example.com";
|
||||
const tokenCache = {
|
||||
getToken: async (key: string) => {
|
||||
return null;
|
||||
},
|
||||
saveToken: async (key: string, token: string) => {},
|
||||
clearToken: async (key: string) => {},
|
||||
};
|
||||
// ---cut---
|
||||
import { useClerk, ClerkProvider, ClerkLoaded } from '@clerk/clerk-expo';
|
||||
import { secureStore } from "@clerk/clerk-expo/secure-store";
|
||||
import { JazzProviderWithClerk } from "jazz-expo/auth/clerk";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
if (!publishableKey) {
|
||||
throw new Error(
|
||||
"Missing Publishable Key. Please set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env",
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider
|
||||
tokenCache={tokenCache}
|
||||
publishableKey={publishableKey}
|
||||
__experimental_resourceCache={secureStore}
|
||||
>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
</JazzAndAuth>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
Once set up, you can use Clerk's auth methods for login and signup:
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
// ---cut---
|
||||
import { SignInButton } from "@clerk/clerk-react";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <button onClick={() => logOut()}>Logout</button>;
|
||||
}
|
||||
|
||||
return <SignInButton />;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
// ---cut---
|
||||
import { useSignIn } from "@clerk/clerk-expo";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-expo";
|
||||
import { Button, Text } from 'react-native';
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
const { signIn, setActive, isLoaded } = useSignIn();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Button title="Logout" onPress={() => logOut()} />;
|
||||
}
|
||||
|
||||
const onSignInPress = async () => {
|
||||
if (!isLoaded) return;
|
||||
const signInAttempt = await signIn.create({
|
||||
identifier: "you@example.com",
|
||||
password: "password",
|
||||
});
|
||||
if (signInAttempt.status === "complete") {
|
||||
await setActive({ session: signInAttempt.createdSessionId });
|
||||
}
|
||||
};
|
||||
|
||||
return <Button title="Sign In" onPress={onSignInPress} />;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Examples
|
||||
|
||||
You can explore Jazz with Clerk integration in our [example projects](/docs/examples). For more Clerk-specific demos, visit [Clerk's documentation](https://clerk.com/docs).
|
||||
|
||||
## When to use Clerk
|
||||
|
||||
Clerk authentication is ideal when:
|
||||
|
||||
- You need an existing user management system
|
||||
- You want to integrate with other Clerk features (roles, permissions)
|
||||
- You require email/password authentication with verification
|
||||
- You need OAuth providers (Google, GitHub, etc.)
|
||||
- You want to avoid users having to manage passphrases
|
||||
|
||||
## Limitations and considerations
|
||||
|
||||
- **Online requirement**: Initial signup/login requires internet connectivity
|
||||
- **Third-party dependency**: Relies on Clerk's services for authentication
|
||||
- **Not fully local-first**: Initial authentication requires a server
|
||||
- **Platform support**: Not available on all platforms
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [Clerk Documentation](https://clerk.com/docs)
|
||||
- [Jazz React Auth Clerk Package](https://npmjs.com/package/jazz-react-auth-clerk)
|
||||
</ContentByFramework>
|
||||
@@ -6,408 +6,32 @@ import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
Jazz authentication is based on cryptographic keys ("Account keys"). Their public part represents a user's identity, their secret part lets you act as that user.
|
||||
|
||||
When a user loads a Jazz application for the first time, we create a new Account by generating keys and storing them locally.
|
||||
## Authentication Flow
|
||||
|
||||
Without any additional steps the user can use Jazz normally, but they would be limited to use on only one device.
|
||||
When a user first opens your app, they'll be in one of these states:
|
||||
|
||||
To make Accounts work across devices, you can store/retrieve the account keys from an authentication method by using the corresponding hooks and providers.
|
||||
- **Anonymous Authentication**: Default starting point where Jazz automatically creates a local account on first visit. Data persists on one device and can be upgraded to a full account.
|
||||
|
||||
<ContentByFramework framework={["react", "vue", "svelte"]}>
|
||||
## Authentication with passkeys [!framework=react,vue,svelte]
|
||||
- **Authenticated Account**: Full account accessible across multiple devices using [passkeys](./passkey), [passphrases](./passphrase), or third-party authentications, such as [Clerk](./clerk).
|
||||
|
||||
Passkey authentication is fully local-first and the most secure of the auth methods that Jazz provides (because keys are managed by the device/operating system itself).
|
||||
- **Guest Mode**: No account, read-only access to public content. Users can browse but can't save data or sync.
|
||||
|
||||
It is based on the [Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) and is both very easy to use (using familiar FaceID/TouchID flows) and widely supported.
|
||||
Learn more about these states in the [Authentication States](./authentication-states) documentation.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Using passkeys in Jazz is as easy as this:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [username, setUsername] = useState("");
|
||||
Without authentication, users are limited to using the application on only one device.
|
||||
|
||||
const auth = usePasskeyAuth({ // Must be inside the JazzProvider!
|
||||
appName: "My super-cool web app",
|
||||
});
|
||||
When a user logs out of an Authenticated Account, they return to the Anonymous Authentication state with a new local account.
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
Here's what happens during registration and login:
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp(username);
|
||||
onOpenChange(false);
|
||||
};
|
||||
- **Register**: When a user registers with an authentication provider, their Anonymous account credentials are stored in the auth provider, and the account is marked as Authenticated. The user keeps all their existing data.
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn();
|
||||
onOpenChange(false);
|
||||
};
|
||||
- **Login**: When a user logs in with an authentication provider, their Anonymous account is discarded and the credentials are loaded from the auth provider. Data from the Anonymous account can be transferred using the [onAnonymousAccountDiscarded handler](./authentication-states#migrating-data-from-anonymous-to-authenticated-account).
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
<button onClick={handleSignUp}>Sign up</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
## Available Authentication Methods
|
||||
|
||||
Jazz provides several ways to authenticate users:
|
||||
|
||||
You can try our passkey authentication using our [passkey example](https://passkey-demo.jazz.tools/) or the [music player demo](https://music-demo.jazz.tools/).
|
||||
|
||||
</ContentByFramework>
|
||||
|
||||
## Passphrase-based authentication
|
||||
|
||||
Passphrase authentication lets users log into any device using a Bitcoin-style passphrase. This means users are themselves responsible for storing the passphrase safely.
|
||||
|
||||
The passphrase is generated from the local account certificate using a wordlist of your choice.
|
||||
|
||||
You can find a set of ready-to-use wordlists in the [bip39](https://github.com/bitcoinjs/bip39/tree/a7ecbfe2e60d0214ce17163d610cad9f7b23140c/src/wordlists) repository.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
For example:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { englishWordlist } from "./wordlist"
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({ // Must be inside the JazzProvider!
|
||||
wordlist: englishWordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>
|
||||
Your current passphrase
|
||||
<textarea
|
||||
readOnly
|
||||
value={auth.passphrase}
|
||||
rows={5}
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleSignUp}>I have stored my passphrase</button>
|
||||
<label>
|
||||
Log in with your passphrase
|
||||
<textarea
|
||||
value={loginPassphrase}
|
||||
onChange={(e) => setLoginPassphrase(e.target.value)}
|
||||
placeholder="Enter your passphrase"
|
||||
rows={5}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework={["react-native", "react-native-expo"]}>
|
||||
For example:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { View, TextInput, Button, Text } from 'react-native';
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({
|
||||
wordlist: englishWordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") {
|
||||
return <Text>You are already signed in</Text>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Text>Your current passphrase</Text>
|
||||
<TextInput
|
||||
editable={false}
|
||||
value={auth.passphrase}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="I have stored my passphrase"
|
||||
onPress={handleSignUp}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<Text>Log in with your passphrase</Text>
|
||||
<TextInput
|
||||
value={loginPassphrase}
|
||||
onChangeText={setLoginPassphrase}
|
||||
placeholder="Enter your passphrase"
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
required
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="Log in"
|
||||
onPress={handleLogIn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
You can try our passphrase authentication using our [passphrase example](https://passphrase-demo.jazz.tools/) or the [todo list demo](https://todo-demo.jazz.tools/).
|
||||
|
||||
<ContentByFramework framework={["react", "react-native-expo"]}>
|
||||
## Integration with Clerk
|
||||
|
||||
Jazz can be used with [Clerk](https://clerk.com/) to authenticate users.
|
||||
|
||||
This authentication method is not fully local-first, because the login and signup need to be done while online. Clerk and anyone who is an admin in the app's Clerk org are trusted with the user's key secret and could impersonate them.
|
||||
|
||||
However, once authenticated, your users won't need to interact with Clerk anymore, and are able to use all of Jazz's features without needing to be online.
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
The clerk provider is not built into `jazz-react` and needs the `jazz-react-auth-clerk` package to be installed.
|
||||
|
||||
After installing the package you can use the `JazzProviderWithClerk` component to wrap your app:
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
You can use the `JazzProviderWithClerk` component to wrap your app. Note the `__experimental_resourceCache` option. This helps render Clerk components when offline.
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
import { JazzProviderWithClerk } from "jazz-react-auth-clerk";
|
||||
|
||||
function JazzProvider({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
);
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
import { JazzProviderWithClerk } from "jazz-expo/auth/clerk";
|
||||
import { secureStore } from "@clerk/clerk-expo/secure-store";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
if (!publishableKey) {
|
||||
throw new Error(
|
||||
"Missing Publishable Key. Please set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env",
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider
|
||||
tokenCache={tokenCache}
|
||||
publishableKey={publishableKey}
|
||||
__experimental_resourceCache={secureStore}
|
||||
>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
</JazzAndAuth>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Then you can use the [Clerk auth methods](https://clerk.com/docs/references/react/overview) to log in and sign up:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { SignInButton } from "@clerk/clerk-react";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <button onClick={() => logOut()}>Logout</button>;
|
||||
}
|
||||
|
||||
return <SignInButton />;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
Then you can use the [Clerk auth methods](https://clerk.com/docs/references/expo/overview) to log in and sign up:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { SignInButton } from "@clerk/clerk-expo";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-expo";
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
const { signIn, setActive, isLoaded } = useSignIn();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <button onClick={() => logOut()}>Logout</button>;
|
||||
}
|
||||
|
||||
// Login code with Clerk Expo
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Migrating data from anonymous to authenticated account
|
||||
|
||||
You may want allow your users to use your app without authenticating (a poll response for example). When *signing up* their anonymous account is transparently upgraded using the provided auth method, keeping the data stored in the account intact.
|
||||
|
||||
However, a user may realise that they already have an existing account *after using the app anonymously and having already stored data in the anonymous account*.
|
||||
|
||||
When they now *log in*, by default the anonymous account will be discarded and this could lead to unexpected data loss.
|
||||
|
||||
To avoid this situation we provide the `onAnonymousAccountDiscarded` handler to migrate the data from the anonymous account to the existing authenticated one.
|
||||
|
||||
This is an example from our [music player](https://github.com/garden-co/jazz/tree/main/examples/music-player):
|
||||
<CodeGroup>
|
||||
```ts
|
||||
export async function onAnonymousAccountDiscarded(
|
||||
anonymousAccount: MusicaAccount,
|
||||
) {
|
||||
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [{}],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const track of anonymousAccountRoot.rootPlaylist.tracks) {
|
||||
if (track.isExampleTrack) continue;
|
||||
|
||||
const trackGroup = track._owner.castAs(Group);
|
||||
trackGroup.addMember(me, "admin");
|
||||
|
||||
me.root.rootPlaylist.tracks.push(track);
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To see how this works in reality we suggest you to try
|
||||
to upload a song in the [music player demo](https://music-demo.jazz.tools/) and then
|
||||
try to log in with an existing account.
|
||||
|
||||
## Disable network sync for anonymous users
|
||||
|
||||
You can disable network sync to make your app local-only under specific circumstances.
|
||||
|
||||
For example, you may want to give the opportunity to non-authenticated users to try your app locally-only (incurring no sync traffic), then enable the network sync only when the user is authenticated:
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// This makes the app work in local mode when the user is anonymous
|
||||
when: "signedUp",
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
For more complex behaviours, you can manually control sync by statefully switching when between `"always"` and `"never"`.
|
||||
- [**Passkeys**](./passkey): Secure, biometric authentication using WebAuthn
|
||||
- [**Passphrases**](./passphrase): Bitcoin-style word phrases that users store
|
||||
- [**Clerk Integration**](./clerk): Third-party authentication service with OAuth support
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
export const metadata = { title: "Passkey Authentication" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Passkey Authentication
|
||||
|
||||
Passkey authentication is fully local-first and the most secure of the auth methods that Jazz provides because keys are managed by the device/operating system itself.
|
||||
|
||||
## How it works
|
||||
|
||||
Passkey authentication is based on the [Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) and uses familiar FaceID/TouchID flows that users already know how to use.
|
||||
|
||||
## Key benefits
|
||||
|
||||
- **Most secure**: Keys are managed by the device/OS
|
||||
- **User-friendly**: Uses familiar biometric verification (FaceID/TouchID)
|
||||
- **Cross-device**: Works across devices with the same biometric authentication
|
||||
- **No password management**: Users don't need to remember or store anything
|
||||
- **Wide support**: Available in most modern browsers
|
||||
|
||||
## Implementation
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Using passkeys in Jazz is as easy as this:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import { usePasskeyAuth } from "jazz-react";
|
||||
type AuthModalProps = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
// ---cut---
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [username, setUsername] = useState("");
|
||||
|
||||
const auth = usePasskeyAuth({ // Must be inside the JazzProvider!
|
||||
appName: "My super-cool web app",
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp(username);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
<button onClick={handleSignUp}>Sign up</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Examples
|
||||
|
||||
You can try passkey authentication using our [passkey example](https://passkey-demo.jazz.tools/) or the [music player demo](https://music-demo.jazz.tools/).
|
||||
|
||||
## When to use Passkeys
|
||||
|
||||
Passkeys are ideal when:
|
||||
- Security is a top priority
|
||||
- You want the most user-friendly authentication experience
|
||||
- You're targeting modern browsers and devices
|
||||
- You want to eliminate the risk of password-based attacks
|
||||
|
||||
## Limitations and considerations
|
||||
|
||||
- Requires hardware/OS support for biometric authentication
|
||||
- Not supported in older browsers (see browser support below)
|
||||
- Requires a fallback method for unsupported environments
|
||||
|
||||
### Browser Support
|
||||
|
||||
[Passkeys are supported in most modern browsers](https://caniuse.com/passkeys).
|
||||
|
||||
For older browsers, we recommend using [passphrase authentication](./passphrase) as a fallback.
|
||||
|
||||
## Additional resources
|
||||
|
||||
For more information about the Web Authentication API and passkeys:
|
||||
- [WebAuthn.io](https://webauthn.io/)
|
||||
- [MDN Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)
|
||||
@@ -0,0 +1,194 @@
|
||||
export const metadata = { title: "Passphrase Authentication" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Passphrase Authentication
|
||||
|
||||
Passphrase authentication lets users log into any device using a recovery phrase consisting of multiple words (similar to cryptocurrency wallets). Users are responsible for storing this passphrase safely.
|
||||
|
||||
## How it works
|
||||
|
||||
When a user creates an account with passphrase authentication:
|
||||
|
||||
1. Jazz generates a unique recovery phrase derived from the user's cryptographic keys
|
||||
2. This phrase consists of words from a wordlist
|
||||
3. Users save this phrase and enter it when logging in on new devices
|
||||
|
||||
You can use one of the ready-to-use wordlists from the [BIP39 repository](https://github.com/bitcoinjs/bip39/tree/a7ecbfe2e60d0214ce17163d610cad9f7b23140c/src/wordlists) or create your own.
|
||||
|
||||
## Key benefits
|
||||
|
||||
- **Portable**: Works across any device, even without browser or OS support
|
||||
- **User-controlled**: User manages their authentication phrase
|
||||
- **Flexible**: Works with any wordlist you choose
|
||||
- **Offline capable**: No external dependencies
|
||||
|
||||
## Implementation
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
// @filename: wordlist.ts
|
||||
export const wordlist = ["apple", "banana", "cherry", "date", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon", "mango", "orange", "pear", "quince", "raspberry", "strawberry", "tangerine", "uva", "watermelon", "xigua", "yuzu", "zucchini"];
|
||||
// @filename: AuthModal.tsx
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import { usePassphraseAuth } from "jazz-react";
|
||||
type AuthModalProps = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
// ---cut---
|
||||
import { wordlist } from "./wordlist"
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({ // Must be inside the JazzProvider!
|
||||
wordlist: wordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>
|
||||
Your current passphrase
|
||||
<textarea
|
||||
readOnly
|
||||
value={auth.passphrase}
|
||||
rows={5}
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleSignUp}>I have stored my passphrase</button>
|
||||
<label>
|
||||
Log in with your passphrase
|
||||
<textarea
|
||||
value={loginPassphrase}
|
||||
onChange={(e) => setLoginPassphrase(e.target.value)}
|
||||
placeholder="Enter your passphrase"
|
||||
rows={5}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework={["react-native", "react-native-expo"]}>
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
// @filename: wordlist.ts
|
||||
export const wordlist = ["apple", "banana", "cherry", "date", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon", "mango", "orange", "pear", "quince", "raspberry", "strawberry", "tangerine", "uva", "watermelon", "xigua", "yuzu", "zucchini"];
|
||||
// @filename: AuthModal.tsx
|
||||
import * as React from "react";
|
||||
import { View, TextInput, Button, Text } from 'react-native';
|
||||
import { useState } from "react";
|
||||
import { usePassphraseAuth } from "jazz-react-native";
|
||||
|
||||
type AuthModalProps = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
// ---cut---
|
||||
import { wordlist } from "./wordlist"
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({
|
||||
wordlist: wordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") {
|
||||
return <Text>You are already signed in</Text>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Text>Your current passphrase</Text>
|
||||
<TextInput
|
||||
editable={false}
|
||||
value={auth.passphrase}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="I have stored my passphrase"
|
||||
onPress={handleSignUp}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<Text>Log in with your passphrase</Text>
|
||||
<TextInput
|
||||
value={loginPassphrase}
|
||||
onChangeText={setLoginPassphrase}
|
||||
placeholder="Enter your passphrase"
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="Log in"
|
||||
onPress={handleLogIn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Examples
|
||||
|
||||
You can see passphrase authentication in our [passphrase example](https://passphrase-demo.jazz.tools/) or the [todo list demo](https://todo-demo.jazz.tools/).
|
||||
|
||||
## When to use Passphrases
|
||||
|
||||
Passphrase authentication is ideal when:
|
||||
|
||||
- You need to support older browsers without WebAuthn capabilities
|
||||
- Your users need to access the app on many different devices
|
||||
- You want a fallback authentication method alongside passkeys
|
||||
|
||||
## Limitations and considerations
|
||||
|
||||
- **User responsibility**: Users must securely store their passphrase
|
||||
- **Recovery concerns**: If a user loses their passphrase, they cannot recover their account
|
||||
- **Security risk**: Anyone with the passphrase can access the account
|
||||
- **User experience**: Requires users to enter a potentially long phrase
|
||||
|
||||
Make sure to emphasize to your users:
|
||||
1. Store the passphrase in a secure location (password manager, written down in a safe place)
|
||||
2. The passphrase is the only way to recover their account
|
||||
3. Anyone with the passphrase can access the account
|
||||
@@ -214,8 +214,8 @@ export class MyAppAccount extends Account {
|
||||
}
|
||||
|
||||
// We need to load the root field to check for the myContacts field
|
||||
const { root } = await this.ensureLoaded({ // [!code ++:3]
|
||||
root: {},
|
||||
const { root } = await this.ensureLoaded({
|
||||
resolve: { root: true }
|
||||
});
|
||||
|
||||
// we specifically need to check for undefined,
|
||||
|
||||
@@ -39,10 +39,10 @@ Update your imports to use the new `jazz-expo` package.
|
||||
```tsx twoslash
|
||||
// @noErrors: 2300 2307
|
||||
// Before
|
||||
import { JazzProvider, useAccount, useCoValue } from "jazz-react-native"; // [!code --]
|
||||
import { JazzProvider, useAccount, useCoState } from "jazz-react-native"; // [!code --]
|
||||
|
||||
// After
|
||||
import { JazzProvider, useAccount, useCoValue } from "jazz-expo"; // [!code ++]
|
||||
import { JazzProvider, useAccount, useCoState } from "jazz-expo"; // [!code ++]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@ import LatencyChart from "@/components/LatencyChart";
|
||||
import { clsx } from "clsx";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import type { Metadata } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Fragment } from "react";
|
||||
|
||||
const title = "Status";
|
||||
|
||||
export const revalidate = 300;
|
||||
export const dynamic = "force-static";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
@@ -41,6 +40,10 @@ interface DataRow {
|
||||
|
||||
const query = async () => {
|
||||
const res = await fetch("https://gcmp.grafana.net/api/ds/query", {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
revalidate: 300,
|
||||
},
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ThemeProvider } from "@/components/ThemeProvider";
|
||||
import { JazzFooter } from "@/components/footer";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import { marketingCopy } from "@/lib/marketingCopy";
|
||||
|
||||
// If loading a variable font, you don't need to specify the font weight
|
||||
const manrope = Manrope({
|
||||
@@ -40,9 +41,8 @@ const commitMono = localFont({
|
||||
});
|
||||
|
||||
const metaTags = {
|
||||
title: "Jazz - Whip up an app",
|
||||
description:
|
||||
"Jazz gives you data without needing a database — plus auth, permissions, files and multiplayer without needing a backend. Do everything right from the frontend and ship better apps, faster.",
|
||||
title: `Jazz - ${marketingCopy.headline}`,
|
||||
description: marketingCopy.description,
|
||||
url: "https://jazz.tools",
|
||||
};
|
||||
|
||||
@@ -63,13 +63,6 @@ export const metadata: Metadata = {
|
||||
description: metaTags.description,
|
||||
url: metaTags.url,
|
||||
siteName: "Jazz",
|
||||
images: [
|
||||
{
|
||||
url: "/social-image.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
13
homepage/homepage/app/not-found.tsx
Normal file
13
homepage/homepage/app/not-found.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Icon404 } from 'gcmp-design-system/src/app/components/atoms/icons/404'
|
||||
|
||||
export default function NotFound() {
|
||||
const text = "Don't Worry 'Bout Me";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-flex-start justify-center min-h-[95vh] w-auto pb-32">
|
||||
<Icon404 className="w-75" />
|
||||
<h1 className="text-3xl font-semibold my-7">{text}</h1>
|
||||
<p>Page not found.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
11
homepage/homepage/app/opengraph-image.tsx
Normal file
11
homepage/homepage/app/opengraph-image.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { marketingCopy } from '@/lib/marketingCopy';
|
||||
import OpenGraphImage, { imageSize, imageContentType } from 'gcmp-design-system/src/app/components/organisms/OpenGraphImage';
|
||||
|
||||
export const title = marketingCopy.headline;
|
||||
export const size = imageSize;
|
||||
export const contentType = imageContentType;
|
||||
export const alt = marketingCopy.headline;
|
||||
|
||||
export default async function Image() {
|
||||
return OpenGraphImage({ title })
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import CreateJazzApp from "@/components/home/CreateJazzApp.mdx";
|
||||
import { marketingCopy } from "@/lib/marketingCopy";
|
||||
import { H1 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import {
|
||||
Icon,
|
||||
@@ -54,7 +55,7 @@ export function HeroSection() {
|
||||
<div className="flex flex-col justify-center gap-5 lg:col-span-2 lg:gap-8">
|
||||
<Kicker>Toolkit for backendless apps</Kicker>
|
||||
<H1>
|
||||
<span className="inline-block">Whip up an app.</span>
|
||||
<span className="inline-block">{marketingCopy.headline}</span>
|
||||
</H1>
|
||||
|
||||
<Prose size="lg" className="text-pretty max-w-2xl dark:text-stone-200">
|
||||
|
||||
51
homepage/homepage/docs/authentication-method-template.md
Normal file
51
homepage/homepage/docs/authentication-method-template.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Authentication Method Template
|
||||
|
||||
Use this template structure for all authentication method documentation pages to ensure consistency.
|
||||
|
||||
````markdown
|
||||
export const metadata = { title: "[Method] Authentication" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# [Method] Authentication
|
||||
|
||||
Brief introduction explaining what this authentication method is.
|
||||
|
||||
## How it works
|
||||
|
||||
Clear, simple explanation of the mechanism behind this auth method.
|
||||
|
||||
## Key benefits
|
||||
|
||||
- Benefit one
|
||||
- Benefit two
|
||||
- Benefit three
|
||||
- Benefit four
|
||||
|
||||
## Implementation
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
// Code example here
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Examples
|
||||
|
||||
Links to live demos using this authentication method.
|
||||
|
||||
## When to use [Method]
|
||||
|
||||
Guidelines about when this method is most appropriate.
|
||||
|
||||
## Limitations and considerations
|
||||
|
||||
Any drawbacks or special considerations to be aware of.
|
||||
|
||||
## Additional resources (optional)
|
||||
|
||||
External links and further reading where applicable.
|
||||
````
|
||||
@@ -226,12 +226,27 @@ export const docNavigationItems = [
|
||||
{
|
||||
name: "Overview",
|
||||
href: "/docs/authentication/overview",
|
||||
done: {
|
||||
react: 100,
|
||||
vue: 50,
|
||||
"react-native": 100,
|
||||
svelte: 50,
|
||||
},
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Authentication States",
|
||||
href: "/docs/authentication/authentication-states",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Passkey",
|
||||
href: "/docs/authentication/passkey",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Passphrase",
|
||||
href: "/docs/authentication/passphrase",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Clerk",
|
||||
href: "/docs/authentication/clerk",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Writing your own",
|
||||
|
||||
4
homepage/homepage/lib/marketingCopy.ts
Normal file
4
homepage/homepage/lib/marketingCopy.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const marketingCopy = {
|
||||
headline: "Whip up an app",
|
||||
description: "Jazz gives you data without needing a database — plus auth, permissions, files and multiplayer without needing a backend. Do everything right from the frontend and ship better apps, faster.",
|
||||
}
|
||||
@@ -39,8 +39,11 @@
|
||||
"hast-util-to-string": "^3.0.1",
|
||||
"jazz-browser": "link:../../packages/jazz-browser",
|
||||
"jazz-browser-media-images": "link:../../packages/jazz-browser-media-images",
|
||||
"jazz-expo": "link:../../packages/jazz-expo",
|
||||
"jazz-nodejs": "link:../../packages/jazz-nodejs",
|
||||
"jazz-react": "link:../../packages/jazz-react",
|
||||
"jazz-react-auth-clerk": "link:../../packages/jazz-react-auth-clerk",
|
||||
"jazz-react-native": "link:../../packages/jazz-react-native",
|
||||
"jazz-tools": "link:../../packages/jazz-tools",
|
||||
"lucide-react": "^0.436.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
|
||||
BIN
homepage/homepage/public/fonts/Manrope-SemiBold.ttf
Normal file
BIN
homepage/homepage/public/fonts/Manrope-SemiBold.ttf
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 172 KiB |
@@ -3,7 +3,8 @@
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build", "build:generate-docs"],
|
||||
"outputs": [".next/**", "!.next/cache/**"]
|
||||
"outputs": [".next/**", "!.next/cache/**"],
|
||||
"env": ["GRAFANA_SERVICE_ACCOUNT"]
|
||||
},
|
||||
"build:generate-docs": {
|
||||
"inputs": ["../../../packages/*/src/**"],
|
||||
|
||||
9
homepage/pnpm-lock.yaml
generated
9
homepage/pnpm-lock.yaml
generated
@@ -244,12 +244,21 @@ importers:
|
||||
jazz-browser-media-images:
|
||||
specifier: link:../../packages/jazz-browser-media-images
|
||||
version: link:../../packages/jazz-browser-media-images
|
||||
jazz-expo:
|
||||
specifier: link:../../packages/jazz-expo
|
||||
version: link:../../packages/jazz-expo
|
||||
jazz-nodejs:
|
||||
specifier: link:../../packages/jazz-nodejs
|
||||
version: link:../../packages/jazz-nodejs
|
||||
jazz-react:
|
||||
specifier: link:../../packages/jazz-react
|
||||
version: link:../../packages/jazz-react
|
||||
jazz-react-auth-clerk:
|
||||
specifier: link:../../packages/jazz-react-auth-clerk
|
||||
version: link:../../packages/jazz-react-auth-clerk
|
||||
jazz-react-native:
|
||||
specifier: link:../../packages/jazz-react-native
|
||||
version: link:../../packages/jazz-react-native
|
||||
jazz-tools:
|
||||
specifier: link:../../packages/jazz-tools
|
||||
version: link:../../packages/jazz-tools
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# cojson-transport-nodejs-ws
|
||||
|
||||
## 0.13.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b19cab7: Relax the WebSocket types on jazz-nodejs
|
||||
|
||||
## 0.13.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cojson-transport-ws",
|
||||
"type": "module",
|
||||
"version": "0.13.2",
|
||||
"version": "0.13.3",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./createWebSocketPeer.js";
|
||||
export * from "./WebSocketPeerWithReconnection.js";
|
||||
export { AnyWebSocketConstructor } from "./types.js";
|
||||
|
||||
@@ -25,3 +25,7 @@ export interface AnyWebSocket {
|
||||
readyState: number;
|
||||
bufferedAmount: number;
|
||||
}
|
||||
|
||||
export interface AnyWebSocketConstructor {
|
||||
new (url: string): AnyWebSocket;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# jazz-auth-clerk
|
||||
|
||||
## 0.13.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3129982: Rewrite the auth management making Clerk the source of truth. This fixes the logOut issues as well as making the logout work from the Clerk APIs. When the Clerk session expires now the user is correctly logged out from Jazz\
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-browser@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.13.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-browser@0.13.3
|
||||
|
||||
## 0.13.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
{
|
||||
"name": "jazz-auth-clerk",
|
||||
"version": "0.13.2",
|
||||
"version": "0.13.4",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "workspace:0.13.2",
|
||||
"jazz-browser": "workspace:0.13.2",
|
||||
"jazz-tools": "workspace:0.13.2"
|
||||
"jazz-browser": "workspace:0.13.4",
|
||||
"jazz-tools": "workspace:0.13.4"
|
||||
},
|
||||
"scripts": {
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"build": "rm -rf ./dist && tsc --sourceMap --outDir dist",
|
||||
"test": "vitest --run --root ../../ --project jazz-auth-clerk",
|
||||
"test:watch": "vitest --watch --root ../../ --project jazz-auth-clerk"
|
||||
"test:watch": "vitest --watch --root ../../ --project jazz-auth-clerk",
|
||||
"dev": " tsc --sourceMap --outDir dist --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "~5.6.2"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getClerkUsername } from "./getClerkUsername.js";
|
||||
import {
|
||||
ClerkCredentials,
|
||||
MinimalClerkClient,
|
||||
isClerkAuthStateEqual,
|
||||
isClerkCredentials,
|
||||
} from "./types.js";
|
||||
|
||||
@@ -37,13 +38,49 @@ export class JazzClerkAuth {
|
||||
});
|
||||
}
|
||||
|
||||
onClerkUserChange = async (clerkClient: Pick<MinimalClerkClient, "user">) => {
|
||||
if (!clerkClient.user) {
|
||||
static async initializeAuth(clerk: MinimalClerkClient) {
|
||||
const secretStorage = new AuthSecretStorage();
|
||||
|
||||
if (!isClerkCredentials(clerk.user?.unsafeMetadata)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await JazzClerkAuth.loadClerkAuthData(
|
||||
clerk.user.unsafeMetadata,
|
||||
secretStorage,
|
||||
);
|
||||
}
|
||||
|
||||
private isFirstCall = true;
|
||||
|
||||
registerListener(clerkClient: MinimalClerkClient) {
|
||||
let previousUser: MinimalClerkClient["user"] | null =
|
||||
clerkClient.user ?? null;
|
||||
|
||||
// Need to use addListener because the clerk user object is not updated when the user logs in
|
||||
return clerkClient.addListener((event) => {
|
||||
const user = (event as Pick<MinimalClerkClient, "user">).user ?? null;
|
||||
|
||||
if (!isClerkAuthStateEqual(previousUser, user) || this.isFirstCall) {
|
||||
this.onClerkUserChange({ user });
|
||||
previousUser = user;
|
||||
this.isFirstCall = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onClerkUserChange = async (clerkClient: Pick<MinimalClerkClient, "user">) => {
|
||||
const isAuthenticated = this.authSecretStorage.isAuthenticated;
|
||||
|
||||
// LogOut is driven by Clerk. The framework adapters will need to pass `logOutReplacement` to the `JazzProvider`
|
||||
// to make the logOut work correctly.
|
||||
if (!clerkClient.user) {
|
||||
if (isAuthenticated) {
|
||||
this.authSecretStorage.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAuthenticated) return;
|
||||
|
||||
const clerkCredentials = clerkClient.user
|
||||
|
||||
@@ -119,4 +119,153 @@ describe("JazzClerkAuth", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("registerListener", () => {
|
||||
function setupMockClerk(user: MinimalClerkClient["user"]) {
|
||||
const listners = new Set<
|
||||
(clerkClient: Pick<MinimalClerkClient, "user">) => void
|
||||
>();
|
||||
|
||||
return {
|
||||
client: {
|
||||
user,
|
||||
addListener: vi.fn((callback) => {
|
||||
listners.add(callback);
|
||||
return () => {
|
||||
listners.delete(callback);
|
||||
};
|
||||
}),
|
||||
} as unknown as MinimalClerkClient,
|
||||
triggerUserChange: (user: unknown) => {
|
||||
for (const listener of listners) {
|
||||
listener({ user } as Pick<MinimalClerkClient, "user">);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
it("should call onClerkUserChange on the first trigger", async () => {
|
||||
const { client, triggerUserChange } = setupMockClerk(null);
|
||||
|
||||
const auth = new JazzClerkAuth(mockAuthenticate, authSecretStorage);
|
||||
const onClerkUserChangeSpy = vi.spyOn(auth, "onClerkUserChange");
|
||||
|
||||
auth.registerListener(client);
|
||||
|
||||
triggerUserChange(null);
|
||||
|
||||
expect(onClerkUserChangeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call onClerkUserChange when user changes", async () => {
|
||||
const { client, triggerUserChange } = setupMockClerk(null);
|
||||
|
||||
const auth = new JazzClerkAuth(mockAuthenticate, authSecretStorage);
|
||||
const onClerkUserChangeSpy = vi.spyOn(auth, "onClerkUserChange");
|
||||
|
||||
auth.registerListener(client);
|
||||
|
||||
triggerUserChange(null);
|
||||
|
||||
triggerUserChange({
|
||||
unsafeMetadata: {
|
||||
jazzAccountID: "test123",
|
||||
jazzAccountSecret: "secret123",
|
||||
jazzAccountSeed: [1, 2, 3],
|
||||
},
|
||||
});
|
||||
|
||||
expect(onClerkUserChangeSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("should not call onClerkUserChange when user is the same", async () => {
|
||||
const { client, triggerUserChange } = setupMockClerk(null);
|
||||
|
||||
const auth = new JazzClerkAuth(mockAuthenticate, authSecretStorage);
|
||||
const onClerkUserChangeSpy = vi.spyOn(auth, "onClerkUserChange");
|
||||
|
||||
auth.registerListener(client);
|
||||
|
||||
triggerUserChange({
|
||||
unsafeMetadata: {
|
||||
jazzAccountID: "test123",
|
||||
jazzAccountSecret: "secret123",
|
||||
jazzAccountSeed: [1, 2, 3],
|
||||
},
|
||||
});
|
||||
|
||||
triggerUserChange({
|
||||
unsafeMetadata: {
|
||||
jazzAccountID: "test123",
|
||||
jazzAccountSecret: "secret123",
|
||||
jazzAccountSeed: [1, 2, 3],
|
||||
},
|
||||
});
|
||||
|
||||
expect(onClerkUserChangeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should not call onClerkUserChange when user switches from undefined to null", async () => {
|
||||
const { client, triggerUserChange } = setupMockClerk(null);
|
||||
|
||||
const auth = new JazzClerkAuth(mockAuthenticate, authSecretStorage);
|
||||
const onClerkUserChangeSpy = vi.spyOn(auth, "onClerkUserChange");
|
||||
|
||||
auth.registerListener(client);
|
||||
|
||||
triggerUserChange(null);
|
||||
triggerUserChange(undefined);
|
||||
triggerUserChange(null);
|
||||
|
||||
expect(onClerkUserChangeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("initializeAuth", () => {
|
||||
it("should load auth data when credentials exist", async () => {
|
||||
const mockClerk = {
|
||||
user: {
|
||||
unsafeMetadata: {
|
||||
jazzAccountID: "test123",
|
||||
jazzAccountSecret: "secret123",
|
||||
jazzAccountSeed: [1, 2, 3],
|
||||
},
|
||||
},
|
||||
} as unknown as MinimalClerkClient;
|
||||
|
||||
await JazzClerkAuth.initializeAuth(mockClerk);
|
||||
|
||||
const storedAuth = await authSecretStorage.get();
|
||||
expect(storedAuth).toEqual({
|
||||
accountID: "test123",
|
||||
accountSecret: "secret123",
|
||||
secretSeed: new Uint8Array([1, 2, 3]),
|
||||
provider: "clerk",
|
||||
});
|
||||
});
|
||||
|
||||
it("should do nothing when no credentials exist", async () => {
|
||||
const mockClerk = {
|
||||
user: {
|
||||
unsafeMetadata: {},
|
||||
},
|
||||
} as unknown as MinimalClerkClient;
|
||||
|
||||
await JazzClerkAuth.initializeAuth(mockClerk);
|
||||
|
||||
const storedAuth = await authSecretStorage.get();
|
||||
expect(storedAuth).toBeNull();
|
||||
});
|
||||
|
||||
it("should do nothing when no user exists", async () => {
|
||||
const mockClerk = {
|
||||
user: null,
|
||||
} as unknown as MinimalClerkClient;
|
||||
|
||||
await JazzClerkAuth.initializeAuth(mockClerk);
|
||||
|
||||
const storedAuth = await authSecretStorage.get();
|
||||
expect(storedAuth).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,3 +40,13 @@ export function isClerkCredentials(
|
||||
): data is ClerkCredentials {
|
||||
return !!data && "jazzAccountID" in data && "jazzAccountSecret" in data;
|
||||
}
|
||||
|
||||
export function isClerkAuthStateEqual(
|
||||
previousUser: MinimalClerkClient["user"] | null | undefined,
|
||||
newUser: MinimalClerkClient["user"] | null | undefined,
|
||||
) {
|
||||
const previousCredentials = isClerkCredentials(previousUser?.unsafeMetadata);
|
||||
const newCredentials = isClerkCredentials(newUser?.unsafeMetadata);
|
||||
|
||||
return previousCredentials === newCredentials;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# jazz-browser-media-images
|
||||
|
||||
## 0.13.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-browser@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.13.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-browser@0.13.3
|
||||
|
||||
## 0.13.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-browser-media-images",
|
||||
"version": "0.13.2",
|
||||
"version": "0.13.4",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -8,8 +8,8 @@
|
||||
"dependencies": {
|
||||
"@types/image-blob-reduce": "^4.1.1",
|
||||
"image-blob-reduce": "^4.1.0",
|
||||
"jazz-browser": "workspace:0.13.2",
|
||||
"jazz-tools": "workspace:0.13.2",
|
||||
"jazz-browser": "workspace:0.13.4",
|
||||
"jazz-tools": "workspace:0.13.4",
|
||||
"pica": "^9.0.1",
|
||||
"typescript": "~5.6.2"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# jazz-browser
|
||||
|
||||
## 0.13.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3129982: Add logOutReplacement hook to replace the Jazz logout function with a custom one
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.13.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [b19cab7]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- cojson-transport-ws@0.13.3
|
||||
|
||||
## 0.13.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-browser",
|
||||
"version": "0.13.2",
|
||||
"version": "0.13.4",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -17,6 +17,7 @@ export type JazzContextManagerProps<Acc extends Account> = {
|
||||
guestMode?: boolean;
|
||||
sync: SyncConfig;
|
||||
onLogOut?: () => void;
|
||||
logOutReplacement?: () => void;
|
||||
onAnonymousAccountDiscarded?: (anonymousAccount: Acc) => Promise<void>;
|
||||
storage?: BaseBrowserContextOptions["storage"];
|
||||
AccountSchema?: AccountClass<Acc>;
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# jazz-browser
|
||||
|
||||
## 0.13.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3129982: Rewrite the auth management making Clerk the source of truth. This fixes the logOut issues as well as making the logout work from the Clerk APIs. When the Clerk session expires now the user is correctly logged out from Jazz\
|
||||
- 3129982: Add logOutReplacement hook to replace the Jazz logout function with a custom one
|
||||
- Updated dependencies [3129982]
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-auth-clerk@0.13.4
|
||||
- jazz-react-native-core@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-react-core@0.13.4
|
||||
|
||||
## 0.13.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [b19cab7]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- cojson-transport-ws@0.13.3
|
||||
- jazz-auth-clerk@0.13.3
|
||||
- jazz-react-core@0.13.3
|
||||
- jazz-react-native-core@0.13.3
|
||||
|
||||
## 0.13.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-expo",
|
||||
"version": "0.13.2",
|
||||
"version": "0.13.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { JazzClerkAuth, type MinimalClerkClient } from "jazz-auth-clerk";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import {
|
||||
JazzClerkAuth,
|
||||
type MinimalClerkClient,
|
||||
isClerkCredentials,
|
||||
} from "jazz-auth-clerk";
|
||||
import { AuthSecretStorage, KvStoreContext } from "jazz-tools";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
ExpoSecureStoreAdapter,
|
||||
JazzProvider,
|
||||
JazzProviderProps,
|
||||
useAuthSecretStorage,
|
||||
@@ -20,10 +26,7 @@ function useJazzClerkAuth(clerk: MinimalClerkClient) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Need to use addListener because the clerk user object is not updated when the user logs in
|
||||
return clerk.addListener((event) => {
|
||||
authMethod.onClerkUserChange(event as Pick<MinimalClerkClient, "user">);
|
||||
});
|
||||
return authMethod.registerListener(clerk);
|
||||
}, []);
|
||||
}
|
||||
|
||||
@@ -37,9 +40,34 @@ function RegisterClerkAuth(props: {
|
||||
|
||||
export const JazzProviderWithClerk = (
|
||||
props: { clerk: MinimalClerkClient } & JazzProviderProps,
|
||||
): JSX.Element => {
|
||||
): JSX.Element | null => {
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
/**
|
||||
* This effect ensures that a logged-in Clerk user is authenticated before the JazzProvider is mounted.
|
||||
*
|
||||
* This is done to optimize the initial load.
|
||||
*/
|
||||
useEffect(() => {
|
||||
KvStoreContext.getInstance().initialize(
|
||||
props.kvStore ?? new ExpoSecureStoreAdapter(),
|
||||
);
|
||||
|
||||
JazzClerkAuth.initializeAuth(props.clerk)
|
||||
.then(() => {
|
||||
setIsLoaded(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("error initializing auth", error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!isLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<JazzProvider {...props} onLogOut={props.clerk.signOut}>
|
||||
<JazzProvider {...props} logOutReplacement={props.clerk.signOut}>
|
||||
<RegisterClerkAuth clerk={props.clerk}>
|
||||
{props.children}
|
||||
</RegisterClerkAuth>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user