Compare commits
25 Commits
cojson-sto
...
styling/pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b11018da81 | ||
|
|
3311828432 | ||
|
|
f03f8dfeaa | ||
|
|
05e0b3d795 | ||
|
|
7ce08753f1 | ||
|
|
01e9255d3b | ||
|
|
0e918f60e3 | ||
|
|
b08c5ec1d2 | ||
|
|
87f24fa985 | ||
|
|
86df2f0132 | ||
|
|
0e1654a474 | ||
|
|
05f447baae | ||
|
|
85e2bb230e | ||
|
|
123a57169a | ||
|
|
b0101f8157 | ||
|
|
1e94004364 | ||
|
|
76e7a81eef | ||
|
|
1445e4ab80 | ||
|
|
6c8534a709 | ||
|
|
508720745f | ||
|
|
2fde6a47aa | ||
|
|
22709e6fe0 | ||
|
|
4ee3f7229c | ||
|
|
09e0e17559 | ||
|
|
7b6656e99e |
2
.github/workflows/unit-test.yml
vendored
@@ -44,4 +44,4 @@ jobs:
|
||||
run: pnpm turbo build
|
||||
|
||||
- name: Unit Tests
|
||||
run: pnpm test
|
||||
run: pnpm test:ci
|
||||
|
||||
1
examples/chat-rn-clerk/.env
Normal file
@@ -0,0 +1 @@
|
||||
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
|
||||
4
examples/chat-rn-clerk/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// https://docs.expo.dev/guides/using-eslint/
|
||||
module.exports = {
|
||||
extends: 'expo',
|
||||
};
|
||||
17
examples/chat-rn-clerk/.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
node_modules/
|
||||
.expo/
|
||||
dist/
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
ios
|
||||
android
|
||||
76
examples/chat-rn-clerk/README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
<p align="center">
|
||||
<a href="https://clerk.com?utm_source=github&utm_medium=clerk_docs" target="_blank" rel="noopener noreferrer">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./assets/images/light-logo.png">
|
||||
<img alt="Clerk Logo for light background" src="./assets/images/dark-logo.png" height="64">
|
||||
</picture>
|
||||
</a>
|
||||
<br />
|
||||
</p>
|
||||
<div align="center">
|
||||
<h1>
|
||||
Clerk and Expo Quickstart
|
||||
</h1>
|
||||
<a href="https://www.npmjs.com/package/@clerk/clerk-js">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@clerk/clerk-js" />
|
||||
</a>
|
||||
<a href="https://discord.com/invite/b5rXHjAg7A">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/856971667393609759?color=7389D8&label&logo=discord&logoColor=ffffff" />
|
||||
</a>
|
||||
<a href="https://twitter.com/clerkdev">
|
||||
<img alt="Twitter" src="https://img.shields.io/twitter/url.svg?label=%40clerkdev&style=social&url=https%3A%2F%2Ftwitter.com%2Fclerkdev" />
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
<img alt="Clerk Hero Image" src="./assets/images/hero.png">
|
||||
</div>
|
||||
|
||||
## Introduction
|
||||
|
||||
Clerk is a developer-first authentication and user management solution. It provides pre-built React components and hooks for sign-in, sign-up, user profile, and organization management. Clerk is designed to be easy to use and customize, and can be dropped into any React or Next.js application.
|
||||
|
||||
After following the quickstart you'll have learned how to:
|
||||
|
||||
- Install `@clerk/clerk-expo`
|
||||
- Setup your environment key
|
||||
- Wrap your Expo app in `<ClerkProvider />` and supply your `tokenCache`
|
||||
- Conditionally show content based on your auth state
|
||||
- Build your sign-in and sign-up pages
|
||||
|
||||
## Running the template
|
||||
|
||||
```bash
|
||||
git clone https://github.com/clerk/clerk-expo-quickstart
|
||||
```
|
||||
|
||||
To run the example locally, you'll need to make sure you have XCode installed and configured properly, then:
|
||||
|
||||
1. Sign up for a Clerk account at [https://clerk.com](https://dashboard.clerk.com/sign-up?utm_source=DevRel&utm_medium=docs&utm_campaign=templates&utm_content=10-24-2023&utm_term=clerk-expo-quickstart).
|
||||
|
||||
2. Go to the [Clerk dashboard](https://dashboard.clerk.com?utm_source=DevRel&utm_medium=docs&utm_campaign=templates&utm_content=10-24-2023&utm_term=clerk-expo-quickstart) and create an application.
|
||||
|
||||
3. Set the required Clerk environment variable as shown in [the example `env` file](./.env.example).
|
||||
|
||||
4. `npm install` the required dependencies.
|
||||
|
||||
5. `npm run start` to launch the development server.
|
||||
|
||||
## Learn more
|
||||
|
||||
To learn more about Clerk and Expo, check out the following resources:
|
||||
|
||||
- [Quickstart: Get started with Expo and Clerk](https://clerk.com/docs/quickstarts/expo?utm_source=DevRel&utm_medium=docs&utm_campaign=templates&utm_content=10-24-2023&utm_term=clerk-expo-quickstart)
|
||||
|
||||
- [Clerk Documentation](https://clerk.com/docs/references/expo/overview?utm_source=DevRel&utm_medium=docs&utm_campaign=templates&utm_content=10-24-2023&utm_term=clerk-expo-quickstart)
|
||||
|
||||
- [Expo Documentation](https://docs.expo.dev/)
|
||||
|
||||
## Found an issue or want to leave feedback
|
||||
|
||||
Feel free to create a support thread on our [Discord](https://clerk.com/discord). Our support team will be happy to assist you in the `#support` channel.
|
||||
|
||||
## Connect with us
|
||||
|
||||
You can discuss ideas, ask questions, and meet others from the community in our [Discord](https://discord.com/invite/b5rXHjAg7A).
|
||||
|
||||
If you prefer, you can also find support through our [Twitter](https://twitter.com/ClerkDev), or you can [email](mailto:support@clerk.dev) us!
|
||||
45
examples/chat-rn-clerk/app.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "jazz-chat-rn-clerk",
|
||||
"scheme": "jazz-chat-rn-clerk",
|
||||
"slug": "jazz-chat-rn-clerk",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/images/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.jazz.chatrnclerk"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"package": "com.jazz.chatrnclerk"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"ios": {
|
||||
"newArchEnabled": true
|
||||
},
|
||||
"android": {
|
||||
"newArchEnabled": true
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "ca3d46e5-a10a-47ec-9d77-3b841e1c62d4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
examples/chat-rn-clerk/app/(app)/_layout.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { useAuth } from "../../src/auth-context";
|
||||
import { Redirect, Stack } from "expo-router";
|
||||
|
||||
export default function HomeLayout() {
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Redirect href={"/chat"} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{ headerShown: false, headerBackVisible: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
33
examples/chat-rn-clerk/app/(app)/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import { SignedOut } from "@clerk/clerk-expo";
|
||||
import { Link } from "expo-router";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-100 p-6">
|
||||
<SignedOut>
|
||||
<View className="bg-white p-6 rounded-lg shadow-lg w-11/12 max-w-md">
|
||||
<Text className="text-2xl font-bold text-center text-gray-900 mb-4">
|
||||
Jazz 🤝 Clerk 🤝 Expo
|
||||
</Text>
|
||||
<Link href="/sign-in" className="mb-4">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign In
|
||||
</Text>
|
||||
</Link>
|
||||
<Link href="/sign-in-oauth" className="mb-4">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign In OAuth
|
||||
</Text>
|
||||
</Link>
|
||||
<Link href="/sign-up">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign Up
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</SignedOut>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
20
examples/chat-rn-clerk/app/(auth)/_layout.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Redirect, Stack } from "expo-router";
|
||||
import { useAuth } from "../../src/auth-context";
|
||||
|
||||
export default function UnAuthenticatedLayout() {
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Redirect href={"/chat"} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerShown: true,
|
||||
headerBackVisible: true,
|
||||
headerTitle: "",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
65
examples/chat-rn-clerk/app/(auth)/sign-in-oauth.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React from "react";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import { Text, View, TouchableOpacity } from "react-native";
|
||||
import { Link } from "expo-router";
|
||||
import { useOAuth } from "@clerk/clerk-expo";
|
||||
import * as Linking from "expo-linking";
|
||||
|
||||
export const useWarmUpBrowser = () => {
|
||||
React.useEffect(() => {
|
||||
// Warm up the android browser to improve UX
|
||||
// https://docs.expo.dev/guides/authentication/#improving-user-experience
|
||||
void WebBrowser.warmUpAsync();
|
||||
return () => {
|
||||
void WebBrowser.coolDownAsync();
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
|
||||
const SignInWithOAuth = () => {
|
||||
useWarmUpBrowser();
|
||||
|
||||
const { startOAuthFlow } = useOAuth({ strategy: "oauth_google" });
|
||||
|
||||
const onPress = React.useCallback(async () => {
|
||||
try {
|
||||
const { createdSessionId, signIn, signUp, setActive } =
|
||||
await startOAuthFlow({
|
||||
redirectUrl: Linking.createURL("/", {
|
||||
scheme: "jazz-chat-rn-clerk",
|
||||
}),
|
||||
});
|
||||
|
||||
if (createdSessionId) {
|
||||
setActive!({ session: createdSessionId });
|
||||
} else {
|
||||
// Use signIn or signUp for next steps such as MFA
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("OAuth error", err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
|
||||
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-lg items-center">
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
className="w-full bg-red-500 py-3 rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Sign in with Google
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<Link href="/" className="mt-4">
|
||||
<Text className="text-blue-600 text-lg font-semibold underline mb-6">
|
||||
Back to Home
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
export default SignInWithOAuth;
|
||||
91
examples/chat-rn-clerk/app/(auth)/sign-in.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { useSignIn } from "@clerk/clerk-expo";
|
||||
import { Text, TextInput, View, TouchableOpacity } from "react-native";
|
||||
import React from "react";
|
||||
import { Link } from "expo-router";
|
||||
|
||||
export default function SignInPage() {
|
||||
const { signIn, setActive, isLoaded } = useSignIn();
|
||||
const [emailAddress, setEmailAddress] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [errorMessage, setErrorMessage] = React.useState("");
|
||||
|
||||
const onSignInPress = React.useCallback(async () => {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
setErrorMessage("");
|
||||
|
||||
try {
|
||||
const signInAttempt = await signIn.create({
|
||||
identifier: emailAddress,
|
||||
password,
|
||||
});
|
||||
|
||||
if (signInAttempt.status === "complete") {
|
||||
await setActive({ session: signInAttempt.createdSessionId });
|
||||
} else {
|
||||
console.error(JSON.stringify(signInAttempt, null, 2));
|
||||
setErrorMessage("Invalid credentials. Please try again.");
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
if (err.errors && err.errors[0]?.message) {
|
||||
setErrorMessage(err.errors[0].message);
|
||||
} else {
|
||||
setErrorMessage(
|
||||
"An unexpected error occurred. Please try again.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [isLoaded, emailAddress, password]);
|
||||
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
|
||||
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-md">
|
||||
<Text className="text-3xl font-bold text-center text-gray-800 mb-6">
|
||||
Sign In
|
||||
</Text>
|
||||
{errorMessage ? (
|
||||
<Text className="text-red-500 text-center mb-4">
|
||||
{errorMessage}
|
||||
</Text>
|
||||
) : null}
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
value={emailAddress}
|
||||
placeholder="Email..."
|
||||
onChangeText={(emailAddress) =>
|
||||
setEmailAddress(emailAddress)
|
||||
}
|
||||
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TextInput
|
||||
value={password}
|
||||
placeholder="Password..."
|
||||
secureTextEntry={true}
|
||||
onChangeText={(password) => setPassword(password)}
|
||||
className="w-full h-12 mb-6 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={onSignInPress}
|
||||
className="w-full h-12 bg-blue-600 rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Sign In
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<View className="flex-row items-center justify-center mt-4">
|
||||
<Text className="text-gray-600">
|
||||
Don't have an account?
|
||||
</Text>
|
||||
<Link href="/sign-up">
|
||||
<Text className="text-blue-500 ml-2 font-semibold">
|
||||
Sign up
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
128
examples/chat-rn-clerk/app/(auth)/sign-up.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import * as React from "react";
|
||||
import { TextInput, View, Text, TouchableOpacity } from "react-native";
|
||||
import { useSignUp } from "@clerk/clerk-expo";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
export default function SignUpPage() {
|
||||
const { isLoaded, signUp, setActive } = useSignUp();
|
||||
|
||||
const [emailAddress, setEmailAddress] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [pendingVerification, setPendingVerification] = React.useState(false);
|
||||
const [code, setCode] = React.useState("");
|
||||
const [errorMessage, setErrorMessage] = React.useState("");
|
||||
const navigation = useNavigation();
|
||||
|
||||
const onSignUpPress = async () => {
|
||||
if (!isLoaded) return;
|
||||
|
||||
setErrorMessage("");
|
||||
|
||||
try {
|
||||
await signUp.create({
|
||||
emailAddress,
|
||||
password,
|
||||
});
|
||||
|
||||
await signUp.prepareEmailAddressVerification({
|
||||
strategy: "email_code",
|
||||
});
|
||||
|
||||
setPendingVerification(true);
|
||||
} catch (err: any) {
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
if (err.errors && err.errors[0]?.message) {
|
||||
setErrorMessage(err.errors[0].message);
|
||||
} else {
|
||||
setErrorMessage(
|
||||
"An unexpected error occurred. Please try again.",
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPressVerify = async () => {
|
||||
if (!isLoaded) return;
|
||||
|
||||
setErrorMessage("");
|
||||
|
||||
try {
|
||||
const completeSignUp = await signUp.attemptEmailAddressVerification(
|
||||
{
|
||||
code,
|
||||
},
|
||||
);
|
||||
|
||||
if (completeSignUp.status === "complete") {
|
||||
await setActive({ session: completeSignUp.createdSessionId });
|
||||
} else {
|
||||
console.error(JSON.stringify(completeSignUp, null, 2));
|
||||
setErrorMessage("Failed to verify. Please check your code.");
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
setErrorMessage("Invalid verification code. Please try again.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
|
||||
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-lg">
|
||||
<Text className="text-3xl font-bold text-center text-gray-800 mb-6">
|
||||
{pendingVerification ? "Verify Email" : "Sign Up"}
|
||||
</Text>
|
||||
{errorMessage ? (
|
||||
<Text className="text-red-500 text-center mb-4">
|
||||
{errorMessage}
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
{!pendingVerification && (
|
||||
<>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
value={emailAddress}
|
||||
placeholder="Email..."
|
||||
onChangeText={(email) => setEmailAddress(email)}
|
||||
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TextInput
|
||||
value={password}
|
||||
placeholder="Password..."
|
||||
secureTextEntry={true}
|
||||
onChangeText={(password) => setPassword(password)}
|
||||
className="w-full h-12 mb-6 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={onSignUpPress}
|
||||
className="w-full h-12 bg-blue-600 rounded-lg flex justify-center items-center mb-4"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Sign Up
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
|
||||
{pendingVerification && (
|
||||
<>
|
||||
<TextInput
|
||||
value={code}
|
||||
placeholder="Verification Code..."
|
||||
onChangeText={(code) => setCode(code)}
|
||||
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={onPressVerify}
|
||||
className="w-full h-12 bg-green-600 rounded-lg flex justify-center items-center mb-4"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Verify Email
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
44
examples/chat-rn-clerk/app/+html.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { ScrollViewStyleReset } from "expo-router/html";
|
||||
import { type PropsWithChildren } from "react";
|
||||
|
||||
/**
|
||||
* This file is web-only and used to configure the root HTML for every web page during static rendering.
|
||||
* The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs.
|
||||
*/
|
||||
export default function Root({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
|
||||
{/*
|
||||
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
|
||||
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
|
||||
*/}
|
||||
<ScrollViewStyleReset />
|
||||
|
||||
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
|
||||
<style
|
||||
dangerouslySetInnerHTML={{ __html: responsiveBackground }}
|
||||
/>
|
||||
{/* Add any additional <head> elements that you want globally available on web... */}
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
const responsiveBackground = `
|
||||
body {
|
||||
background-color: #fff;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
}`;
|
||||
29
examples/chat-rn-clerk/app/+not-found.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Link, Stack } from "expo-router";
|
||||
import { StyleSheet, View, Text } from "react-native";
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: "Oops!" }} />
|
||||
<View style={styles.container}>
|
||||
<Text>This screen doesn't exist.</Text>
|
||||
<Link href="/" style={styles.link}>
|
||||
<Text>Go to home screen!</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: 20,
|
||||
},
|
||||
link: {
|
||||
marginTop: 15,
|
||||
paddingVertical: 15,
|
||||
},
|
||||
});
|
||||
43
examples/chat-rn-clerk/app/_layout.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useFonts } from "expo-font";
|
||||
import { ClerkLoaded, ClerkProvider } from "@clerk/clerk-expo";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
import React, { useEffect } from "react";
|
||||
import { Slot } from "expo-router";
|
||||
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"),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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}>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
</JazzAndAuth>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
);
|
||||
}
|
||||
173
examples/chat-rn-clerk/app/chat/[chatId].tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import React, {
|
||||
SafeAreaView,
|
||||
View,
|
||||
Text,
|
||||
Alert,
|
||||
TouchableOpacity,
|
||||
FlatList,
|
||||
KeyboardAvoidingView,
|
||||
TextInput,
|
||||
Button,
|
||||
} from "react-native";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState, useEffect, useLayoutEffect } from "react";
|
||||
import { useAccount, useCoState } from "@/src/jazz";
|
||||
import { Chat, Message } from "@/src/schema";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import clsx from "clsx";
|
||||
import { useNavigation, useFocusEffect } from "@react-navigation/native";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
|
||||
export default function Conversation() {
|
||||
const { chatId } = useLocalSearchParams();
|
||||
const { me } = useAccount();
|
||||
const [chat, setChat] = useState<Chat>();
|
||||
const [message, setMessage] = useState("");
|
||||
const loadedChat = useCoState(Chat, chat?.id, [{}]);
|
||||
const navigation = useNavigation();
|
||||
|
||||
useEffect(() => {
|
||||
if (chat) return;
|
||||
if (chatId === "new") {
|
||||
createChat();
|
||||
} else {
|
||||
loadChat(chatId as ID<Chat>);
|
||||
}
|
||||
}, [chat]);
|
||||
|
||||
// Effect to dynamically set header options
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerTitle: "Chat",
|
||||
headerRight: () =>
|
||||
chat ? (
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (chat?.id) {
|
||||
Clipboard.setStringAsync(
|
||||
`https://chat.jazz.tools/#/chat/${chat.id}`,
|
||||
);
|
||||
Alert.alert(
|
||||
"Copied to clipboard",
|
||||
`Chat ID: ${chat.id}`,
|
||||
);
|
||||
}
|
||||
}}
|
||||
title="Share"
|
||||
/>
|
||||
) : null,
|
||||
});
|
||||
}, [navigation, chat]);
|
||||
|
||||
const createChat = () => {
|
||||
const group = Group.create({ owner: me });
|
||||
group.addMember("everyone", "writer");
|
||||
const chat = Chat.create([], { owner: group });
|
||||
setChat(chat);
|
||||
};
|
||||
|
||||
const loadChat = async (chatId: ID<Chat>) => {
|
||||
try {
|
||||
const chat = await Chat.load(chatId, me, []);
|
||||
setChat(chat);
|
||||
} catch (error) {
|
||||
console.log("Error loading chat", error);
|
||||
Alert.alert("Error", `Error loading chat: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!chat) return;
|
||||
if (message.trim()) {
|
||||
chat.push(
|
||||
Message.create({ text: message }, { owner: chat._owner }),
|
||||
);
|
||||
setMessage("");
|
||||
}
|
||||
};
|
||||
|
||||
const renderMessageItem = ({ item }: { item: Message }) => {
|
||||
const isMe = item._edits.text.by?.isMe;
|
||||
return (
|
||||
<View
|
||||
className={clsx(
|
||||
`rounded-xl px-3 py-2 max-w-[75%] my-1`,
|
||||
isMe ? `bg-blue-500 self-end` : `bg-gray-200 self-start`,
|
||||
)}
|
||||
>
|
||||
{!isMe ? (
|
||||
<Text
|
||||
className={clsx(
|
||||
`text-xs text-gray-500 mb-1`,
|
||||
isMe ? "text-right" : "text-left",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.by?.profile?.name}
|
||||
</Text>
|
||||
) : null}
|
||||
<View
|
||||
className={clsx(
|
||||
"flex relative items-end justify-between",
|
||||
isMe ? "flex-row" : "flex-row",
|
||||
)}
|
||||
>
|
||||
<Text
|
||||
className={clsx(
|
||||
!isMe ? "text-black" : "text-gray-200",
|
||||
`text-md max-w-[85%]`,
|
||||
)}
|
||||
>
|
||||
{item.text}
|
||||
</Text>
|
||||
<Text
|
||||
className={clsx(
|
||||
"text-[10px] text-right ml-2",
|
||||
!isMe ? "mt-2 text-gray-500" : "mt-1 text-gray-200",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.madeAt.getHours()}:
|
||||
{item._edits.text.madeAt.getMinutes()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-gray-50">
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 8,
|
||||
}}
|
||||
className="flex"
|
||||
data={loadedChat}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderMessageItem}
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
keyboardVerticalOffset={110}
|
||||
behavior="padding"
|
||||
className="p-3 bg-white border-t border-gray-300"
|
||||
>
|
||||
<SafeAreaView className="flex-row items-center gap-2">
|
||||
<TextInput
|
||||
className="flex-1 rounded-full h-10 px-4 bg-gray-100 border border-gray-300 focus:border-blue-500 focus:bg-white"
|
||||
value={message}
|
||||
onChangeText={setMessage}
|
||||
placeholder="Type a message..."
|
||||
textAlignVertical="center"
|
||||
onSubmitEditing={sendMessage}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={sendMessage}
|
||||
className="bg-blue-500 rounded-full h-10 w-10 items-center justify-center"
|
||||
>
|
||||
<Text className="text-white text-xl">↑</Text>
|
||||
</TouchableOpacity>
|
||||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
14
examples/chat-rn-clerk/app/chat/_layout.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function ChatLayout() {
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerShown: true,
|
||||
headerBackVisible: true,
|
||||
headerTitle: "",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
85
examples/chat-rn-clerk/app/chat/index.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { useLayoutEffect } from "react";
|
||||
import React, {
|
||||
Button,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
Alert,
|
||||
} from "react-native";
|
||||
import { ID } from "jazz-tools";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
import { Chat } from "../../src/schema";
|
||||
import { useAccount } from "../../src/jazz";
|
||||
import { useUser } from "@clerk/clerk-expo";
|
||||
|
||||
export default function ChatScreen() {
|
||||
const { logOut } = useAccount();
|
||||
const router = useRouter();
|
||||
const navigation = useNavigation();
|
||||
const { user } = useUser();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerTitle: "Chat",
|
||||
headerRight: () => <Button onPress={logOut} title="Logout" />,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
const loadChat = async (chatId: ID<Chat> | "new") => {
|
||||
router.navigate(`/chat/${chatId}`);
|
||||
};
|
||||
|
||||
const joinChat = () => {
|
||||
Alert.prompt(
|
||||
"Join Chat",
|
||||
"Enter the Chat ID (example: co_zBGEHYvRfGuT2YSBraY3njGjnde)",
|
||||
[
|
||||
{
|
||||
text: "Cancel",
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Join",
|
||||
onPress: (chatId) => {
|
||||
if (chatId) {
|
||||
loadChat(chatId as ID<Chat>);
|
||||
} else {
|
||||
Alert.alert("Error", "Chat ID cannot be empty.");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
"plain-text",
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-gray-50">
|
||||
<View className="flex-1 justify-center items-center px-6">
|
||||
<View className="w-full max-w-sm bg-white p-8 rounded-lg shadow-lg">
|
||||
<Text className="text-xl font-semibold text-gray-800">
|
||||
Welcome, {user?.emailAddresses[0].emailAddress}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => loadChat("new")}
|
||||
className="w-full bg-blue-600 py-4 rounded-md mb-4 mt-4"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold text-center">
|
||||
Start New Chat
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={joinChat}
|
||||
className="w-full bg-green-500 py-4 rounded-md"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold text-center">
|
||||
Join Chat
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
BIN
examples/chat-rn-clerk/assets/fonts/SpaceMono-Regular.ttf
Executable file
BIN
examples/chat-rn-clerk/assets/images/adaptive-icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
examples/chat-rn-clerk/assets/images/dark-logo.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
examples/chat-rn-clerk/assets/images/favicon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
examples/chat-rn-clerk/assets/images/hero.png
Normal file
|
After Width: | Height: | Size: 313 KiB |
BIN
examples/chat-rn-clerk/assets/images/icon.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
examples/chat-rn-clerk/assets/images/light-logo.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
examples/chat-rn-clerk/assets/images/partial-react-logo.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/chat-rn-clerk/assets/images/react-logo.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
examples/chat-rn-clerk/assets/images/react-logo@2x.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
examples/chat-rn-clerk/assets/images/react-logo@3x.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
examples/chat-rn-clerk/assets/images/splash.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
10
examples/chat-rn-clerk/babel.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
plugins: [
|
||||
"nativewind/babel",
|
||||
"@babel/plugin-transform-class-static-block",
|
||||
],
|
||||
};
|
||||
};
|
||||
36
examples/chat-rn-clerk/cache.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export interface TokenCache {
|
||||
getToken: (key: string) => Promise<string | undefined | null>;
|
||||
saveToken: (key: string, token: string) => Promise<void>;
|
||||
clearToken?: (key: string) => void;
|
||||
}
|
||||
|
||||
const createTokenCache = (): TokenCache => {
|
||||
return {
|
||||
getToken: async (key: string) => {
|
||||
try {
|
||||
const item = await SecureStore.getItemAsync(key);
|
||||
if (item) {
|
||||
console.log(`${key} was used 🔐 \n`);
|
||||
} else {
|
||||
console.log("No values stored under key: " + key);
|
||||
}
|
||||
return item;
|
||||
} catch (error) {
|
||||
console.error("secure store get item error: ", error);
|
||||
await SecureStore.deleteItemAsync(key);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
saveToken: (key: string, token: string) => {
|
||||
return SecureStore.setItemAsync(key, token);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// SecureStore is not supported on the web
|
||||
// https://github.com/expo/expo/issues/7744#issuecomment-611093485
|
||||
export const tokenCache =
|
||||
Platform.OS !== "web" ? createTokenCache() : undefined;
|
||||
27
examples/chat-rn-clerk/eas.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 12.5.1",
|
||||
"appVersionSource": "remote"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"ios-simulator": {
|
||||
"extends": "development",
|
||||
"ios": {
|
||||
"simulator": true
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"distribution": "internal"
|
||||
},
|
||||
"production": {
|
||||
"autoIncrement": true
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
2
examples/chat-rn-clerk/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import "./polyfills";
|
||||
import "expo-router/entry";
|
||||
31
examples/chat-rn-clerk/metro.config.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// Learn more https://docs.expo.dev/guides/monorepos
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
const { FileStore } = require("metro-cache");
|
||||
const path = require("path");
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const projectRoot = __dirname;
|
||||
const workspaceRoot = path.resolve(projectRoot, "../..");
|
||||
|
||||
const config = getDefaultConfig(projectRoot);
|
||||
|
||||
// Since we are using pnpm, we have to setup the monorepo manually for Metro
|
||||
// #1 - Watch all files in the monorepo
|
||||
config.watchFolders = [workspaceRoot];
|
||||
// #2 - Try resolving with project modules first, then workspace modules
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
];
|
||||
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
|
||||
config.resolver.unstable_enablePackageExports = true;
|
||||
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
|
||||
|
||||
// Use turborepo to restore the cache when possible
|
||||
config.cacheStores = [
|
||||
new FileStore({
|
||||
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
|
||||
}),
|
||||
];
|
||||
|
||||
module.exports = config;
|
||||
1
examples/chat-rn-clerk/nativewind-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="nativewind/types" />
|
||||
75
examples/chat-rn-clerk/package.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "chat-rn-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"test": "jest --watchAll",
|
||||
"lint": "expo lint"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@clerk/clerk-expo": "^2.2.21",
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-native-community/netinfo": "^11.3.1",
|
||||
"@react-navigation/native": "^6.1.18",
|
||||
"@react-navigation/native-stack": "^6.11.0",
|
||||
"base-64": "^1.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"expo": "~51.0.37",
|
||||
"expo-build-properties": "~0.12.5",
|
||||
"expo-clipboard": "~6.0.3",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-crypto": "~13.0.2",
|
||||
"expo-dev-client": "~4.0.28",
|
||||
"expo-font": "~12.0.4",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-router": "~3.5.23",
|
||||
"expo-secure-store": "~13.0.2",
|
||||
"expo-splash-screen": "~0.27.5",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-system-ui": "~3.0.7",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
"jazz-react-auth-clerk": "workspace:*",
|
||||
"jazz-react-native": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"nativewind": "^2.0.11",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-native": "~0.74.5",
|
||||
"react-native-fetch-api": "^3.0.0",
|
||||
"react-native-gesture-handler": "~2.16.1",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-mmkv": "3.0.1",
|
||||
"react-native-polyfill-globals": "^3.1.0",
|
||||
"react-native-reanimated": "~3.10.1",
|
||||
"react-native-safe-area-context": "4.10.5",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-web": "~0.19.10",
|
||||
"text-encoding": "^0.7.0",
|
||||
"web-streams-polyfill": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.7",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/react": "^18.2.19",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-expo": "^7.1.2",
|
||||
"jest": "^29.2.1",
|
||||
"jest-expo": "~51.0.3",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
6
examples/chat-rn-clerk/polyfills.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import "react-native-polyfill-globals/auto";
|
||||
import "@azure/core-asynciterator-polyfill";
|
||||
import { ReadableStream } from "web-streams-polyfill/ponyfill/es6";
|
||||
import { polyfillGlobal } from "react-native/Libraries/Utilities/PolyfillFunctions";
|
||||
|
||||
polyfillGlobal("ReadableStream", () => ReadableStream);
|
||||
62
examples/chat-rn-clerk/src/auth-context.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, {
|
||||
useContext,
|
||||
createContext,
|
||||
useEffect,
|
||||
useState,
|
||||
PropsWithChildren,
|
||||
} from "react";
|
||||
import { useClerk, useUser } from "@clerk/clerk-expo";
|
||||
import { useJazzClerkAuth } from "jazz-react-auth-clerk";
|
||||
import { Jazz } from "./jazz";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
const AuthContext = createContext<{
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
}>({
|
||||
isAuthenticated: false,
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
export function useAuth() {
|
||||
return useContext(AuthContext);
|
||||
}
|
||||
|
||||
export function JazzAndAuth({ children }: PropsWithChildren) {
|
||||
const { isSignedIn, isLoaded: isClerkLoaded } = useUser();
|
||||
const clerk = useClerk();
|
||||
const [auth, state] = useJazzClerkAuth(clerk);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSignedIn && isClerkLoaded && auth) {
|
||||
setIsAuthenticated(true);
|
||||
} else {
|
||||
setIsAuthenticated(false);
|
||||
}
|
||||
}, [isSignedIn, isClerkLoaded, auth]);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{ isAuthenticated, isLoading: !isClerkLoaded || !auth }}
|
||||
>
|
||||
{state?.errors?.length > 0 &&
|
||||
state.errors.map((error) => (
|
||||
<View key={error}>
|
||||
<Text style={{ color: "red" }}>{error}</Text>
|
||||
</View>
|
||||
))}
|
||||
{auth ? (
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
peer="wss://mesh.jazz.tools/?key=chat-rn-clerk-example-jazz@gcmp.io"
|
||||
storage={undefined}
|
||||
>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
7
examples/chat-rn-clerk/src/jazz.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createJazzRNApp } from "jazz-react-native";
|
||||
import { MMKVStorage } from "./mmkv-storage";
|
||||
|
||||
const nativeStorage = new MMKVStorage();
|
||||
|
||||
export const Jazz = createJazzRNApp({ nativeStorage });
|
||||
export const { useAccount, useCoState, useAcceptInvite } = Jazz;
|
||||
25
examples/chat-rn-clerk/src/mmkv-storage.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { MMKV } from "react-native-mmkv";
|
||||
import type { NativeStorage } from "jazz-react-native";
|
||||
|
||||
const storage = new MMKV();
|
||||
|
||||
export class MMKVStorage implements NativeStorage {
|
||||
get(key: string): Promise<string | undefined> {
|
||||
return Promise.resolve(storage.getString(key));
|
||||
}
|
||||
|
||||
set(key: string, value: string): Promise<void> {
|
||||
storage.set(key, value);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
delete(key: string): Promise<void> {
|
||||
storage.delete(key);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
clearAll(): Promise<void> {
|
||||
storage.clearAll();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
7
examples/chat-rn-clerk/src/schema.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { CoMap, CoList, co } from "jazz-tools";
|
||||
|
||||
export class Message extends CoMap {
|
||||
text = co.string;
|
||||
}
|
||||
|
||||
export class Chat extends CoList.Of(co.ref(Message)) {}
|
||||
12
examples/chat-rn-clerk/tailwind.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./app/**/*.{js,jsx,ts,tsx}",
|
||||
"./components/**/*.{js,jsx,ts,tsx}",
|
||||
"./src/**/*.{js,jsx,ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
16
examples/chat-rn-clerk/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
]
|
||||
}
|
||||
1
examples/chat-rn/.env
Normal file
@@ -0,0 +1 @@
|
||||
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
|
||||
@@ -1,46 +1,46 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "jazz-chat-rn",
|
||||
"scheme": "jazz-chat-rn",
|
||||
"slug": "jazz-chat-rn",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.jazz.chatrn"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"package": "com.jazz.chatrn"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"ios": {
|
||||
"newArchEnabled": true
|
||||
},
|
||||
"android": {
|
||||
"newArchEnabled": true
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "e0e61872-1906-4c84-b9d8-9be77355cad0"
|
||||
}
|
||||
},
|
||||
"owner": "paxx"
|
||||
}
|
||||
"expo": {
|
||||
"name": "jazz-chat-rn",
|
||||
"scheme": "jazz-chat-rn",
|
||||
"slug": "jazz-chat-rn",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.jazz.chatrn"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"package": "com.jazz.chatrn"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"ios": {
|
||||
"newArchEnabled": true
|
||||
},
|
||||
"android": {
|
||||
"newArchEnabled": true
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "e0e61872-1906-4c84-b9d8-9be77355cad0"
|
||||
}
|
||||
},
|
||||
"owner": "paxx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "./polyfills";
|
||||
import { registerRootComponent } from "expo";
|
||||
import App from "./src/App";
|
||||
|
||||
registerRootComponent(App);
|
||||
|
||||
@@ -3,6 +3,7 @@ const { getDefaultConfig } = require("expo/metro-config");
|
||||
const { FileStore } = require("metro-cache");
|
||||
const path = require("path");
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const projectRoot = __dirname;
|
||||
const workspaceRoot = path.resolve(projectRoot, "../..");
|
||||
|
||||
@@ -16,8 +17,9 @@ config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
];
|
||||
config.resolver.sourceExts = ["js", "json", "ts", "tsx"];
|
||||
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
|
||||
config.resolver.unstable_enablePackageExports = true;
|
||||
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
|
||||
|
||||
// Use turborepo to restore the cache when possible
|
||||
config.cacheStores = [
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"version": "1.0.2",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
@@ -15,14 +16,15 @@
|
||||
"@react-navigation/native-stack": "^6.11.0",
|
||||
"base-64": "^1.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"expo": "~51.0.36",
|
||||
"expo": "~51.0.37",
|
||||
"expo-build-properties": "~0.12.5",
|
||||
"expo-clipboard": "~6.0.3",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-crypto": "~13.0.2",
|
||||
"expo-dev-client": "~4.0.27",
|
||||
"expo-dev-client": "~4.0.28",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
"jazz-react-native": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"nativewind": "^2.0.11",
|
||||
|
||||
@@ -59,6 +59,7 @@ function App() {
|
||||
<Stack.Screen
|
||||
options={{ title: "Jazz Chat" }}
|
||||
name="ChatScreen"
|
||||
// @ts-ignore
|
||||
component={ChatScreen}
|
||||
/>
|
||||
<Stack.Screen
|
||||
|
||||
@@ -22,7 +22,6 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||
const [chat, setChat] = useState<Chat>();
|
||||
const [message, setMessage] = useState("");
|
||||
const loadedChat = useCoState(Chat, chat?.id, [{}]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createJazzRNApp } from "jazz-react-native";
|
||||
import { MMKVStorage } from "./mmkv-storage";
|
||||
import { Account } from "jazz-tools";
|
||||
|
||||
const nativeStorage = new MMKVStorage();
|
||||
|
||||
|
||||
@@ -53,4 +53,5 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export { Button, buttonVariants }
|
||||
|
||||
@@ -16,13 +16,15 @@ export function HeroHeader({
|
||||
title,
|
||||
slogan,
|
||||
pt = true,
|
||||
className = ''
|
||||
}: {
|
||||
title: ReactNode;
|
||||
slogan: ReactNode;
|
||||
pt?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<hgroup className={clsx(pt && "pt-12 md:pt-20", "mb-10")}>
|
||||
<hgroup className={clsx(pt && "pt-12 md:pt-20", "mb-10", className)}>
|
||||
<H1>{title}</H1>
|
||||
<H1Sub>{slogan}</H1Sub>
|
||||
</hgroup>
|
||||
|
||||
@@ -22,7 +22,7 @@ type FooterProps = {
|
||||
|
||||
export function Footer({ logo, companyName, sections }: FooterProps) {
|
||||
return (
|
||||
<footer className="flex z-10 mt-10 min-h-[15rem] bg-stone-100 dark:bg-stone-900 text-stone-600 dark:text-stone-400 w-full justify-center">
|
||||
<footer className="flex z-10 mt-10 min-h-[15rem] border-t bg-stone-100 dark:bg-stone-925 text-stone-600 dark:text-stone-400 w-full justify-center dark:border-stone-900">
|
||||
<div className="p-8 container w-full grid grid-cols-3 md:grid-cols-4 lg:grid-cols-7 gap-8 max-sm:mb-12">
|
||||
<div className="col-span-full md:col-span-1 sm:row-start-4 md:row-start-auto lg:col-span-2 md:row-span-2 md:flex-1 flex flex-row md:flex-col max-sm:mt-4 justify-between max-sm:items-start gap-2 text-sm min-w-[10rem]">
|
||||
{logo}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function Nav({
|
||||
className={[
|
||||
clsx(
|
||||
"hidden md:flex sticky left-0 right-0 top-0 max-sm:bottom-0 w-full justify-center",
|
||||
"bg-white dark:bg-stone-950 border-b max-sm:border-t border-stone-50 dark:border-b-stone-950",
|
||||
"bg-white dark:bg-stone-950 border-b max-sm:border-t border-stone-200 dark:border-stone-900",
|
||||
"max-h-none overflow-hidden transition[max-height] duration-300 ease-in-out",
|
||||
"z-50",
|
||||
menuOpen ? "h-[100dvh]" : "h-16"
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function Home() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-stone-100 dark:bg-stone-925 py-8 lg:py-16">
|
||||
<div className="bg-stone-100 border-y dark:bg-stone-925 py-8 lg:py-16 dark:border-stone-900">
|
||||
<div className="container grid gap-8 lg:gap-12">
|
||||
<h2 className="font-display md:text-center text-stone-950 dark:text-white text-2xl md:text-3xl font-semibold tracking-tight">
|
||||
Hard things are easy now.
|
||||
@@ -568,20 +568,38 @@ export default function Home() {
|
||||
</GridCard>
|
||||
</GappedGrid>
|
||||
|
||||
<div>
|
||||
<div className="flex flex-col justify-between gap-3">
|
||||
<h3 className="font-display text-stone-950 dark:text-white text-xl font-semibold tracking-tight lg:text-2xl">
|
||||
Get started
|
||||
<div className="border border-stone-200 dark:border-stone-900 rounded-xl shadow-sm p-4 md:py-16">
|
||||
<div className="flex flex-col lg:max-w-3xl md:text-center mx-auto justify-between gap-6">
|
||||
<p className="uppercase text-blue tracking-wide text-sm font-medium dark:text-stone-400">
|
||||
Become an early adopter
|
||||
</p>
|
||||
<h3 className="font-display md:text-center text-stone-950 dark:text-white text-2xl md:text-3xl font-semibold tracking-tight">
|
||||
We'll help you build your next app with Jazz
|
||||
</h3>
|
||||
<div className="flex gap-3">
|
||||
<Button href="/docs" variant="primary">
|
||||
Read documentation
|
||||
</Button>
|
||||
<div className="space-y-2 md:text-balance leading-relaxed">
|
||||
<p>
|
||||
It's early days, but we work hard every day
|
||||
to make Jazz a great tool for our users.
|
||||
</p>
|
||||
<p>
|
||||
We want to hear about what you're building,
|
||||
so we can help you every step of the way.
|
||||
We'll prioritize features that you need to
|
||||
succeed.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex md:justify-center gap-3">
|
||||
<Button
|
||||
href="https://discord.gg/utDMjHYg42"
|
||||
variant="secondary"
|
||||
variant="primary"
|
||||
>
|
||||
Join Discord
|
||||
Let's talk on Discord
|
||||
</Button>
|
||||
<Button href="/docs" variant="secondary">
|
||||
Read <span className="sm:hidden">docs</span>{" "}
|
||||
<span className="hidden sm:inline">
|
||||
documentation
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
#### Completely DIY Mesh.
|
||||
<p className="no-prose text-base">
|
||||
Build your own network of sync and storage nodes. Handle
|
||||
networking, security and backups yourself.
|
||||
</p>
|
||||
<div className="text-sm">
|
||||
Costs:
|
||||
- N × instance cost for your sync nodes.
|
||||
- Very high self-hosted egress costs.
|
||||
- High self-hosted storage costs.
|
||||
</div>
|
||||
devops,networking, security and backups yourself.
|
||||
</p>
|
||||
@@ -2,11 +2,5 @@
|
||||
<p className="no-prose text-base">
|
||||
Connect your users to Jazz Mesh for all its benefits,
|
||||
but also run and connect your own data backup node (just
|
||||
in case.)
|
||||
</p>
|
||||
<div className="text-sm">
|
||||
Extra costs:
|
||||
- Instance costs for the backup node.
|
||||
- Moderate self-hosted storage costs.
|
||||
- Your backup node is counted as a continously connected device.
|
||||
</div>
|
||||
in case).
|
||||
</p>
|
||||
@@ -3,11 +3,4 @@
|
||||
Connect your users to Jazz Mesh, or your own nodes as a
|
||||
lower-performance fallback. The two networks stay in
|
||||
constant sync.
|
||||
</p>
|
||||
<div className="text-sm">
|
||||
Extra costs:
|
||||
- N × instance cost for your sync nodes.
|
||||
- Typically moderate self-hosted egress costs.
|
||||
- High self-hosted storage costs.
|
||||
- Any of your sync nodes connected to Jazz Mesh is counted as a continously connected device.
|
||||
</div>
|
||||
</p>
|
||||
@@ -4,18 +4,19 @@ import { GappedGrid } from "gcmp-design-system/src/app/components/molecules/Gapp
|
||||
import { GridCard } from "gcmp-design-system/src/app/components/atoms/GridCard";
|
||||
import { ComingSoonBadge } from "gcmp-design-system/src/app/components/atoms/ComingSoonBadge";
|
||||
import { P } from "gcmp-design-system/src/app/components/atoms/Paragraph";
|
||||
import { H3, H4 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import {
|
||||
H2,
|
||||
H3,
|
||||
H4,
|
||||
} from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import { LI } from "gcmp-design-system/src/app/components/atoms/ListItem";
|
||||
import { UL } from "gcmp-design-system/src/app/components/molecules/List";
|
||||
import {
|
||||
Prose,
|
||||
SmallProse,
|
||||
} from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import MeshPlusBackup from "./meshPlusBackup.mdx";
|
||||
import MeshPlusDIY from "./meshPlusDIY.mdx";
|
||||
import CompletelyDIY from "./completelyDIY.mdx";
|
||||
|
||||
import PricingFAQ from "./pricingFaq.mdx";
|
||||
import { Button } from "@/components/Button";
|
||||
import { Pricing } from "@/components/Pricing";
|
||||
|
||||
export const metadata = {
|
||||
title: "jazz - Jazz Mesh",
|
||||
@@ -24,16 +25,14 @@ export const metadata = {
|
||||
|
||||
export default function Mesh() {
|
||||
return (
|
||||
<div className="container space-y-16">
|
||||
<div className="space-y-16">
|
||||
<HeroHeader
|
||||
className="container"
|
||||
title="Jazz Mesh"
|
||||
slogan="Real-time sync and storage infrastructure that scales up to millions of users."
|
||||
/>
|
||||
|
||||
<div>
|
||||
<P>
|
||||
Pricing that scales down to zero.
|
||||
</P>
|
||||
<div className="container">
|
||||
<P>Pricing that scales down to zero.</P>
|
||||
<GappedGrid>
|
||||
<GridCard>
|
||||
<H3>Optimal mesh routing.</H3>
|
||||
@@ -62,166 +61,194 @@ export default function Mesh() {
|
||||
</GappedGrid>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SectionHeader title="Pricing" slogan="" />
|
||||
<GappedGrid>
|
||||
<GridCard>
|
||||
<H3>
|
||||
Mesh Free{" "}
|
||||
<div className="text-2xl float-right">$0</div>
|
||||
</H3>
|
||||
<div className="bg-stone-100 border-y dark:bg-stone-925 py-8 lg:py-16 dark:border-y-0 dark:bg-transparent dark:py-0">
|
||||
<div className="container">
|
||||
<H2 className="mb-5 sm:text-center md:text-left">
|
||||
Pricing
|
||||
</H2>
|
||||
|
||||
<Pricing />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container space-y-16">
|
||||
<div>
|
||||
<SectionHeader title="Pricing" slogan="" />
|
||||
<GappedGrid>
|
||||
<GridCard>
|
||||
<H3>
|
||||
Mesh Free{" "}
|
||||
<div className="text-2xl float-right">$0</div>
|
||||
</H3>
|
||||
|
||||
<UL>
|
||||
<LI>Best-effort sync</LI>
|
||||
<LI>3,000 sync-minutes/mo</LI>
|
||||
<LI>1 GB storage</LI>
|
||||
</UL>
|
||||
</GridCard>
|
||||
<GridCard>
|
||||
<H3>
|
||||
Mesh Starter <ComingSoonBadge />
|
||||
<div className="float-right">
|
||||
<span className="text-2xl">$9</span>/mo
|
||||
</div>
|
||||
</H3>
|
||||
<UL>
|
||||
<LI>Base-priority sync</LI>
|
||||
<LI>6,000 sync-minutes/mo</LI>
|
||||
<LI>100 GB storage</LI>
|
||||
</UL>
|
||||
<div className="text-xs">
|
||||
<P>Extra usage:</P>
|
||||
<LI>$9 per additional 6,000 sync-minutes</LI>
|
||||
<LI>$9 per additional 1TB storage/mo</LI>
|
||||
</div>
|
||||
</GridCard>
|
||||
<GridCard>
|
||||
<H3>
|
||||
Mesh Pro <ComingSoonBadge />
|
||||
<div className="float-right">
|
||||
<span className="text-2xl">$79</span>/mo
|
||||
</div>
|
||||
</H3>
|
||||
<UL>
|
||||
<LI>High-priority sync</LI>
|
||||
<LI>30,000 sync-minutes/mo</LI>
|
||||
<LI>1 TB storage</LI>
|
||||
<LI>Offer sync.yourdomain.com</LI>
|
||||
</UL>
|
||||
<div className="text-xs">
|
||||
<P>Extra usage:</P>
|
||||
<UL>
|
||||
<LI>$15 per additional 6,000 sync-minutes</LI>
|
||||
<LI>$15 per additional 1TB storage/mo</LI>
|
||||
<LI>Best-effort sync</LI>
|
||||
<LI>Community support</LI>
|
||||
<LI>
|
||||
<s>20 Monthly Active Users</s>
|
||||
</LI>
|
||||
<LI>
|
||||
<s>1 GB storage</s>
|
||||
</LI>
|
||||
</UL>
|
||||
<div className="border-white bg-blue-50 border-4 rounded-lg px-4 -rotate-6 shadow">
|
||||
<p className="text-center my-4 font-bold">
|
||||
Public Alpha
|
||||
</p>
|
||||
<UL>
|
||||
<LI>Use your email address as API key</LI>
|
||||
<LI>Currently no enforced limits</LI>
|
||||
</UL>
|
||||
</div>
|
||||
</GridCard>
|
||||
<GridCard>
|
||||
<H3>
|
||||
Mesh Indie <ComingSoonBadge />
|
||||
<div className="float-right">
|
||||
<span className="text-2xl">$19</span>/mo
|
||||
</div>
|
||||
</H3>
|
||||
<UL>
|
||||
<LI>Base-priority sync</LI>
|
||||
<LI>Community support</LI>
|
||||
<LI>1000 Monthly Active Usersincluded</LI>
|
||||
<LI>500 GB storageincluded</LI>
|
||||
</UL>
|
||||
<P>Extra usage:</P>
|
||||
<LI>$9 per add. 1000 Monthly Active Users</LI>
|
||||
<LI>$9 per add. 500 GB storage/mo</LI>
|
||||
|
||||
<p className="mt-4 text-sm">
|
||||
For companies with <$200k in annual revenue
|
||||
or institutional funding.
|
||||
</p>
|
||||
</GridCard>
|
||||
<GridCard>
|
||||
<H3>
|
||||
Mesh Pro
|
||||
<div className="float-right">
|
||||
from <span className="text-2xl">$500</span>
|
||||
/mo
|
||||
</div>
|
||||
</H3>
|
||||
<UL>
|
||||
<LI>Highest-priority sync</LI>
|
||||
<LI>Dedicated integration & dev support</LI>
|
||||
<LI>Unlimited Monthly Active Users</LI>
|
||||
<LI>Unlimited storage</LI>
|
||||
<LI>SLAs, custom deployment, etc.</LI>
|
||||
<LI>
|
||||
Offer <code>sync.yourdomain.com</code>
|
||||
</LI>
|
||||
</UL>
|
||||
<Button
|
||||
href="https://cal.com/anselm-io/mesh-pro-intro"
|
||||
size="lg"
|
||||
className="block text-center"
|
||||
>
|
||||
Book intro call
|
||||
</Button>
|
||||
<p className="mt-4 text-sm">
|
||||
Our team of devs & product experts will
|
||||
get you going for free. Then we'll make a
|
||||
deal just for you.
|
||||
</p>
|
||||
</GridCard>
|
||||
</GappedGrid>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SectionHeader
|
||||
title="Global Footprint"
|
||||
slogan="We're rapidly expanding our network of sync & storage nodes. This is our current best-effort coverage."
|
||||
/>
|
||||
<GappedGrid>
|
||||
<div className="text-sm">
|
||||
<H4>Under 50ms RTT</H4>
|
||||
<UL>
|
||||
<LI>Frankfurt</LI>
|
||||
<LI>New York</LI>
|
||||
<LI>Newark</LI>
|
||||
<LI>North California</LI>
|
||||
<LI>North Virginia</LI>
|
||||
<LI>San Francisco</LI>
|
||||
<LI>Singapore</LI>
|
||||
<LI>Toronto</LI>
|
||||
</UL>
|
||||
</div>
|
||||
</GridCard>
|
||||
</GappedGrid>
|
||||
<H3>FAQ</H3>
|
||||
<SmallProse>
|
||||
<PricingFAQ />
|
||||
</SmallProse>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SectionHeader
|
||||
title="Global Footprint"
|
||||
slogan="We're rapidly expanding our network of sync & storage nodes. This is our current best-effort coverage."
|
||||
/>
|
||||
<GappedGrid>
|
||||
<div className="text-sm">
|
||||
<H4>Under 50ms RTT</H4>
|
||||
<UL>
|
||||
<LI>Frankfurt</LI>
|
||||
<LI>New York</LI>
|
||||
<LI>Newark</LI>
|
||||
<LI>North California</LI>
|
||||
<LI>North Virginia</LI>
|
||||
<LI>San Francisco</LI>
|
||||
<LI>Singapore</LI>
|
||||
<LI>Toronto</LI>
|
||||
</UL>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<H4>Under 100ms RTT</H4>
|
||||
<UL>
|
||||
<LI>Amsterdam</LI>
|
||||
<LI>Atlanta</LI>
|
||||
<LI>London</LI>
|
||||
<LI>Ohio</LI>
|
||||
<LI>Paris</LI>
|
||||
</UL>
|
||||
</div>
|
||||
|
||||
<div className="text-sm">
|
||||
<H4>Under 100ms RTT</H4>
|
||||
<UL>
|
||||
<LI>Amsterdam</LI>
|
||||
<LI>Atlanta</LI>
|
||||
<LI>London</LI>
|
||||
<LI>Ohio</LI>
|
||||
<LI>Paris</LI>
|
||||
</UL>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<H4>Under 200ms RTT</H4>
|
||||
<UL>
|
||||
<LI>Bangalore</LI>
|
||||
<LI>Dallas</LI>
|
||||
<LI>Mumbai</LI>
|
||||
<LI>Oregon</LI>
|
||||
</UL>
|
||||
|
||||
<div className="text-sm">
|
||||
<H4>Under 200ms RTT</H4>
|
||||
<UL>
|
||||
<LI>Bangalore</LI>
|
||||
<LI>Dallas</LI>
|
||||
<LI>Mumbai</LI>
|
||||
<LI>Oregon</LI>
|
||||
</UL>
|
||||
<H4>Under 300ms RTT</H4>
|
||||
<UL>
|
||||
<LI> Seoul</LI>
|
||||
<LI> Tokyo</LI>
|
||||
</UL>
|
||||
</div>
|
||||
|
||||
<H4>Under 300ms RTT</H4>
|
||||
<UL>
|
||||
<LI> Seoul</LI>
|
||||
<LI> Tokyo</LI>
|
||||
</UL>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<H4>Under 400ms RTT</H4>
|
||||
<UL>
|
||||
<LI>Sao Paulo</LI>
|
||||
<LI>Sydney</LI>
|
||||
</UL>
|
||||
|
||||
<div className="text-sm">
|
||||
<H4>Under 400ms RTT</H4>
|
||||
<UL>
|
||||
<LI>Sao Paulo</LI>
|
||||
<LI>Sydney</LI>
|
||||
</UL>
|
||||
<H4>Under 500ms RTT</H4>
|
||||
|
||||
<H4>Under 500ms RTT</H4>
|
||||
<UL>
|
||||
<LI>Cape Town</LI>
|
||||
</UL>
|
||||
</div>
|
||||
</GappedGrid>
|
||||
|
||||
<UL>
|
||||
<LI>Cape Town</LI>
|
||||
</UL>
|
||||
</div>
|
||||
</GappedGrid>
|
||||
|
||||
<H3>Enterprise</H3>
|
||||
<P>
|
||||
Custom deployment in the cloud, your private cloud,
|
||||
on-premises or hybrids?
|
||||
<br />
|
||||
SLAs and dedicated support? White-glove integration
|
||||
services? Let's talk:{" "}
|
||||
<a href="mailto:hello@gcmp.io">hello@gcmp.io</a>
|
||||
</P>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SectionHeader
|
||||
title="Custom Deployment Scenarios"
|
||||
slogan="You can rely on Jazz Mesh. But you don't have to."
|
||||
/>
|
||||
<P>
|
||||
Because Jazz is open-source, you can optionally run your own
|
||||
sync nodes — in a variety of setups.
|
||||
</P>
|
||||
<GappedGrid>
|
||||
<GridCard>
|
||||
<Prose>
|
||||
<MeshPlusBackup />
|
||||
</Prose>
|
||||
</GridCard>
|
||||
<GridCard>
|
||||
<Prose>
|
||||
<MeshPlusDIY />
|
||||
</Prose>
|
||||
</GridCard>
|
||||
<GridCard>
|
||||
<Prose>
|
||||
<CompletelyDIY />
|
||||
</Prose>
|
||||
</GridCard>
|
||||
</GappedGrid>
|
||||
<div>
|
||||
<SectionHeader
|
||||
title="Custom Deployment Scenarios"
|
||||
slogan="You can rely on Jazz Mesh. But you don't have to."
|
||||
/>
|
||||
<P>
|
||||
Because Jazz is open-source, you can optionally run your
|
||||
own sync nodes — in a variety of setups.
|
||||
</P>
|
||||
<GappedGrid>
|
||||
<GridCard>
|
||||
<Prose>
|
||||
<MeshPlusBackup />
|
||||
</Prose>
|
||||
</GridCard>
|
||||
<GridCard>
|
||||
<Prose>
|
||||
<MeshPlusDIY />
|
||||
</Prose>
|
||||
</GridCard>
|
||||
<GridCard>
|
||||
<Prose>
|
||||
<CompletelyDIY />
|
||||
</Prose>
|
||||
</GridCard>
|
||||
</GappedGrid>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#### How are sync-minutes counted?
|
||||
|
||||
Sync-minutes are counted on a **per-connected-device, per-minute basis.**<br/>
|
||||
A device is considered syncing **only when it's actively sending or receiving data.**
|
||||
|
||||
#### How can I estimate my usage?
|
||||
|
||||
The best way to estimate your usage is to **guess how many minutes per month each user will spend actively using your app.**
|
||||
Storage is mostly determined by large binary blobs (like images or videos) that you store in Jazz.
|
||||
|
||||
#### What happens if I exceed my plan's limits?
|
||||
|
||||
All limits are initially soft limits, so don't worry if you suddenly get lots of users or traffic!
|
||||
|
||||
Sync beyond the limit is still served, but at a lower priority.
|
||||
Data beyond the storage limit is still stored and backed up, but may be significantly slower to access.
|
||||
If you exceed your plan's limits consistently, we'll reach out to discuss upgrading your plan.
|
||||
44
homepage/homepage/app/showcase/page.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import Image from "next/image";
|
||||
import { products } from "@/lib/showcase";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata = {
|
||||
title: "jazz - Built with Jazz",
|
||||
description: "Great apps by smart people.",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="container flex flex-col gap-6 pb-10 lg:pb-20">
|
||||
<HeroHeader
|
||||
title="Built with Jazz"
|
||||
slogan="Great apps by smart people."
|
||||
/>
|
||||
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{products.map((product) => (
|
||||
<Link
|
||||
href={product.url}
|
||||
key={product.url}
|
||||
className="group border bg-stone-50 shadow-sm p-3 flex flex-col gap-3 rounded-lg md:p-4 md:gap-4 dark:border-stone-900 dark:bg-stone-900"
|
||||
>
|
||||
<Image
|
||||
className="rounded-md border dark:border-0"
|
||||
src={product.imageUrl}
|
||||
width="900"
|
||||
height="675"
|
||||
alt=""
|
||||
/>
|
||||
<div className="space-y-2">
|
||||
<h2 className="font-medium text-stone-900 dark:text-white leading-none">
|
||||
{product.name}
|
||||
</h2>
|
||||
<p className="text-sm">{product.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -24,14 +24,15 @@ export function Button(props: ButtonProps) {
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
primary: "bg-blue text-white font-medium bg-blue hover:bg-blue-800",
|
||||
primary:
|
||||
"bg-blue border-blue text-white font-medium bg-blue hover:bg-blue-800 hover:border-blue-800",
|
||||
secondary:
|
||||
"text-stone-900 font-medium bg-stone-100 hover:bg-stone-200 dark:bg-stone-900 dark:text-white",
|
||||
"text-stone-900 border border-stone-200 font-medium hover:border-stone-300 dark:border-stone-900 dark:hover:border-stone-800 dark:text-white",
|
||||
};
|
||||
|
||||
const classNames = clsx(
|
||||
className,
|
||||
"rounded-md transition-colors",
|
||||
"rounded-lg text-center transition-colors",
|
||||
sizeClasses[size],
|
||||
variantClasses[variant],
|
||||
);
|
||||
|
||||
145
homepage/homepage/components/Pricing.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import { CircleCheckIcon } from "lucide-react";
|
||||
import { Button } from "@/components/Button";
|
||||
import { ComingSoonBadge } from "gcmp-design-system/src/app/components/atoms/ComingSoonBadge";
|
||||
|
||||
export function ListItem({
|
||||
variant = "blue",
|
||||
children,
|
||||
}: {
|
||||
variant?: "gray" | "blue";
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const iconSize = 16;
|
||||
|
||||
const iconVariants = {
|
||||
gray: (
|
||||
<CircleCheckIcon
|
||||
size={iconSize}
|
||||
className="text-stone-500 shrink-0"
|
||||
/>
|
||||
),
|
||||
blue: (
|
||||
<CircleCheckIcon
|
||||
size={iconSize}
|
||||
className="text-blue-500 dark:text-white shrink-0"
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<li className="inline-flex items-center gap-2 text-stone-800 dark:text-stone-200 py-2">
|
||||
{iconVariants[variant]}
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export function Pricing() {
|
||||
return (
|
||||
<div className="flex flex-col sm:max-w-lg mx-auto md:max-w-none md:flex-row md:items-start gap-4">
|
||||
<div className="md:flex-1 flex flex-col gap-3 overflow-hidden rounded-3xl p-6 shadow-lg shadow-gray-900/5 bg-white dark:bg-stone-925">
|
||||
<h3 className="font-semibold text-stone-900 text-lg dark:text-white">
|
||||
Starter
|
||||
</h3>
|
||||
|
||||
<p className="text-3xl font-light text-stone-900 dark:text-white">
|
||||
$0
|
||||
<span className="text-sm text-stone-600 dark:text-stone-500">
|
||||
/mo
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<ul className="flex flex-col divide-y text-sm lg:text-base dark:divide-stone-900">
|
||||
<ListItem>Best-effort sync</ListItem>
|
||||
<ListItem>Community support</ListItem>
|
||||
<ListItem variant="gray">
|
||||
<s className="text-stone-500">
|
||||
20 monthly active users
|
||||
</s>
|
||||
</ListItem>
|
||||
<ListItem variant="gray">
|
||||
<s className="text-stone-500">1 GB storage</s>
|
||||
</ListItem>
|
||||
</ul>
|
||||
<div className="md:mt-5 space-y-3">
|
||||
<p className="text-xs">
|
||||
Currently no enforced limits for public alpha.
|
||||
</p>
|
||||
|
||||
<p className="text-xs">
|
||||
Use your email address as API key.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:flex-1 flex flex-col gap-3 overflow-hidden rounded-3xl p-6 shadow-lg shadow-gray-900/5 bg-white dark:bg-stone-925">
|
||||
<div>
|
||||
<h3 className="font-semibold text-stone-900 text-lg inline mr-2 dark:text-white">
|
||||
Indie
|
||||
</h3>
|
||||
<ComingSoonBadge />
|
||||
</div>
|
||||
|
||||
<p className="text-3xl font-light text-stone-900 dark:text-white">
|
||||
$19
|
||||
<span className="text-sm text-stone-600 dark:text-stone-500">
|
||||
/mo
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<ul className="flex flex-col divide-y text-sm lg:text-base dark:divide-stone-900">
|
||||
<ListItem>Base-priority sync</ListItem>
|
||||
<ListItem>Community support</ListItem>
|
||||
<ListItem>1000 monthly active users</ListItem>
|
||||
<ListItem>500GB storage</ListItem>
|
||||
</ul>
|
||||
|
||||
<p className="text-sm">Extra usage</p>
|
||||
|
||||
<ul className="flex flex-col divide-y text-sm lg:text-base dark:divide-stone-900">
|
||||
<ListItem>$9 per additional 1000 MAUs</ListItem>
|
||||
<ListItem>$9 per additional 500GB storage/mo</ListItem>
|
||||
</ul>
|
||||
<p className="text-xs">
|
||||
For companies with <$200k in annual revenue or
|
||||
institutional funding.
|
||||
</p>
|
||||
</div>
|
||||
<div className="md:flex-1 flex flex-col gap-3 overflow-hidden rounded-3xl p-6 shadow-lg shadow-gray-900/5 bg-white dark:bg-stone-925 pb-6">
|
||||
<h3 className="font-semibold text-stone-900 text-lg dark:text-white">
|
||||
Pro
|
||||
</h3>
|
||||
|
||||
<p className="text-3xl font-light text-stone-900 dark:text-white">
|
||||
<span className="text-lg font-medium">from {""}</span>
|
||||
$1k
|
||||
<span className="text-sm text-stone-600 dark:text-stone-500">
|
||||
/mo
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<ul className="flex flex-col divide-y text-sm lg:text-base dark:divide-stone-900">
|
||||
<ListItem>High-priority sync</ListItem>
|
||||
<ListItem>White-glove support</ListItem>
|
||||
<ListItem>Unlimited monthly active users</ListItem>
|
||||
<ListItem>Unlimited storage</ListItem>
|
||||
<ListItem>SLAs, custom deployment, etc.</ListItem>
|
||||
<ListItem>
|
||||
Offer <code>sync.yourdomain.com</code>
|
||||
</ListItem>
|
||||
</ul>
|
||||
|
||||
<Button
|
||||
href="https://cal.com/anselm-io/mesh-pro-intro"
|
||||
size="lg"
|
||||
className="md:mt-6"
|
||||
>
|
||||
Book intro call
|
||||
</Button>
|
||||
<p className="text-xs md:mt-3">
|
||||
Our team of devs and product specialists will get you going
|
||||
for free. Then we'll make a bespoke deal.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,10 @@ export function JazzNav() {
|
||||
title: "Docs",
|
||||
href: "/docs",
|
||||
},
|
||||
{
|
||||
title: "Built with Jazz",
|
||||
href: "/showcase",
|
||||
},
|
||||
{
|
||||
title: "Blog",
|
||||
href: "https://gcmp.io/news",
|
||||
|
||||
16
homepage/homepage/lib/showcase.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export const products = [
|
||||
{
|
||||
name: "Learn Anything",
|
||||
imageUrl: "/learn-anything.png",
|
||||
url: "https://learn-anything.xyz",
|
||||
description:
|
||||
"A free, comprehensive learning platform where people collect the best resources for every subject.",
|
||||
},
|
||||
{
|
||||
name: "Invoice Radar",
|
||||
imageUrl: "/invoice-radar.png",
|
||||
url: "https://invoiceradar.com",
|
||||
description:
|
||||
"Automatically gather invoices from mail and cloud providers.",
|
||||
},
|
||||
];
|
||||
BIN
homepage/homepage/public/invoice-radar.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
homepage/homepage/public/learn-anything.png
Normal file
|
After Width: | Height: | Size: 368 KiB |
@@ -22,8 +22,9 @@
|
||||
"dev": "turbo dev",
|
||||
"build": "turbo build && cd homepage/homepage && pnpm run build",
|
||||
"lint": "turbo lint && cd homepage/homepage && pnpm run lint",
|
||||
"test": "vitest --coverage.enabled=false",
|
||||
"test:coverage": "vitest --ui --coverage --watch",
|
||||
"test": "vitest",
|
||||
"test:ci": "vitest --coverage.enabled=true",
|
||||
"test:coverage": "vitest --ui --coverage.enabled=true",
|
||||
"format": "pnpm run -r format && cd homepage/homepage && pnpm run format",
|
||||
"changeset": "changeset",
|
||||
"changeset-version": "changeset version",
|
||||
@@ -41,6 +42,10 @@
|
||||
"expo-modules-*",
|
||||
"typescript"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
149
packages/cojson/src/exports.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
CoValueCore,
|
||||
type CoValueUniqueness,
|
||||
MAX_RECOMMENDED_TX_SIZE,
|
||||
idforHeader,
|
||||
} from "./coValueCore.js";
|
||||
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
||||
import { LocalNode } from "./localNode.js";
|
||||
import { type RawCoValue } from "./coValue.js";
|
||||
import { RawCoMap } from "./coValues/coMap.js";
|
||||
import { RawCoList } from "./coValues/coList.js";
|
||||
import { RawCoStream, RawBinaryCoStream } from "./coValues/coStream.js";
|
||||
import {
|
||||
secretSeedLength,
|
||||
shortHashLength,
|
||||
StreamingHash,
|
||||
CryptoProvider,
|
||||
} from "./crypto/crypto.js";
|
||||
import { connectedPeers, Channel } from "./streamUtils.js";
|
||||
import { ControlledAgent, RawControlledAccount } from "./coValues/account.js";
|
||||
import type { Role } from "./permissions.js";
|
||||
import { rawCoIDtoBytes, rawCoIDfromBytes, isRawCoID } from "./ids.js";
|
||||
import { RawGroup, EVERYONE } from "./coValues/group.js";
|
||||
import type { Everyone } from "./coValues/group.js";
|
||||
import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
|
||||
import { parseJSON } from "./jsonStringify.js";
|
||||
import {
|
||||
RawAccount,
|
||||
RawProfile,
|
||||
accountHeaderForInitialAgentSecret,
|
||||
} from "./coValues/account.js";
|
||||
import { expectGroup } from "./typeUtils/expectGroup.js";
|
||||
import { isAccountID } from "./typeUtils/isAccountID.js";
|
||||
|
||||
import type { SessionID, AgentID } from "./ids.js";
|
||||
import type { CoID, AnyRawCoValue } from "./coValue.js";
|
||||
import type {
|
||||
BinaryStreamInfo,
|
||||
BinaryCoStreamMeta,
|
||||
} from "./coValues/coStream.js";
|
||||
import type { JsonValue } from "./jsonValue.js";
|
||||
import type {
|
||||
SyncMessage,
|
||||
Peer,
|
||||
IncomingSyncStream,
|
||||
OutgoingSyncQueue,
|
||||
} from "./sync.js";
|
||||
import { DisconnectedError, PingTimeoutError } from "./sync.js";
|
||||
import type { AgentSecret } from "./crypto/crypto.js";
|
||||
import type {
|
||||
RawAccountID,
|
||||
AccountMeta,
|
||||
RawAccountMigration,
|
||||
} from "./coValues/account.js";
|
||||
import type { InviteSecret } from "./coValues/group.js";
|
||||
import type * as Media from "./media.js";
|
||||
|
||||
type Value = JsonValue | AnyRawCoValue;
|
||||
|
||||
import { LSMStorage, BlockFilename, WalFilename } from "./storage/index.js";
|
||||
import { FileSystem } from "./storage/FileSystem.js";
|
||||
import { getPriorityFromHeader } from "./priority.js";
|
||||
|
||||
/** @hidden */
|
||||
export const cojsonInternals = {
|
||||
connectedPeers,
|
||||
rawCoIDtoBytes,
|
||||
rawCoIDfromBytes,
|
||||
secretSeedLength,
|
||||
shortHashLength,
|
||||
expectGroup,
|
||||
base64URLtoBytes,
|
||||
bytesToBase64url,
|
||||
parseJSON,
|
||||
accountOrAgentIDfromSessionID,
|
||||
isAccountID,
|
||||
accountHeaderForInitialAgentSecret,
|
||||
idforHeader,
|
||||
StreamingHash,
|
||||
Channel,
|
||||
getPriorityFromHeader,
|
||||
};
|
||||
|
||||
export {
|
||||
LocalNode,
|
||||
RawGroup,
|
||||
Role,
|
||||
EVERYONE,
|
||||
Everyone,
|
||||
RawCoMap,
|
||||
RawCoList,
|
||||
RawCoStream,
|
||||
RawBinaryCoStream,
|
||||
RawCoValue,
|
||||
CoID,
|
||||
AnyRawCoValue,
|
||||
RawAccount,
|
||||
RawAccountID,
|
||||
AccountMeta,
|
||||
RawAccountMigration,
|
||||
RawProfile as Profile,
|
||||
SessionID,
|
||||
Media,
|
||||
CoValueCore,
|
||||
ControlledAgent,
|
||||
RawControlledAccount,
|
||||
MAX_RECOMMENDED_TX_SIZE,
|
||||
JsonValue,
|
||||
Peer,
|
||||
BinaryStreamInfo,
|
||||
BinaryCoStreamMeta,
|
||||
AgentID,
|
||||
AgentSecret,
|
||||
InviteSecret,
|
||||
CryptoProvider,
|
||||
SyncMessage,
|
||||
isRawCoID,
|
||||
LSMStorage,
|
||||
};
|
||||
|
||||
export type {
|
||||
Value,
|
||||
FileSystem,
|
||||
BlockFilename,
|
||||
WalFilename,
|
||||
IncomingSyncStream,
|
||||
OutgoingSyncQueue,
|
||||
DisconnectedError,
|
||||
PingTimeoutError,
|
||||
CoValueUniqueness,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace CojsonInternalTypes {
|
||||
export type CoValueKnownState = import("./sync.js").CoValueKnownState;
|
||||
export type DoneMessage = import("./sync.js").DoneMessage;
|
||||
export type KnownStateMessage = import("./sync.js").KnownStateMessage;
|
||||
export type LoadMessage = import("./sync.js").LoadMessage;
|
||||
export type NewContentMessage = import("./sync.js").NewContentMessage;
|
||||
export type CoValueHeader = import("./coValueCore.js").CoValueHeader;
|
||||
export type Transaction = import("./coValueCore.js").Transaction;
|
||||
export type TransactionID = import("./ids.js").TransactionID;
|
||||
export type Signature = import("./crypto/crypto.js").Signature;
|
||||
export type RawCoID = import("./ids.js").RawCoID;
|
||||
export type ProfileShape = import("./coValues/account.js").ProfileShape;
|
||||
export type SealerSecret = import("./crypto/crypto.js").SealerSecret;
|
||||
export type SignerSecret = import("./crypto/crypto.js").SignerSecret;
|
||||
export type JsonObject = import("./jsonValue.js").JsonObject;
|
||||
}
|
||||
@@ -1,152 +1,2 @@
|
||||
import { PureJSCrypto } from "./crypto/PureJSCrypto.js";
|
||||
|
||||
import {
|
||||
CoValueCore,
|
||||
type CoValueUniqueness,
|
||||
MAX_RECOMMENDED_TX_SIZE,
|
||||
idforHeader,
|
||||
} from "./coValueCore.js";
|
||||
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
||||
import { LocalNode } from "./localNode.js";
|
||||
import { type RawCoValue } from "./coValue.js";
|
||||
import { RawCoMap } from "./coValues/coMap.js";
|
||||
import { RawCoList } from "./coValues/coList.js";
|
||||
import { RawCoStream, RawBinaryCoStream } from "./coValues/coStream.js";
|
||||
import {
|
||||
secretSeedLength,
|
||||
shortHashLength,
|
||||
StreamingHash,
|
||||
CryptoProvider,
|
||||
} from "./crypto/crypto.js";
|
||||
import { connectedPeers, Channel } from "./streamUtils.js";
|
||||
import { ControlledAgent, RawControlledAccount } from "./coValues/account.js";
|
||||
import type { Role } from "./permissions.js";
|
||||
import { rawCoIDtoBytes, rawCoIDfromBytes, isRawCoID } from "./ids.js";
|
||||
import { RawGroup, EVERYONE } from "./coValues/group.js";
|
||||
import type { Everyone } from "./coValues/group.js";
|
||||
import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
|
||||
import { parseJSON } from "./jsonStringify.js";
|
||||
import {
|
||||
RawAccount,
|
||||
RawProfile,
|
||||
accountHeaderForInitialAgentSecret,
|
||||
} from "./coValues/account.js";
|
||||
import { expectGroup } from "./typeUtils/expectGroup.js";
|
||||
import { isAccountID } from "./typeUtils/isAccountID.js";
|
||||
|
||||
import type { SessionID, AgentID } from "./ids.js";
|
||||
import type { CoID, AnyRawCoValue } from "./coValue.js";
|
||||
import type {
|
||||
BinaryStreamInfo,
|
||||
BinaryCoStreamMeta,
|
||||
} from "./coValues/coStream.js";
|
||||
import type { JsonValue } from "./jsonValue.js";
|
||||
import type {
|
||||
SyncMessage,
|
||||
Peer,
|
||||
IncomingSyncStream,
|
||||
OutgoingSyncQueue,
|
||||
} from "./sync.js";
|
||||
import { DisconnectedError, PingTimeoutError } from "./sync.js";
|
||||
import type { AgentSecret } from "./crypto/crypto.js";
|
||||
import type {
|
||||
RawAccountID,
|
||||
AccountMeta,
|
||||
RawAccountMigration,
|
||||
} from "./coValues/account.js";
|
||||
import type { InviteSecret } from "./coValues/group.js";
|
||||
import type * as Media from "./media.js";
|
||||
|
||||
type Value = JsonValue | AnyRawCoValue;
|
||||
|
||||
import { LSMStorage, BlockFilename, WalFilename } from "./storage/index.js";
|
||||
import { FileSystem } from "./storage/FileSystem.js";
|
||||
import { getPriorityFromHeader } from "./priority.js";
|
||||
|
||||
/** @hidden */
|
||||
export const cojsonInternals = {
|
||||
connectedPeers,
|
||||
rawCoIDtoBytes,
|
||||
rawCoIDfromBytes,
|
||||
secretSeedLength,
|
||||
shortHashLength,
|
||||
expectGroup,
|
||||
base64URLtoBytes,
|
||||
bytesToBase64url,
|
||||
parseJSON,
|
||||
accountOrAgentIDfromSessionID,
|
||||
isAccountID,
|
||||
accountHeaderForInitialAgentSecret,
|
||||
idforHeader,
|
||||
StreamingHash,
|
||||
Channel,
|
||||
getPriorityFromHeader,
|
||||
};
|
||||
|
||||
export {
|
||||
LocalNode,
|
||||
RawGroup,
|
||||
Role,
|
||||
EVERYONE,
|
||||
Everyone,
|
||||
RawCoMap,
|
||||
RawCoList,
|
||||
RawCoStream,
|
||||
RawBinaryCoStream,
|
||||
RawCoValue,
|
||||
CoID,
|
||||
AnyRawCoValue,
|
||||
RawAccount,
|
||||
RawAccountID,
|
||||
AccountMeta,
|
||||
RawAccountMigration,
|
||||
RawProfile as Profile,
|
||||
SessionID,
|
||||
Media,
|
||||
CoValueCore,
|
||||
ControlledAgent,
|
||||
RawControlledAccount,
|
||||
MAX_RECOMMENDED_TX_SIZE,
|
||||
JsonValue,
|
||||
Peer,
|
||||
BinaryStreamInfo,
|
||||
BinaryCoStreamMeta,
|
||||
AgentID,
|
||||
AgentSecret,
|
||||
InviteSecret,
|
||||
CryptoProvider,
|
||||
SyncMessage,
|
||||
isRawCoID,
|
||||
LSMStorage,
|
||||
PureJSCrypto,
|
||||
};
|
||||
|
||||
export type {
|
||||
Value,
|
||||
FileSystem,
|
||||
BlockFilename,
|
||||
WalFilename,
|
||||
IncomingSyncStream,
|
||||
OutgoingSyncQueue,
|
||||
DisconnectedError,
|
||||
PingTimeoutError,
|
||||
CoValueUniqueness,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace CojsonInternalTypes {
|
||||
export type CoValueKnownState = import("./sync.js").CoValueKnownState;
|
||||
export type DoneMessage = import("./sync.js").DoneMessage;
|
||||
export type KnownStateMessage = import("./sync.js").KnownStateMessage;
|
||||
export type LoadMessage = import("./sync.js").LoadMessage;
|
||||
export type NewContentMessage = import("./sync.js").NewContentMessage;
|
||||
export type CoValueHeader = import("./coValueCore.js").CoValueHeader;
|
||||
export type Transaction = import("./coValueCore.js").Transaction;
|
||||
export type TransactionID = import("./ids.js").TransactionID;
|
||||
export type Signature = import("./crypto/crypto.js").Signature;
|
||||
export type RawCoID = import("./ids.js").RawCoID;
|
||||
export type ProfileShape = import("./coValues/account.js").ProfileShape;
|
||||
export type SealerSecret = import("./crypto/crypto.js").SealerSecret;
|
||||
export type SignerSecret = import("./crypto/crypto.js").SignerSecret;
|
||||
export type JsonObject = import("./jsonValue.js").JsonObject;
|
||||
}
|
||||
export * from "./exports.js";
|
||||
export { PureJSCrypto } from "./crypto/PureJSCrypto.js";
|
||||
|
||||
@@ -1,152 +1,2 @@
|
||||
import { WasmCrypto } from "./crypto/WasmCrypto.js";
|
||||
|
||||
import {
|
||||
CoValueCore,
|
||||
type CoValueUniqueness,
|
||||
MAX_RECOMMENDED_TX_SIZE,
|
||||
idforHeader,
|
||||
} from "./coValueCore.js";
|
||||
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
||||
import { LocalNode } from "./localNode.js";
|
||||
import { type RawCoValue } from "./coValue.js";
|
||||
import { RawCoMap } from "./coValues/coMap.js";
|
||||
import { RawCoList } from "./coValues/coList.js";
|
||||
import { RawCoStream, RawBinaryCoStream } from "./coValues/coStream.js";
|
||||
import {
|
||||
secretSeedLength,
|
||||
shortHashLength,
|
||||
StreamingHash,
|
||||
CryptoProvider,
|
||||
} from "./crypto/crypto.js";
|
||||
import { connectedPeers, Channel } from "./streamUtils.js";
|
||||
import { ControlledAgent, RawControlledAccount } from "./coValues/account.js";
|
||||
import type { Role } from "./permissions.js";
|
||||
import { rawCoIDtoBytes, rawCoIDfromBytes, isRawCoID } from "./ids.js";
|
||||
import { RawGroup, EVERYONE } from "./coValues/group.js";
|
||||
import type { Everyone } from "./coValues/group.js";
|
||||
import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
|
||||
import { parseJSON } from "./jsonStringify.js";
|
||||
import {
|
||||
RawAccount,
|
||||
RawProfile,
|
||||
accountHeaderForInitialAgentSecret,
|
||||
} from "./coValues/account.js";
|
||||
import { expectGroup } from "./typeUtils/expectGroup.js";
|
||||
import { isAccountID } from "./typeUtils/isAccountID.js";
|
||||
|
||||
import type { SessionID, AgentID } from "./ids.js";
|
||||
import type { CoID, AnyRawCoValue } from "./coValue.js";
|
||||
import type {
|
||||
BinaryStreamInfo,
|
||||
BinaryCoStreamMeta,
|
||||
} from "./coValues/coStream.js";
|
||||
import type { JsonValue } from "./jsonValue.js";
|
||||
import type {
|
||||
SyncMessage,
|
||||
Peer,
|
||||
IncomingSyncStream,
|
||||
OutgoingSyncQueue,
|
||||
} from "./sync.js";
|
||||
import { DisconnectedError, PingTimeoutError } from "./sync.js";
|
||||
import type { AgentSecret } from "./crypto/crypto.js";
|
||||
import type {
|
||||
RawAccountID,
|
||||
AccountMeta,
|
||||
RawAccountMigration,
|
||||
} from "./coValues/account.js";
|
||||
import type { InviteSecret } from "./coValues/group.js";
|
||||
import type * as Media from "./media.js";
|
||||
|
||||
type Value = JsonValue | AnyRawCoValue;
|
||||
|
||||
import { LSMStorage, BlockFilename, WalFilename } from "./storage/index.js";
|
||||
import { FileSystem } from "./storage/FileSystem.js";
|
||||
import { getPriorityFromHeader } from "./priority.js";
|
||||
|
||||
/** @hidden */
|
||||
export const cojsonInternals = {
|
||||
connectedPeers,
|
||||
rawCoIDtoBytes,
|
||||
rawCoIDfromBytes,
|
||||
secretSeedLength,
|
||||
shortHashLength,
|
||||
expectGroup,
|
||||
base64URLtoBytes,
|
||||
bytesToBase64url,
|
||||
parseJSON,
|
||||
accountOrAgentIDfromSessionID,
|
||||
isAccountID,
|
||||
accountHeaderForInitialAgentSecret,
|
||||
idforHeader,
|
||||
StreamingHash,
|
||||
Channel,
|
||||
getPriorityFromHeader,
|
||||
};
|
||||
|
||||
export {
|
||||
LocalNode,
|
||||
RawGroup,
|
||||
Role,
|
||||
EVERYONE,
|
||||
Everyone,
|
||||
RawCoMap,
|
||||
RawCoList,
|
||||
RawCoStream,
|
||||
RawBinaryCoStream,
|
||||
RawCoValue,
|
||||
CoID,
|
||||
AnyRawCoValue,
|
||||
RawAccount,
|
||||
RawAccountID,
|
||||
AccountMeta,
|
||||
RawAccountMigration,
|
||||
RawProfile as Profile,
|
||||
SessionID,
|
||||
Media,
|
||||
CoValueCore,
|
||||
ControlledAgent,
|
||||
RawControlledAccount,
|
||||
MAX_RECOMMENDED_TX_SIZE,
|
||||
JsonValue,
|
||||
Peer,
|
||||
BinaryStreamInfo,
|
||||
BinaryCoStreamMeta,
|
||||
AgentID,
|
||||
AgentSecret,
|
||||
InviteSecret,
|
||||
CryptoProvider,
|
||||
SyncMessage,
|
||||
isRawCoID,
|
||||
LSMStorage,
|
||||
WasmCrypto,
|
||||
};
|
||||
|
||||
export type {
|
||||
Value,
|
||||
FileSystem,
|
||||
BlockFilename,
|
||||
WalFilename,
|
||||
IncomingSyncStream,
|
||||
OutgoingSyncQueue,
|
||||
DisconnectedError,
|
||||
PingTimeoutError,
|
||||
CoValueUniqueness,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace CojsonInternalTypes {
|
||||
export type CoValueKnownState = import("./sync.js").CoValueKnownState;
|
||||
export type DoneMessage = import("./sync.js").DoneMessage;
|
||||
export type KnownStateMessage = import("./sync.js").KnownStateMessage;
|
||||
export type LoadMessage = import("./sync.js").LoadMessage;
|
||||
export type NewContentMessage = import("./sync.js").NewContentMessage;
|
||||
export type CoValueHeader = import("./coValueCore.js").CoValueHeader;
|
||||
export type Transaction = import("./coValueCore.js").Transaction;
|
||||
export type TransactionID = import("./ids.js").TransactionID;
|
||||
export type Signature = import("./crypto/crypto.js").Signature;
|
||||
export type RawCoID = import("./ids.js").RawCoID;
|
||||
export type ProfileShape = import("./coValues/account.js").ProfileShape;
|
||||
export type SealerSecret = import("./crypto/crypto.js").SealerSecret;
|
||||
export type SignerSecret = import("./crypto/crypto.js").SignerSecret;
|
||||
export type JsonObject = import("./jsonValue.js").JsonObject;
|
||||
}
|
||||
export * from "./exports.js";
|
||||
export { WasmCrypto } from "./crypto/WasmCrypto.js";
|
||||
|
||||
35
packages/jazz-tools/src/exports.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export type {
|
||||
InviteSecret,
|
||||
Peer,
|
||||
SessionID,
|
||||
AgentID,
|
||||
SyncMessage,
|
||||
CryptoProvider,
|
||||
CoValueUniqueness,
|
||||
} from "cojson";
|
||||
|
||||
export type { ID, CoValue } from "./internal.js";
|
||||
|
||||
export { Encoders, co } from "./internal.js";
|
||||
|
||||
export { CoMap, type CoMapInit } from "./internal.js";
|
||||
export { CoList } from "./internal.js";
|
||||
export { CoStream, BinaryCoStream } from "./internal.js";
|
||||
export { Group, Profile } from "./internal.js";
|
||||
export { Account, isControlledAccount, type AccountClass } from "./internal.js";
|
||||
export { ImageDefinition } from "./internal.js";
|
||||
export { CoValueBase, type CoValueClass } from "./internal.js";
|
||||
export type { DepthsIn, DeeplyLoaded } from "./internal.js";
|
||||
|
||||
export { loadCoValue, subscribeToCoValue } from "./internal.js";
|
||||
|
||||
export {
|
||||
type AuthMethod,
|
||||
type AuthResult,
|
||||
createJazzContext,
|
||||
fixedCredentialsAuth,
|
||||
ephemeralCredentialsAuth,
|
||||
AnonymousJazzAgent,
|
||||
createAnonymousJazzContext,
|
||||
randomSessionProvider,
|
||||
} from "./internal.js";
|
||||
@@ -1,40 +1,7 @@
|
||||
export * from "./exports.js";
|
||||
|
||||
export {
|
||||
cojsonInternals,
|
||||
MAX_RECOMMENDED_TX_SIZE,
|
||||
PureJSCrypto,
|
||||
} from "cojson/native";
|
||||
export type {
|
||||
InviteSecret,
|
||||
Peer,
|
||||
SessionID,
|
||||
AgentID,
|
||||
SyncMessage,
|
||||
CryptoProvider,
|
||||
CoValueUniqueness,
|
||||
} from "cojson";
|
||||
|
||||
export type { ID, CoValue } from "./internal.js";
|
||||
|
||||
export { Encoders, co } from "./internal.js";
|
||||
|
||||
export { CoMap, type CoMapInit } from "./internal.js";
|
||||
export { CoList } from "./internal.js";
|
||||
export { CoStream, BinaryCoStream } from "./internal.js";
|
||||
export { Group, Profile } from "./internal.js";
|
||||
export { Account, isControlledAccount, type AccountClass } from "./internal.js";
|
||||
export { ImageDefinition } from "./internal.js";
|
||||
export { CoValueBase, type CoValueClass } from "./internal.js";
|
||||
export type { DepthsIn, DeeplyLoaded } from "./internal.js";
|
||||
|
||||
export { loadCoValue, subscribeToCoValue } from "./internal.js";
|
||||
|
||||
export {
|
||||
type AuthMethod,
|
||||
type AuthResult,
|
||||
createJazzContext,
|
||||
fixedCredentialsAuth,
|
||||
ephemeralCredentialsAuth,
|
||||
AnonymousJazzAgent,
|
||||
createAnonymousJazzContext,
|
||||
randomSessionProvider,
|
||||
} from "./internal.js";
|
||||
|
||||
@@ -1,36 +1,3 @@
|
||||
export * from "./exports.js";
|
||||
|
||||
export { cojsonInternals, MAX_RECOMMENDED_TX_SIZE, WasmCrypto } from "cojson";
|
||||
export type {
|
||||
InviteSecret,
|
||||
Peer,
|
||||
SessionID,
|
||||
AgentID,
|
||||
SyncMessage,
|
||||
CryptoProvider,
|
||||
CoValueUniqueness,
|
||||
} from "cojson";
|
||||
|
||||
export type { ID, CoValue } from "./internal.js";
|
||||
|
||||
export { Encoders, co } from "./internal.js";
|
||||
|
||||
export { CoMap, type CoMapInit } from "./internal.js";
|
||||
export { CoList } from "./internal.js";
|
||||
export { CoStream, BinaryCoStream } from "./internal.js";
|
||||
export { Group, Profile } from "./internal.js";
|
||||
export { Account, isControlledAccount, type AccountClass } from "./internal.js";
|
||||
export { ImageDefinition } from "./internal.js";
|
||||
export { CoValueBase, type CoValueClass } from "./internal.js";
|
||||
export type { DepthsIn, DeeplyLoaded } from "./internal.js";
|
||||
|
||||
export { loadCoValue, subscribeToCoValue } from "./internal.js";
|
||||
|
||||
export {
|
||||
type AuthMethod,
|
||||
type AuthResult,
|
||||
createJazzContext,
|
||||
fixedCredentialsAuth,
|
||||
ephemeralCredentialsAuth,
|
||||
AnonymousJazzAgent,
|
||||
createAnonymousJazzContext,
|
||||
randomSessionProvider,
|
||||
} from "./internal.js";
|
||||
|
||||
4347
pnpm-lock.yaml
generated
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
root: "./",
|
||||
test: {
|
||||
coverage: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
provider: "istanbul",
|
||||
include: ["packages/*/src/**/*.ts"],
|
||||
exclude: ["packages/*/src/tests"],
|
||||
@@ -20,7 +20,6 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
include: ["packages/*/tests/**/*.test.ts"],
|
||||
watch: false,
|
||||
watchExclude: ["**/node_modules/**", "**/dist/**"],
|
||||
maxConcurrency: 5,
|
||||
},
|
||||
|
||||