Compare commits

...

6 Commits

Author SHA1 Message Date
Guido D'Orsi
eee2330325 Merge pull request #1891 from garden-co/changeset-release/main
Version Packages
2025-04-10 19:49:47 +02:00
github-actions[bot]
b83ec05ccc Version Packages 2025-04-10 17:36:24 +00:00
Guido D'Orsi
386525db48 Merge pull request #1886 from garden-co/fix/clerk-auth
fix: handle Clerk expiration and fix logout
2025-04-10 19:34:01 +02:00
Guido D'Orsi
a8809d840c test: add logout test on music-player 2025-04-10 19:26:28 +02:00
Guido D'Orsi
005fc1f8c9 fix: restore logOut when logOutReplacement is not passed 2025-04-10 19:07:34 +02:00
Guido D'Orsi
3129982582 fix: handle Clerk expiration and fix logout 2025-04-10 18:22:52 +02:00
106 changed files with 974 additions and 292 deletions

View File

@@ -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

View File

@@ -1,5 +1,15 @@
# chat-rn-expo-clerk
## 1.0.97
### Patch Changes
- Updated dependencies [3129982]
- Updated dependencies [3129982]
- jazz-expo@0.13.4
- jazz-tools@0.13.4
- jazz-react-native-media-images@0.13.4
## 1.0.96
### Patch Changes

View File

@@ -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>

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-expo-clerk",
"main": "index.js",
"version": "1.0.96",
"version": "1.0.97",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",

View File

@@ -1,5 +1,14 @@
# chat-rn-expo
## 1.0.84
### Patch Changes
- Updated dependencies [3129982]
- Updated dependencies [3129982]
- jazz-expo@0.13.4
- jazz-tools@0.13.4
## 1.0.83
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn-expo",
"version": "1.0.83",
"version": "1.0.84",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",

View File

@@ -1,5 +1,13 @@
# chat-rn
## 1.0.93
### Patch Changes
- Updated dependencies [3129982]
- jazz-tools@0.13.4
- jazz-react-native@0.13.4
## 1.0.92
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.92",
"version": "1.0.93",
"main": "index.js",
"scripts": {
"android": "react-native run-android",

View File

@@ -1,5 +1,14 @@
# chat-vue
## 0.0.77
### Patch Changes
- Updated dependencies [3129982]
- jazz-browser@0.13.4
- jazz-tools@0.13.4
- jazz-vue@0.13.4
## 0.0.76
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-vue",
"version": "0.0.76",
"version": "0.0.77",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,14 @@
# jazz-example-chat
## 0.0.174
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
- jazz-inspector@0.13.4
## 0.0.173
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.173",
"version": "0.0.174",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
playwright-report

View File

@@ -1,5 +1,15 @@
# minimal-auth-clerk
## 0.0.73
### Patch Changes
- Updated dependencies [3129982]
- Updated dependencies [3129982]
- jazz-react-auth-clerk@0.13.4
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.72
### Patch Changes

View File

@@ -1,14 +1,16 @@
{
"name": "clerk",
"private": true,
"version": "0.0.72",
"version": "0.0.73",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write"
"format-and-lint:fix": "biome check . --write",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
},
"dependencies": {
"@clerk/clerk-react": "^5.4.1",
@@ -19,6 +21,7 @@
"react-dom": "^18.3.1"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@biomejs/biome": "1.9.4",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",

View 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,
},
],
});

View File

@@ -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>
);
}

View File

@@ -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>,
);
}

View 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" });
});

View 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();
});

View File

@@ -0,0 +1,31 @@
import { expect, test } from "@playwright/test";
test("login & reload", async ({ page }) => {
await page.goto("/");
expect(page.getByText("You're not logged in")).toBeVisible();
await page.getByRole("button", { name: "Sign in" }).click();
await page
.getByRole("textbox", { name: "Email address" })
.fill("guido+clerk-test@garden.co");
await page.keyboard.press("Enter");
await page
.getByRole("textbox", { name: "Password" })
.fill("guido+clerk-test@garden.co");
await page.keyboard.press("Enter");
await page.waitForURL("/");
await page.getByText("You're logged in").waitFor({ state: "visible" });
expect(page.getByText("You're logged in")).toBeVisible();
await page.reload();
expect(page.getByText("You're logged in")).toBeVisible();
});

View File

@@ -1,5 +1,13 @@
# file-share-svelte
## 0.0.57
### Patch Changes
- Updated dependencies [3129982]
- jazz-tools@0.13.4
- jazz-svelte@0.13.4
## 0.0.56
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "file-share-svelte",
"version": "0.0.56",
"version": "0.0.57",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,14 @@
# jazz-tailwind-demo-auth-starter
## 0.0.13
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
- jazz-inspector@0.13.4
## 0.0.12
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "filestream",
"private": true,
"version": "0.0.12",
"version": "0.0.13",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,13 @@
# form
## 0.1.15
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.1.14
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "form",
"private": true,
"version": "0.1.14",
"version": "0.1.15",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,13 @@
# image-upload
## 0.0.71
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.70
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "image-upload",
"private": true,
"version": "0.0.70",
"version": "0.0.71",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,11 @@
# jazz-example-inspector
## 0.0.124
### Patch Changes
- jazz-inspector@0.13.4
## 0.0.123
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector-app",
"private": true,
"version": "0.0.123",
"version": "0.0.124",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,13 @@
# multi-cursors
## 0.0.67
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.66
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "multi-cursors",
"private": true,
"version": "0.0.66",
"version": "0.0.67",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,15 @@
# multiauth
## 0.0.14
### Patch Changes
- Updated dependencies [3129982]
- Updated dependencies [3129982]
- jazz-react-auth-clerk@0.13.4
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.13
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "multiauth",
"private": true,
"version": "0.0.13",
"version": "0.0.14",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,14 @@
# jazz-example-musicplayer
## 0.0.95
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
- jazz-inspector@0.13.4
## 0.0.94
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.94",
"version": "0.0.95",
"type": "module",
"scripts": {
"dev": "vite",

View 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();
});

View File

@@ -11,8 +11,12 @@ export class HomePage {
name: "Playlist title",
});
loginButton = this.page.getByRole("button", {
name: "Sign in",
});
logoutButton = this.page.getByRole("button", {
name: "Logout",
name: "Sign out",
});
async expectActiveTrackPlaying() {
@@ -131,12 +135,12 @@ export class HomePage {
await this.page
.getByRole("button", { name: "Sign up with passkey" })
.click();
await expect(
this.page.getByRole("button", { name: "Sign out" }),
).toBeVisible();
await expect(this.logoutButton).toBeVisible();
}
async logout() {
async logOut() {
await this.logoutButton.click();
await expect(this.loginButton).toBeVisible();
}
}

View File

@@ -1,5 +1,13 @@
# organization
## 0.0.67
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.66
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "organization",
"private": true,
"version": "0.0.66",
"version": "0.0.67",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,11 @@
# passkey-svelte
## 0.0.61
### Patch Changes
- jazz-svelte@0.13.4
## 0.0.60
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "passkey-svelte",
"version": "0.0.60",
"version": "0.0.61",
"type": "module",
"private": true,
"scripts": {

View File

@@ -1,5 +1,13 @@
# minimal-auth-passkey
## 0.0.72
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.71
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "passkey",
"private": true,
"version": "0.0.71",
"version": "0.0.72",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,13 @@
# passphrase
## 0.0.69
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.68
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "passphrase",
"private": true,
"version": "0.0.68",
"version": "0.0.69",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,13 @@
# jazz-password-manager
## 0.0.93
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.92
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.92",
"version": "0.0.93",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,13 @@
# jazz-example-pets
## 0.0.191
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.190
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.190",
"version": "0.0.191",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,13 @@
# reactions
## 0.0.71
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.70
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "reactions",
"private": true,
"version": "0.0.70",
"version": "0.0.71",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,14 @@
# todo-vue
## 0.0.75
### Patch Changes
- Updated dependencies [3129982]
- jazz-browser@0.13.4
- jazz-tools@0.13.4
- jazz-vue@0.13.4
## 0.0.74
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "todo-vue",
"version": "0.0.74",
"version": "0.0.75",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,13 @@
# jazz-example-todo
## 0.0.190
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.0.189
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.189",
"version": "0.0.190",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,14 @@
# version-history
## 0.0.68
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
- jazz-inspector@0.13.4
## 0.0.67
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "version-history",
"private": true,
"version": "0.0.67",
"version": "0.0.68",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,14 @@
# jazz-auth-clerk
## 0.13.4
### Patch Changes
- 3129982: Rewrite the auth management making Clerk the source of truth. This fixes the logOut issues as well as making the logout work from the Clerk APIs. When the Clerk session expires now the user is correctly logged out from Jazz\
- Updated dependencies [3129982]
- jazz-browser@0.13.4
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,21 +1,22 @@
{
"name": "jazz-auth-clerk",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.13.2",
"jazz-browser": "workspace:0.13.3",
"jazz-tools": "workspace:0.13.3"
"jazz-browser": "workspace:0.13.4",
"jazz-tools": "workspace:0.13.4"
},
"scripts": {
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write",
"build": "rm -rf ./dist && tsc --sourceMap --outDir dist",
"test": "vitest --run --root ../../ --project jazz-auth-clerk",
"test:watch": "vitest --watch --root ../../ --project jazz-auth-clerk"
"test:watch": "vitest --watch --root ../../ --project jazz-auth-clerk",
"dev": " tsc --sourceMap --outDir dist --watch"
},
"devDependencies": {
"typescript": "~5.6.2"

View File

@@ -8,6 +8,7 @@ import { getClerkUsername } from "./getClerkUsername.js";
import {
ClerkCredentials,
MinimalClerkClient,
isClerkAuthStateEqual,
isClerkCredentials,
} from "./types.js";
@@ -37,13 +38,49 @@ export class JazzClerkAuth {
});
}
onClerkUserChange = async (clerkClient: Pick<MinimalClerkClient, "user">) => {
if (!clerkClient.user) {
static async initializeAuth(clerk: MinimalClerkClient) {
const secretStorage = new AuthSecretStorage();
if (!isClerkCredentials(clerk.user?.unsafeMetadata)) {
return;
}
await JazzClerkAuth.loadClerkAuthData(
clerk.user.unsafeMetadata,
secretStorage,
);
}
private isFirstCall = true;
registerListener(clerkClient: MinimalClerkClient) {
let previousUser: MinimalClerkClient["user"] | null =
clerkClient.user ?? null;
// Need to use addListener because the clerk user object is not updated when the user logs in
return clerkClient.addListener((event) => {
const user = (event as Pick<MinimalClerkClient, "user">).user ?? null;
if (!isClerkAuthStateEqual(previousUser, user) || this.isFirstCall) {
this.onClerkUserChange({ user });
previousUser = user;
this.isFirstCall = false;
}
});
}
onClerkUserChange = async (clerkClient: Pick<MinimalClerkClient, "user">) => {
const isAuthenticated = this.authSecretStorage.isAuthenticated;
// LogOut is driven by Clerk. The framework adapters will need to pass `logOutReplacement` to the `JazzProvider`
// to make the logOut work correctly.
if (!clerkClient.user) {
if (isAuthenticated) {
this.authSecretStorage.clear();
}
return;
}
if (isAuthenticated) return;
const clerkCredentials = clerkClient.user

View File

@@ -119,4 +119,153 @@ describe("JazzClerkAuth", () => {
});
});
});
describe("registerListener", () => {
function setupMockClerk(user: MinimalClerkClient["user"]) {
const listners = new Set<
(clerkClient: Pick<MinimalClerkClient, "user">) => void
>();
return {
client: {
user,
addListener: vi.fn((callback) => {
listners.add(callback);
return () => {
listners.delete(callback);
};
}),
} as unknown as MinimalClerkClient,
triggerUserChange: (user: unknown) => {
for (const listener of listners) {
listener({ user } as Pick<MinimalClerkClient, "user">);
}
},
};
}
it("should call onClerkUserChange on the first trigger", async () => {
const { client, triggerUserChange } = setupMockClerk(null);
const auth = new JazzClerkAuth(mockAuthenticate, authSecretStorage);
const onClerkUserChangeSpy = vi.spyOn(auth, "onClerkUserChange");
auth.registerListener(client);
triggerUserChange(null);
expect(onClerkUserChangeSpy).toHaveBeenCalledTimes(1);
});
it("should call onClerkUserChange when user changes", async () => {
const { client, triggerUserChange } = setupMockClerk(null);
const auth = new JazzClerkAuth(mockAuthenticate, authSecretStorage);
const onClerkUserChangeSpy = vi.spyOn(auth, "onClerkUserChange");
auth.registerListener(client);
triggerUserChange(null);
triggerUserChange({
unsafeMetadata: {
jazzAccountID: "test123",
jazzAccountSecret: "secret123",
jazzAccountSeed: [1, 2, 3],
},
});
expect(onClerkUserChangeSpy).toHaveBeenCalledTimes(2);
});
it("should not call onClerkUserChange when user is the same", async () => {
const { client, triggerUserChange } = setupMockClerk(null);
const auth = new JazzClerkAuth(mockAuthenticate, authSecretStorage);
const onClerkUserChangeSpy = vi.spyOn(auth, "onClerkUserChange");
auth.registerListener(client);
triggerUserChange({
unsafeMetadata: {
jazzAccountID: "test123",
jazzAccountSecret: "secret123",
jazzAccountSeed: [1, 2, 3],
},
});
triggerUserChange({
unsafeMetadata: {
jazzAccountID: "test123",
jazzAccountSecret: "secret123",
jazzAccountSeed: [1, 2, 3],
},
});
expect(onClerkUserChangeSpy).toHaveBeenCalledTimes(1);
});
it("should not call onClerkUserChange when user switches from undefined to null", async () => {
const { client, triggerUserChange } = setupMockClerk(null);
const auth = new JazzClerkAuth(mockAuthenticate, authSecretStorage);
const onClerkUserChangeSpy = vi.spyOn(auth, "onClerkUserChange");
auth.registerListener(client);
triggerUserChange(null);
triggerUserChange(undefined);
triggerUserChange(null);
expect(onClerkUserChangeSpy).toHaveBeenCalledTimes(1);
});
});
describe("initializeAuth", () => {
it("should load auth data when credentials exist", async () => {
const mockClerk = {
user: {
unsafeMetadata: {
jazzAccountID: "test123",
jazzAccountSecret: "secret123",
jazzAccountSeed: [1, 2, 3],
},
},
} as unknown as MinimalClerkClient;
await JazzClerkAuth.initializeAuth(mockClerk);
const storedAuth = await authSecretStorage.get();
expect(storedAuth).toEqual({
accountID: "test123",
accountSecret: "secret123",
secretSeed: new Uint8Array([1, 2, 3]),
provider: "clerk",
});
});
it("should do nothing when no credentials exist", async () => {
const mockClerk = {
user: {
unsafeMetadata: {},
},
} as unknown as MinimalClerkClient;
await JazzClerkAuth.initializeAuth(mockClerk);
const storedAuth = await authSecretStorage.get();
expect(storedAuth).toBeNull();
});
it("should do nothing when no user exists", async () => {
const mockClerk = {
user: null,
} as unknown as MinimalClerkClient;
await JazzClerkAuth.initializeAuth(mockClerk);
const storedAuth = await authSecretStorage.get();
expect(storedAuth).toBeNull();
});
});
});

View File

@@ -40,3 +40,13 @@ export function isClerkCredentials(
): data is ClerkCredentials {
return !!data && "jazzAccountID" in data && "jazzAccountSecret" in data;
}
export function isClerkAuthStateEqual(
previousUser: MinimalClerkClient["user"] | null | undefined,
newUser: MinimalClerkClient["user"] | null | undefined,
) {
const previousCredentials = isClerkCredentials(previousUser?.unsafeMetadata);
const newCredentials = isClerkCredentials(newUser?.unsafeMetadata);
return previousCredentials === newCredentials;
}

View File

@@ -1,5 +1,13 @@
# jazz-browser-media-images
## 0.13.4
### Patch Changes
- Updated dependencies [3129982]
- jazz-browser@0.13.4
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-media-images",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -8,8 +8,8 @@
"dependencies": {
"@types/image-blob-reduce": "^4.1.1",
"image-blob-reduce": "^4.1.0",
"jazz-browser": "workspace:0.13.3",
"jazz-tools": "workspace:0.13.3",
"jazz-browser": "workspace:0.13.4",
"jazz-tools": "workspace:0.13.4",
"pica": "^9.0.1",
"typescript": "~5.6.2"
},

View File

@@ -1,5 +1,13 @@
# jazz-browser
## 0.13.4
### Patch Changes
- 3129982: Add logOutReplacement hook to replace the Jazz logout function with a custom one
- Updated dependencies [3129982]
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -17,6 +17,7 @@ export type JazzContextManagerProps<Acc extends Account> = {
guestMode?: boolean;
sync: SyncConfig;
onLogOut?: () => void;
logOutReplacement?: () => void;
onAnonymousAccountDiscarded?: (anonymousAccount: Acc) => Promise<void>;
storage?: BaseBrowserContextOptions["storage"];
AccountSchema?: AccountClass<Acc>;

View File

@@ -1,5 +1,18 @@
# jazz-browser
## 0.13.4
### Patch Changes
- 3129982: Rewrite the auth management making Clerk the source of truth. This fixes the logOut issues as well as making the logout work from the Clerk APIs. When the Clerk session expires now the user is correctly logged out from Jazz\
- 3129982: Add logOutReplacement hook to replace the Jazz logout function with a custom one
- Updated dependencies [3129982]
- Updated dependencies [3129982]
- jazz-auth-clerk@0.13.4
- jazz-react-native-core@0.13.4
- jazz-tools@0.13.4
- jazz-react-core@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-expo",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -1,6 +1,12 @@
import { JazzClerkAuth, type MinimalClerkClient } from "jazz-auth-clerk";
import { useEffect, useMemo } from "react";
import {
JazzClerkAuth,
type MinimalClerkClient,
isClerkCredentials,
} from "jazz-auth-clerk";
import { AuthSecretStorage, KvStoreContext } from "jazz-tools";
import { useEffect, useMemo, useState } from "react";
import {
ExpoSecureStoreAdapter,
JazzProvider,
JazzProviderProps,
useAuthSecretStorage,
@@ -20,10 +26,7 @@ function useJazzClerkAuth(clerk: MinimalClerkClient) {
}, []);
useEffect(() => {
// Need to use addListener because the clerk user object is not updated when the user logs in
return clerk.addListener((event) => {
authMethod.onClerkUserChange(event as Pick<MinimalClerkClient, "user">);
});
return authMethod.registerListener(clerk);
}, []);
}
@@ -37,9 +40,34 @@ function RegisterClerkAuth(props: {
export const JazzProviderWithClerk = (
props: { clerk: MinimalClerkClient } & JazzProviderProps,
): JSX.Element => {
): JSX.Element | null => {
const [isLoaded, setIsLoaded] = useState(false);
/**
* This effect ensures that a logged-in Clerk user is authenticated before the JazzProvider is mounted.
*
* This is done to optimize the initial load.
*/
useEffect(() => {
KvStoreContext.getInstance().initialize(
props.kvStore ?? new ExpoSecureStoreAdapter(),
);
JazzClerkAuth.initializeAuth(props.clerk)
.then(() => {
setIsLoaded(true);
})
.catch((error) => {
console.error("error initializing auth", error);
});
}, []);
if (!isLoaded) {
return null;
}
return (
<JazzProvider {...props} onLogOut={props.clerk.signOut}>
<JazzProvider {...props} logOutReplacement={props.clerk.signOut}>
<RegisterClerkAuth clerk={props.clerk}>
{props.children}
</RegisterClerkAuth>

View File

@@ -1,5 +1,13 @@
# jazz-inspector
## 0.13.4
### Patch Changes
- Updated dependencies [3129982]
- jazz-tools@0.13.4
- jazz-react-core@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-inspector",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "./dist/app.js",
"types": "./dist/app.d.ts",

View File

@@ -1,5 +1,12 @@
# jazz-autosub
## 0.13.4
### Patch Changes
- Updated dependencies [3129982]
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -5,7 +5,7 @@
"types": "dist/index.d.ts",
"type": "module",
"license": "MIT",
"version": "0.13.3",
"version": "0.13.4",
"dependencies": {
"cojson": "workspace:*",
"cojson-transport-ws": "workspace:*",

View File

@@ -1,5 +1,17 @@
# jazz-browser-media-images
## 0.13.4
### Patch Changes
- 3129982: Rewrite the auth management making Clerk the source of truth. This fixes the logOut issues as well as making the logout work from the Clerk APIs. When the Clerk session expires now the user is correctly logged out from Jazz\
- Updated dependencies [3129982]
- Updated dependencies [3129982]
- jazz-auth-clerk@0.13.4
- jazz-browser@0.13.4
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-auth-clerk",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -18,7 +18,8 @@
"scripts": {
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write",
"build": "rm -rf ./dist && tsc --sourceMap --outDir dist"
"build": "rm -rf ./dist && tsc --sourceMap --outDir dist",
"dev": " tsc --sourceMap --outDir dist --watch"
},
"devDependencies": {
"@testing-library/react": "16.2.0",

View File

@@ -1,8 +1,4 @@
import {
JazzClerkAuth,
type MinimalClerkClient,
isClerkCredentials,
} from "jazz-auth-clerk";
import { JazzClerkAuth, type MinimalClerkClient } from "jazz-auth-clerk";
import { LocalStorageKVStore } from "jazz-browser";
import {
JazzProvider,
@@ -10,7 +6,7 @@ import {
useAuthSecretStorage,
useJazzContext,
} from "jazz-react";
import { AuthSecretStorage, InMemoryKVStore, KvStoreContext } from "jazz-tools";
import { InMemoryKVStore, KvStoreContext } from "jazz-tools";
import { useEffect, useMemo, useState } from "react";
function useJazzClerkAuth(clerk: MinimalClerkClient) {
@@ -26,10 +22,7 @@ function useJazzClerkAuth(clerk: MinimalClerkClient) {
}, []);
useEffect(() => {
// Need to use addListener because the clerk user object is not updated when the user logs in
return clerk.addListener((event) => {
authMethod.onClerkUserChange(event as Pick<MinimalClerkClient, "user">);
});
return authMethod.registerListener(clerk);
}, []);
}
@@ -46,19 +39,16 @@ export const JazzProviderWithClerk = (
props: { clerk: MinimalClerkClient } & JazzProviderProps,
) => {
const [isLoaded, setIsLoaded] = useState(false);
setupKvStore();
const secretStorage = new AuthSecretStorage();
/**
* This effect ensures that a logged-in Clerk user is authenticated before the JazzProvider is mounted.
*
* This is done to optimize the initial load.
*/
useEffect(() => {
if (!isClerkCredentials(props.clerk.user?.unsafeMetadata)) {
setIsLoaded(true);
return;
}
setupKvStore();
JazzClerkAuth.loadClerkAuthData(
props.clerk.user.unsafeMetadata,
secretStorage,
).then(() => {
JazzClerkAuth.initializeAuth(props.clerk).then(() => {
setIsLoaded(true);
});
}, []);
@@ -68,7 +58,7 @@ export const JazzProviderWithClerk = (
}
return (
<JazzProvider {...props} onLogOut={props.clerk.signOut}>
<JazzProvider {...props} logOutReplacement={props.clerk.signOut}>
<RegisterClerkAuth clerk={props.clerk}>
{props.children}
</RegisterClerkAuth>

View File

@@ -1,165 +0,0 @@
// @vitest-environment happy-dom
import { act, render, waitFor } from "@testing-library/react";
import { JazzClerkAuth, type MinimalClerkClient } from "jazz-auth-clerk";
import { AuthSecretStorage, InMemoryKVStore, KvStoreContext } from "jazz-tools";
import { MockInstance, beforeEach, describe, expect, it, vi } from "vitest";
import { JazzProviderWithClerk } from "../index";
vi.mock("jazz-react", async (importOriginal) => {
const { JazzTestProvider, createJazzTestAccount } = await import(
"jazz-react/testing"
);
const account = await createJazzTestAccount({
isCurrentActiveAccount: true,
});
function JazzProvider(props: { children: React.ReactNode }) {
return (
<JazzTestProvider account={account}>{props.children}</JazzTestProvider>
);
}
return {
...(await importOriginal<typeof import("jazz-react")>()),
JazzProvider,
};
});
vi.mock("jazz-auth-clerk", async (importOriginal) => {
const { JazzClerkAuth } = await import("jazz-auth-clerk");
JazzClerkAuth.loadClerkAuthData = vi.fn().mockResolvedValue(undefined);
return {
...(await importOriginal<typeof import("jazz-auth-clerk")>()),
JazzClerkAuth,
};
});
const authSecretStorage = new AuthSecretStorage();
KvStoreContext.getInstance().initialize(new InMemoryKVStore());
describe("JazzProviderWithClerk", () => {
beforeEach(async () => {
await authSecretStorage.clear();
vi.clearAllMocks();
});
const setup = (
children = <div data-testid="test-child">Test Content</div>,
) => {
let callbacks = new Set<(clerk: MinimalClerkClient) => void>();
const mockClerk = {
user: {
fullName: "Test User",
unsafeMetadata: {},
update: vi.fn(),
},
signOut: vi.fn().mockImplementation(() => {
mockClerk.user = null;
Array.from(callbacks).map((callback) => callback(mockClerk));
}),
addListener: vi.fn((callback) => {
callbacks.add(callback);
return () => {
callbacks.delete(callback);
};
}),
} as unknown as MinimalClerkClient;
const utils = render(
<JazzProviderWithClerk
clerk={mockClerk}
sync={{ peer: "wss://test.jazz.tools" }}
>
{children}
</JazzProviderWithClerk>,
);
return {
...utils,
mockClerk,
callbacks,
};
};
it("should push the local credentials to clerk", async () => {
const { mockClerk, callbacks } = setup();
expect(mockClerk.user?.update).not.toHaveBeenCalled();
await act(async () => {
await Promise.all(
Array.from(callbacks).map((callback) => callback(mockClerk)),
);
});
expect(mockClerk.user?.update).toHaveBeenCalledWith({
unsafeMetadata: {
jazzAccountID: expect.any(String),
jazzAccountSecret: expect.any(String),
jazzAccountSeed: expect.any(Array),
},
});
});
it("should load the clerk credentials when the user is authenticated", async () => {
render(
<JazzProviderWithClerk
clerk={{
addListener: vi.fn(),
signOut: vi.fn(),
user: {
update: vi.fn(),
unsafeMetadata: {
jazzAccountID: "test",
jazzAccountSecret: "test",
jazzAccountSeed: "test",
},
firstName: "Test",
lastName: "User",
username: "test",
fullName: "Test User",
id: "test",
primaryEmailAddress: {
emailAddress: "test@test.com",
},
},
}}
sync={{ peer: "wss://test.jazz.tools" }}
>
<div data-testid="test-child">Test Content</div>
</JazzProviderWithClerk>,
);
expect(JazzClerkAuth.loadClerkAuthData).toHaveBeenCalledWith(
{
jazzAccountID: "test",
jazzAccountSecret: "test",
jazzAccountSeed: "test",
},
authSecretStorage,
);
});
it("should not load the clerk credentials when the user is not authenticated", async () => {
render(
<JazzProviderWithClerk
clerk={{
addListener: vi.fn(),
signOut: vi.fn(),
user: null,
}}
sync={{ peer: "wss://test.jazz.tools" }}
>
<div data-testid="test-child">Test Content</div>
</JazzProviderWithClerk>,
);
expect(JazzClerkAuth.loadClerkAuthData).not.toHaveBeenCalledWith();
});
});

View File

@@ -1,5 +1,12 @@
# jazz-react-core
## 0.13.4
### Patch Changes
- Updated dependencies [3129982]
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-core",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -1,5 +1,14 @@
# jazz-browser
## 0.13.4
### Patch Changes
- 3129982: Add logOutReplacement hook to replace the Jazz logout function with a custom one
- Updated dependencies [3129982]
- jazz-tools@0.13.4
- jazz-react-core@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-react-native-core",
"type": "module",
"version": "0.13.3",
"version": "0.13.4",
"license": "MIT",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -17,6 +17,7 @@ export type JazzContextManagerProps<Acc extends Account> = {
guestMode?: boolean;
sync: SyncConfig;
onLogOut?: () => void;
logOutReplacement?: () => void;
storage?: BaseReactNativeContextOptions["storage"];
AccountSchema?: AccountClass<Acc>;
defaultProfileName?: string;

View File

@@ -28,6 +28,7 @@ export function JazzProviderCore<Acc extends Account = RegisteredAccount>({
kvStore,
onAnonymousAccountDiscarded,
CryptoProvider,
logOutReplacement,
}: JazzProviderProps<Acc>) {
setupKvStore(kvStore);
@@ -39,6 +40,9 @@ export function JazzProviderCore<Acc extends Account = RegisteredAccount>({
onAnonymousAccountDiscarded,
);
const onLogOutRefCallback = useRefCallback(onLogOut);
const logOutReplacementRefCallback = useRefCallback(logOutReplacement);
const logoutReplacementActiveRef = useRef(false);
logoutReplacementActiveRef.current = Boolean(logOutReplacement);
const value = React.useSyncExternalStore<JazzContextType<Acc> | undefined>(
React.useCallback(
@@ -50,6 +54,9 @@ export function JazzProviderCore<Acc extends Account = RegisteredAccount>({
storage,
defaultProfileName,
onLogOut: onLogOutRefCallback,
logOutReplacement: logoutReplacementActiveRef.current
? logOutReplacementRefCallback
: undefined,
onAnonymousAccountDiscarded: onAnonymousAccountDiscardedRefCallback,
CryptoProvider,
};

View File

@@ -1,5 +1,12 @@
# jazz-browser-media-images
## 0.13.4
### Patch Changes
- Updated dependencies [3129982]
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native-media-images",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -1,5 +1,13 @@
# jazz-browser
## 0.13.4
### Patch Changes
- Updated dependencies [3129982]
- jazz-react-native-core@0.13.4
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -1,5 +1,16 @@
# jazz-react
## 0.13.4
### Patch Changes
- 3129982: Add logOutReplacement hook to replace the Jazz logout function with a custom one
- Updated dependencies [3129982]
- jazz-browser@0.13.4
- jazz-tools@0.13.4
- jazz-browser-media-images@0.13.4
- jazz-react-core@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react",
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -18,10 +18,10 @@
"dependencies": {
"@scure/bip39": "^1.3.0",
"cojson": "workspace:0.13.2",
"jazz-browser-media-images": "workspace:0.13.3",
"jazz-browser": "workspace:0.13.3",
"jazz-react-core": "workspace:0.13.3",
"jazz-tools": "workspace:0.13.3"
"jazz-browser-media-images": "workspace:0.13.4",
"jazz-browser": "workspace:0.13.4",
"jazz-react-core": "workspace:0.13.4",
"jazz-tools": "workspace:0.13.4"
},
"devDependencies": {
"@testing-library/dom": "^10.4.0",

View File

@@ -25,6 +25,7 @@ export function JazzProvider<Acc extends Account = RegisteredAccount>({
AccountSchema,
defaultProfileName,
onLogOut,
logOutReplacement,
onAnonymousAccountDiscarded,
}: JazzProviderProps<Acc>) {
const [contextManager] = React.useState(
@@ -32,9 +33,12 @@ export function JazzProvider<Acc extends Account = RegisteredAccount>({
);
const onLogOutRefCallback = useRefCallback(onLogOut);
const logOutReplacementRefCallback = useRefCallback(logOutReplacement);
const onAnonymousAccountDiscardedRefCallback = useRefCallback(
onAnonymousAccountDiscarded,
);
const logoutReplacementActiveRef = useRef(false);
logoutReplacementActiveRef.current = Boolean(logOutReplacement);
const value = React.useSyncExternalStore<JazzContextType<Acc> | undefined>(
React.useCallback(
@@ -46,6 +50,9 @@ export function JazzProvider<Acc extends Account = RegisteredAccount>({
storage,
defaultProfileName,
onLogOut: onLogOutRefCallback,
logOutReplacement: logoutReplacementActiveRef.current
? logOutReplacementRefCallback
: undefined,
onAnonymousAccountDiscarded: onAnonymousAccountDiscardedRefCallback,
} satisfies JazzContextManagerProps<Acc>;

View File

@@ -1,5 +1,12 @@
# jazz-run
## 0.13.4
### Patch Changes
- Updated dependencies [3129982]
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -3,7 +3,7 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.13.3",
"version": "0.13.4",
"exports": {
"./startSyncServer": {
"import": "./dist/startSyncServer.js",
@@ -31,7 +31,7 @@
"cojson-storage-sqlite": "workspace:0.13.2",
"cojson-transport-ws": "workspace:0.13.3",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.13.3",
"jazz-tools": "workspace:0.13.4",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -1,5 +1,13 @@
# jazz-svelte
## 0.13.4
### Patch Changes
- Updated dependencies [3129982]
- jazz-browser@0.13.4
- jazz-tools@0.13.4
## 0.13.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-svelte",
"version": "0.13.3",
"version": "0.13.4",
"scripts": {
"dev": "vite dev",
"build": "vite build && npm run package",

View File

@@ -1,5 +1,11 @@
# jazz-tools
## 0.13.4
### Patch Changes
- 3129982: Add logOutReplacement hook to replace the Jazz logout function with a custom one
## 0.13.3
### Patch Changes

View File

@@ -17,7 +17,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.13.3",
"version": "0.13.4",
"dependencies": {
"@scure/bip39": "^1.3.0",
"cojson": "workspace:*",

View File

@@ -15,6 +15,7 @@ export type JazzContextManagerAuthProps = {
export type JazzContextManagerBaseProps<Acc extends Account> = {
onAnonymousAccountDiscarded?: (anonymousAccount: Acc) => Promise<void>;
onLogOut?: () => void | Promise<unknown>;
logOutReplacement?: () => void | Promise<unknown>;
};
type PlatformSpecificAuthContext<Acc extends Account> = {
@@ -136,8 +137,13 @@ export class JazzContextManager<
}
await this.props.onLogOut?.();
await this.context.logOut();
return this.createContext(this.props);
if (this.props.logOutReplacement) {
await this.props.logOutReplacement();
} else {
await this.context.logOut();
return this.createContext(this.props);
}
};
done = () => {

Some files were not shown because too many files have changed in this diff Show More