Compare commits
113 Commits
jazz-react
...
jazz-react
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7895504a4 | ||
|
|
699db46ed6 | ||
|
|
d303be6709 | ||
|
|
fe6f561afd | ||
|
|
93c7f6c253 | ||
|
|
3b2831f7de | ||
|
|
902e539f56 | ||
|
|
9e9946cf48 | ||
|
|
051e6c497e | ||
|
|
9b2a73b900 | ||
|
|
dd8ed599f7 | ||
|
|
33e51267e3 | ||
|
|
59251a58c4 | ||
|
|
e090b3992d | ||
|
|
31794684a0 | ||
|
|
14fcc8dde5 | ||
|
|
13f1821c04 | ||
|
|
4c3b85abcd | ||
|
|
4b0544fd0d | ||
|
|
568674a7fa | ||
|
|
ef012f07aa | ||
|
|
eee2330325 | ||
|
|
b83ec05ccc | ||
|
|
386525db48 | ||
|
|
a8809d840c | ||
|
|
005fc1f8c9 | ||
|
|
3129982582 | ||
|
|
5d7bb70c7d | ||
|
|
1a7a84f71b | ||
|
|
1d51bdc016 | ||
|
|
8da6f3a897 | ||
|
|
dca9293ae7 | ||
|
|
2b362fd331 | ||
|
|
f03f1b6de8 | ||
|
|
bd57177586 | ||
|
|
9e0e2709a5 | ||
|
|
01b2ab7148 | ||
|
|
44b7d39467 | ||
|
|
5373ee2858 | ||
|
|
b19cab78d3 | ||
|
|
3f86dfce4f | ||
|
|
12f8bfa28f | ||
|
|
53211a4fca | ||
|
|
0a6cd4e9b2 | ||
|
|
69954caee6 | ||
|
|
6a5bd28d07 | ||
|
|
ba58bc3ace | ||
|
|
94cb615a38 | ||
|
|
b130f46b6c | ||
|
|
989d59f978 | ||
|
|
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 | ||
|
|
785fc893ee | ||
|
|
4f5e471667 | ||
|
|
fc6b20d370 | ||
|
|
e4886d1b03 | ||
|
|
dfb2b19209 | ||
|
|
1a92d6b1e5 | ||
|
|
d5c1f49cc5 | ||
|
|
7d6ce843de | ||
|
|
0298f0eb29 | ||
|
|
794c56dfac | ||
|
|
6b9382b5e9 | ||
|
|
08ae9b295f | ||
|
|
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,34 @@
|
||||
# chat-rn-expo-clerk
|
||||
|
||||
## 1.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-expo@0.13.5
|
||||
- jazz-react-native-media-images@0.13.5
|
||||
|
||||
## 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.98",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# chat-rn-expo
|
||||
|
||||
## 1.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-expo@0.13.5
|
||||
|
||||
## 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.85",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e090b39]
|
||||
- Updated dependencies [fe6f561]
|
||||
- cojson@0.13.5
|
||||
- jazz-tools@0.13.5
|
||||
- cojson-transport-ws@0.13.5
|
||||
- jazz-react-native@0.13.5
|
||||
|
||||
## 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.94",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-browser@0.13.5
|
||||
- jazz-vue@0.13.5
|
||||
|
||||
## 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.78",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.175
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [08ae9b2]
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-inspector@0.13.5
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.175",
|
||||
"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,34 @@
|
||||
# minimal-auth-clerk
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
- jazz-react-auth-clerk@0.13.5
|
||||
|
||||
## 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.74",
|
||||
"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();
|
||||
});
|
||||
33
examples/clerk/tests/reload.spec.ts
Normal file
33
examples/clerk/tests/reload.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
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();
|
||||
|
||||
await page.getByText("You're logged in").waitFor({ state: "visible" });
|
||||
|
||||
expect(page.getByText("You're logged in")).toBeVisible();
|
||||
});
|
||||
@@ -1,5 +1,30 @@
|
||||
# file-share-svelte
|
||||
|
||||
## 0.0.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-svelte@0.13.5
|
||||
|
||||
## 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.58",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# jazz-tailwind-demo-auth-starter
|
||||
|
||||
## 0.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [08ae9b2]
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-inspector@0.13.5
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.14",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# form
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.16",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# image-upload
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.72",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# jazz-example-inspector
|
||||
|
||||
## 0.0.125
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [08ae9b2]
|
||||
- Updated dependencies [e090b39]
|
||||
- jazz-inspector@0.13.5
|
||||
- cojson@0.13.5
|
||||
- cojson-transport-ws@0.13.5
|
||||
|
||||
## 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.125",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# multi-cursors
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.68",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# multiauth
|
||||
|
||||
## 0.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
- jazz-react-auth-clerk@0.13.5
|
||||
|
||||
## 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.15",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [08ae9b2]
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-inspector@0.13.5
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.96",
|
||||
"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 up",
|
||||
});
|
||||
|
||||
logoutButton = this.page.getByRole("button", {
|
||||
name: "Logout",
|
||||
name: "Sign out",
|
||||
});
|
||||
|
||||
async expectActiveTrackPlaying() {
|
||||
@@ -131,12 +135,21 @@ 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 this.logoutButton.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await expect(this.logoutButton).toBeVisible();
|
||||
}
|
||||
|
||||
async logout() {
|
||||
async logOut() {
|
||||
await this.logoutButton.click();
|
||||
|
||||
await this.loginButton.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await expect(this.loginButton).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# organization
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.68",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.62
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.5
|
||||
|
||||
## 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.62",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# minimal-auth-passkey
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.73",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# passphrase
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.70",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.94",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.192
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.192",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# reactions
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.72",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-browser@0.13.5
|
||||
- jazz-vue@0.13.5
|
||||
|
||||
## 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.76",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.191
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.191",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# version-history
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [08ae9b2]
|
||||
- Updated dependencies [fe6f561]
|
||||
- jazz-inspector@0.13.5
|
||||
- jazz-tools@0.13.5
|
||||
- jazz-react@0.13.5
|
||||
|
||||
## 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.69",
|
||||
"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,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PackageDocs } from "@/components/docs/packageDocs";
|
||||
import { packages } from "@/lib/packages";
|
||||
import { packages } from "@/content/packages";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { TocProvider } from "@/components/TocProvider";
|
||||
import { ApiNav } from "@/components/docs/ApiNav";
|
||||
import DocsLayout from "@/components/docs/DocsLayout";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
@@ -9,10 +8,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<TocProvider>
|
||||
<DocsLayout nav={<ApiNav />} navIcon="package" navName="API Ref">
|
||||
<Prose className="overflow-x-hidden lg:flex-1 py-10">{children}</Prose>
|
||||
</DocsLayout>
|
||||
</TocProvider>
|
||||
<DocsLayout nav={<ApiNav />} navIcon="package" navName="API Ref">
|
||||
<Prose className="overflow-x-hidden lg:flex-1 py-10">{children}</Prose>
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { packages } from "@/lib/packages";
|
||||
import { packages } from "@/content/packages";
|
||||
import { clsx } from "clsx";
|
||||
import { Icon } from "gcmp-design-system/src/app/components/atoms/Icon";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
@@ -1,413 +0,0 @@
|
||||
export const metadata = { title: "Authentication methods" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Authentication in Jazz
|
||||
|
||||
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.
|
||||
|
||||
Without any additional steps the user can use Jazz normally, but they would be limited to use on only one device.
|
||||
|
||||
To make Accounts work across devices, you can store/retrieve the account keys from an authentication method by using the corresponding hooks and providers.
|
||||
|
||||
<ContentByFramework framework={["react", "vue", "svelte"]}>
|
||||
## Authentication with passkeys [!framework=react,vue,svelte]
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
<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("");
|
||||
|
||||
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>
|
||||
|
||||
|
||||
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"`.
|
||||
@@ -1,117 +0,0 @@
|
||||
import { TocItemsSetter } from "@/components/docs/TocItemsSetter";
|
||||
import ComingSoonPage from "@/components/docs/coming-soon.mdx";
|
||||
import { docNavigationItems } from "@/lib/docNavigationItems.js";
|
||||
import { Framework, frameworks } from "@/lib/framework";
|
||||
import type { Toc } from "@stefanprobst/rehype-extract-toc";
|
||||
|
||||
async function getMdxSource(framework: string, slugPath?: string) {
|
||||
// Try to import the framework-specific file first
|
||||
try {
|
||||
if (!slugPath) {
|
||||
return await import("./index.mdx");
|
||||
}
|
||||
return await import(`./${slugPath}/${framework}.mdx`);
|
||||
} catch (error) {
|
||||
// Fallback to vanilla
|
||||
return await import(`./${slugPath}.mdx`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: { params: Promise<{ slug: string[]; framework: string }> }) {
|
||||
const { slug, framework } = await params;
|
||||
|
||||
const slugPath = slug?.join("/");
|
||||
|
||||
try {
|
||||
const mdxSource = await getMdxSource(framework, slugPath);
|
||||
const title = mdxSource.tableOfContents?.[0].value || "Documentation";
|
||||
|
||||
return {
|
||||
title,
|
||||
openGraph: {
|
||||
title,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
title: "Documentation",
|
||||
openGraph: {
|
||||
title: "Documentation",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: { params: Promise<{ slug: string[]; framework: string }> }) {
|
||||
const { slug, framework } = await params;
|
||||
|
||||
const slugPath = slug?.join("/");
|
||||
|
||||
try {
|
||||
const mdxSource = await getMdxSource(framework, slugPath);
|
||||
const {
|
||||
default: Content,
|
||||
tableOfContents,
|
||||
headingsFrameworkVisibility,
|
||||
test,
|
||||
} = mdxSource;
|
||||
|
||||
// Remove items that should not be shown for the current framework
|
||||
const tocItems = (tableOfContents as Toc).filter(({ id }) =>
|
||||
id && id in headingsFrameworkVisibility
|
||||
? headingsFrameworkVisibility[id]?.includes(framework)
|
||||
: true,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TocItemsSetter items={tocItems} />
|
||||
<Content />
|
||||
</>
|
||||
);
|
||||
} catch (error) {
|
||||
return (
|
||||
<>
|
||||
<TocItemsSetter items={[]} />
|
||||
<ComingSoonPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// https://nextjs.org/docs/app/api-reference/functions/generate-static-params
|
||||
export const dynamicParams = false;
|
||||
export const dynamic = "force-static";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const paths: Array<{ slug?: string[]; framework: Framework }> = [];
|
||||
|
||||
for (const framework of frameworks) {
|
||||
paths.push({
|
||||
framework,
|
||||
slug: [],
|
||||
});
|
||||
for (const heading of docNavigationItems) {
|
||||
for (const item of heading?.items) {
|
||||
if (item.href && item.href.startsWith("/docs")) {
|
||||
const slug = item.href
|
||||
.replace("/docs", "")
|
||||
.split("/")
|
||||
.filter(Boolean);
|
||||
if (slug.length) {
|
||||
paths.push({
|
||||
slug,
|
||||
framework,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { docNavigationItems } from "@/content/docs/docNavigationItems.js";
|
||||
import { Framework, frameworks } from "@/content/framework";
|
||||
import { DocPage, getDocMetadata } from "@/lib/docMdxContent";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ topic: string; subtopic: string; framework: string }>;
|
||||
}) {
|
||||
const { topic, subtopic, framework } = await params;
|
||||
|
||||
return getDocMetadata(framework, [topic, subtopic]);
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ topic: string; subtopic: string; framework: string }>;
|
||||
}) {
|
||||
const { topic, subtopic, framework } = await params;
|
||||
|
||||
return <DocPage framework={framework} slug={[topic, subtopic]} />;
|
||||
}
|
||||
|
||||
// https://nextjs.org/docs/app/api-reference/functions/generate-static-params
|
||||
export const dynamicParams = false;
|
||||
export const dynamic = "force-static";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const paths: Array<{
|
||||
topic?: string;
|
||||
subtopic?: string;
|
||||
framework: Framework;
|
||||
}> = [];
|
||||
|
||||
for (const framework of frameworks) {
|
||||
for (const heading of docNavigationItems) {
|
||||
for (const item of heading?.items) {
|
||||
if (item.href && item.href.startsWith("/docs")) {
|
||||
const [topic, subtopic] = item.href
|
||||
.replace("/docs", "")
|
||||
.split("/")
|
||||
.filter(Boolean);
|
||||
if (topic && subtopic) {
|
||||
paths.push({
|
||||
topic,
|
||||
subtopic,
|
||||
framework,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { docNavigationItems } from "@/content/docs/docNavigationItems.js";
|
||||
import { Framework, frameworks } from "@/content/framework";
|
||||
import { DocPage, getDocMetadata } from "@/lib/docMdxContent";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ topic: string; framework: string }>;
|
||||
}) {
|
||||
const { topic, framework } = await params;
|
||||
|
||||
return getDocMetadata(framework, [topic]);
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ topic: string; framework: string }>;
|
||||
}) {
|
||||
const { topic, framework } = await params;
|
||||
|
||||
return <DocPage framework={framework} slug={[topic]} />;
|
||||
}
|
||||
|
||||
// https://nextjs.org/docs/app/api-reference/functions/generate-static-params
|
||||
export const dynamicParams = false;
|
||||
export const dynamic = "force-static";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const paths: Array<{ topic?: string; framework: Framework }> = [];
|
||||
|
||||
for (const framework of frameworks) {
|
||||
for (const heading of docNavigationItems) {
|
||||
for (const item of heading?.items) {
|
||||
if (item.href && item.href.startsWith("/docs")) {
|
||||
const [topic] = item.href
|
||||
.replace("/docs", "")
|
||||
.split("/")
|
||||
.filter(Boolean);
|
||||
if (topic) {
|
||||
paths.push({
|
||||
topic,
|
||||
framework,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
38
homepage/homepage/app/(docs)/docs/[framework]/page.tsx
Normal file
38
homepage/homepage/app/(docs)/docs/[framework]/page.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Framework, frameworks } from "@/content/framework";
|
||||
import { DocPage, getDocMetadata } from "@/lib/docMdxContent";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ framework: string }>;
|
||||
}) {
|
||||
const { framework } = await params;
|
||||
|
||||
return getDocMetadata(framework, []);
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ framework: string }>;
|
||||
}) {
|
||||
const { framework } = await params;
|
||||
|
||||
return <DocPage framework={framework} slug={[]} />;
|
||||
}
|
||||
|
||||
// https://nextjs.org/docs/app/api-reference/functions/generate-static-params
|
||||
export const dynamicParams = false;
|
||||
export const dynamic = "force-static";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const paths: Array<{ framework: Framework }> = [];
|
||||
|
||||
for (const framework of frameworks) {
|
||||
paths.push({
|
||||
framework,
|
||||
});
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { TocProvider } from "@/components/TocProvider";
|
||||
import DocsLayout from "@/components/docs/DocsLayout";
|
||||
import { DocNav } from "@/components/docs/nav";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<TocProvider>
|
||||
<DocsLayout nav={<DocNav />}>
|
||||
<Prose className="overflow-x-hidden lg:flex-1 py-10 max-w-3xl mx-auto">
|
||||
{children}
|
||||
</Prose>
|
||||
</DocsLayout>
|
||||
</TocProvider>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { ReactLogo } from "@/components/icons/ReactLogo";
|
||||
import { ReactNativeLogo } from "@/components/icons/ReactNativeLogo";
|
||||
import { SvelteLogo } from "@/components/icons/SvelteLogo";
|
||||
import { VueLogo } from "@/components/icons/VueLogo";
|
||||
import { Example, features, tech } from "@/lib/example";
|
||||
import { Example, features, tech } from "@/content/example";
|
||||
import { clsx } from "clsx";
|
||||
import { H2 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import { Icon } from "gcmp-design-system/src/app/components/atoms/Icon";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { products } from "@/lib/showcase";
|
||||
import { products } from "@/content/showcase";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -6,6 +6,7 @@ import localFont from "next/font/local";
|
||||
|
||||
import { ThemeProvider } from "@/components/ThemeProvider";
|
||||
import { JazzFooter } from "@/components/footer";
|
||||
import { marketingCopy } from "@/content/marketingCopy";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
14
homepage/homepage/app/opengraph-image.tsx
Normal file
14
homepage/homepage/app/opengraph-image.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { marketingCopy } from "@/content/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,6 +1,6 @@
|
||||
import { SideNavHeader } from "@/components/SideNavHeader";
|
||||
import { SideNavItem } from "@/components/SideNavItem";
|
||||
import { Framework } from "@/lib/framework";
|
||||
import { Framework } from "@/content/framework";
|
||||
import { useFramework } from "@/lib/use-framework";
|
||||
import { clsx } from "clsx";
|
||||
import { Icon } from "gcmp-design-system/src/app/components/atoms/Icon";
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { TocContext } from "@/lib/TocContext";
|
||||
import type { Toc, TocEntry } from "@stefanprobst/rehype-extract-toc";
|
||||
import { useState } from "react";
|
||||
|
||||
export function TocProvider({ children }: { children: React.ReactNode }) {
|
||||
const [tocItems, setTocItems] = useState<Toc>();
|
||||
|
||||
return (
|
||||
<TocContext.Provider value={{ tocItems, setTocItems }}>
|
||||
{children}
|
||||
</TocContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SideNavHeader } from "@/components/SideNavHeader";
|
||||
import { SideNavItem } from "@/components/SideNavItem";
|
||||
import { packages } from "@/lib/packages";
|
||||
import { packages } from "@/content/packages";
|
||||
import { clsx } from "clsx";
|
||||
import { Icon } from "gcmp-design-system/src/app/components/atoms/Icon";
|
||||
import Link from "next/link";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { JazzNav } from "@/components/nav";
|
||||
import { useTocItems } from "@/lib/TocContext";
|
||||
import { TocEntry } from "@stefanprobst/rehype-extract-toc";
|
||||
import { clsx } from "clsx";
|
||||
import type { IconName } from "gcmp-design-system/src/app/components/atoms/Icon";
|
||||
import type { NavSection } from "gcmp-design-system/src/app/components/organisms/Nav";
|
||||
@@ -12,14 +12,14 @@ export default function DocsLayout({
|
||||
nav,
|
||||
navName,
|
||||
navIcon,
|
||||
tocItems,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
nav?: React.ReactNode;
|
||||
navName?: string;
|
||||
navIcon?: IconName;
|
||||
tocItems?: TocEntry[];
|
||||
}) {
|
||||
const { tocItems } = useTocItems();
|
||||
|
||||
const navSections: NavSection[] = [
|
||||
{
|
||||
name: navName || "Docs",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { isValidFramework } from "@/lib/framework";
|
||||
import { isValidFramework } from "@/content/framework";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { AnchorHTMLAttributes, DetailedHTMLProps } from "react";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Framework } from "@/lib/framework";
|
||||
import { Framework } from "@/content/framework";
|
||||
import { useFramework } from "@/lib/use-framework";
|
||||
import { Button } from "gcmp-design-system/src/app/components/atoms/Button";
|
||||
import { Icon } from "gcmp-design-system/src/app/components/atoms/Icon";
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useTocItems } from "@/lib/TocContext";
|
||||
import type { TocEntry } from "@stefanprobst/rehype-extract-toc";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function TocItemsSetter({ items }: { items: TocEntry[] | undefined }) {
|
||||
const { setTocItems } = useTocItems();
|
||||
|
||||
useEffect(() => {
|
||||
setTocItems(items);
|
||||
}, [items, setTocItems]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import { SideNav } from "@/components/SideNav";
|
||||
import { SideNavHeader } from "@/components/SideNavHeader";
|
||||
import { FrameworkSelect } from "@/components/docs/FrameworkSelect";
|
||||
import { docNavigationItems } from "@/lib/docNavigationItems.js";
|
||||
import { docNavigationItems } from "@/content/docs/docNavigationItems.js";
|
||||
import { useFramework } from "@/lib/use-framework";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExampleLinks } from "@/components/examples/ExampleLinks";
|
||||
import { ExampleTags } from "@/components/examples/ExampleTags";
|
||||
import { Example } from "@/lib/example";
|
||||
import { Example } from "@/content/example";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export function ExampleCard({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CodeExampleTabs } from "@/components/examples/CodeExampleTabs";
|
||||
import { ExampleLinks } from "@/components/examples/ExampleLinks";
|
||||
import { ExampleTags } from "@/components/examples/ExampleTags";
|
||||
import { Example } from "@/lib/example";
|
||||
import { Example } from "@/content/example";
|
||||
import { GappedGrid } from "gcmp-design-system/src/app/components/molecules/GappedGrid";
|
||||
|
||||
export function ExampleDemo({ example }: { example: Example }) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Example } from "@/lib/example";
|
||||
import { Example } from "@/content/example";
|
||||
import { InterpolateInCode } from "@/mdx-components";
|
||||
import { DialogDescription } from "@headlessui/react";
|
||||
import { Button } from "gcmp-design-system/src/app/components/atoms/Button";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Example } from "@/lib/example";
|
||||
import { Example } from "@/content/example";
|
||||
|
||||
export function ExampleTags({ example }: { example: Example }) {
|
||||
const { tech, features } = example;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
import { socials } from "@/lib/socials";
|
||||
import { socials } from "@/content/socials";
|
||||
import { GcmpLogo } from "gcmp-design-system/src/app/components/atoms/logos/GcmpLogo";
|
||||
import { Footer } from "gcmp-design-system/src/app/components/organisms/Footer";
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CreateJazzApp from "@/components/home/CreateJazzApp.mdx";
|
||||
import { marketingCopy } from "@/content/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">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
import { socials } from "@/lib/socials";
|
||||
import { socials } from "@/content/socials";
|
||||
import { JazzLogo } from "gcmp-design-system/src/app/components/atoms/logos/JazzLogo";
|
||||
import {
|
||||
Nav,
|
||||
|
||||
@@ -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"`.
|
||||
221
homepage/homepage/content/docs/authentication/clerk.mdx
Normal file
221
homepage/homepage/content/docs/authentication/clerk.mdx
Normal file
@@ -0,0 +1,221 @@
|
||||
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.
|
||||
|
||||
## 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)
|
||||
@@ -0,0 +1,3 @@
|
||||
# Clerk Authentication
|
||||
|
||||
We do not currently support Clerk in React Native, but we do have support for [React Native Expo](/docs/react-native-expo/authentication/clerk).
|
||||
37
homepage/homepage/content/docs/authentication/overview.mdx
Normal file
37
homepage/homepage/content/docs/authentication/overview.mdx
Normal file
@@ -0,0 +1,37 @@
|
||||
export const metadata = { title: "Authentication methods" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Authentication in Jazz
|
||||
|
||||
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.
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
When a user first opens your app, they'll be in one of these states:
|
||||
|
||||
- **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.
|
||||
|
||||
- **Authenticated Account**: Full account accessible across multiple devices using [passkeys](./passkey), [passphrases](./passphrase), or third-party authentications, such as [Clerk](./clerk).
|
||||
|
||||
- **Guest Mode**: No account, read-only access to public content. Users can browse but can't save data or sync.
|
||||
|
||||
Learn more about these states in the [Authentication States](./authentication-states) documentation.
|
||||
|
||||
Without authentication, users are limited to using the application on only one device.
|
||||
|
||||
When a user logs out of an Authenticated Account, they return to the Anonymous Authentication state with a new local account.
|
||||
|
||||
Here's what happens during registration and login:
|
||||
|
||||
- **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.
|
||||
|
||||
- **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).
|
||||
|
||||
## Available Authentication Methods
|
||||
|
||||
Jazz provides several ways to authenticate users:
|
||||
|
||||
- [**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
|
||||
97
homepage/homepage/content/docs/authentication/passkey.mdx
Normal file
97
homepage/homepage/content/docs/authentication/passkey.mdx
Normal file
@@ -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)
|
||||
194
homepage/homepage/content/docs/authentication/passphrase.mdx
Normal file
194
homepage/homepage/content/docs/authentication/passphrase.mdx
Normal file
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user