diff --git a/.expo/README.md b/.expo/README.md index f7eb5fe..fc84881 100644 --- a/.expo/README.md +++ b/.expo/README.md @@ -1,8 +1,14 @@ -> Why do I have a folder named ".expo" in my project? -The ".expo" folder is created when an Expo project is started using "expo start" command. -> What do the files contain? -- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds. -- "settings.json": contains the server configuration that is used to serve the application manifest. -> Should I commit the ".expo" folder? -No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine. -Upon project creation, the ".expo" folder is already added to your ".gitignore" file. +> Why do I have a folder named ".expo" in my project? The ".expo" folder is +> created when an Expo project is started using "expo start" command. What do +> the files contain? + +- "devices.json": contains information about devices that have recently opened + this project. This is used to populate the "Development sessions" list in your + development builds. +- "settings.json": contains the server configuration that is used to serve the + application manifest. + > Should I commit the ".expo" folder? No, you should not share the ".expo" + > folder. It does not contain any information that is relevant for other + > developers working on the project, it is specific to your machine. Upon + > project creation, the ".expo" folder is already added to your ".gitignore" + > file. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..bd59833 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,41 @@ +name: Test and lint +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + push: + branches: [main] + pull_request: + branches: ["**"] + +jobs: + check: + name: Test and lint + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + + - name: Node setup + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + with: + cache-dependency-path: | + package.json + example/package.json + node-version: "20.x" + cache: "npm" + + - name: Install and build + run: | + npm i + npm run build + cd example && npm install + - name: Publish package for testing branch + run: npx pkg-pr-new publish || echo "Have you set up pkg-pr-new for this repo?" + - name: Test + run: | + npm run test + npm run typecheck + npm run lint diff --git a/.gitignore b/.gitignore index c4eda6d..f86397d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,11 +9,9 @@ dist-ssr explorations node_modules .eslintcache -# components are libraries! -package-lock.json - -# this is a package-json-redirect stub dir, see https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file -frontend/package.json +**/.expo +**/expo-env.d.ts # npm pack output *.tgz +*.tsbuildinfo .claude diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..f3877f7 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "trailingComma": "all", + "proseWrap": "always" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4e2ea5b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## 0.3.0 + +- Adds /test and /\_generated/component.js entrypoints +- Drops commonjs support +- Improves source mapping for generated files +- Changes to a statically generated component API diff --git a/CLAUDE.md b/CLAUDE.md index 8d6aa19..51e7c4c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,9 @@ # Claude Development Notes ## Codegen Command + After making changes to component functions, run codegen with: + ```bash cd example/ && npm run dev -- --once ``` @@ -10,18 +12,23 @@ cd example/ && npm run dev -- --once When adding new functions to this Convex component: -1. **Client function** (`src/client/index.ts`): - - Add method to `PushNotifications` class that calls `ctx.runMutation(this.component.public.functionName, { ...args, logLevel: this.config.logLevel })` +1. **Client function** (`src/client/index.ts`): + + - Add method to `PushNotifications` class that calls + `ctx.runMutation(this.component.public.functionName, { ...args, logLevel: this.config.logLevel })` - Follow existing patterns for argument types and return types 2. **Component function** (`src/component/public.ts`): + - Define args schema using `v.object()` - Export mutation/query with proper return type - Call `ensureCoordinator(ctx)` after processing for coordination 3. **Batch functions**: - - Use existing handler functions (like `sendPushNotificationHandler`) in loops + + - Use existing handler functions (like `sendPushNotificationHandler`) in + loops - Call `ensureCoordinator` once after all processing - Return array of results matching individual function return types -4. **Always run codegen** after changes to regenerate types \ No newline at end of file +4. **Always run codegen** after changes to regenerate types diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a101ea6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +# Developing guide + +## Running locally + +```sh +npm run setup +npm run dev +``` + +Setup should: + +1. Install dependencies in the root and example directories +1. Build the component +1. Create a Convex project if not already connected, and deploy to it once. +1. Set the `EXPO_PUBLIC_CONVEX_URL` environment variable for the example expo + app (needs to be set in the example expo app's `.env.local` file) + +## Testing + +```sh +npm run clean +npm run build +npm run typecheck +npm run lint +npm run test +``` + +## Deploying + +### Building a one-off package + +```sh +npm run clean +npm ci +npm pack +``` + +### Deploying a new version + +```sh +npm run release +``` + +or for alpha release: + +```sh +npm run alpha +``` diff --git a/README.md b/README.md index ad8784b..6fc82a4 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,10 @@ -This is a Convex component that integrates with [Expo's push notification API](https://docs.expo.dev/push-notifications/overview/) -to allow sending mobile push notifications to users of your app. It will batch calls to Expo's API and handle retrying delivery. +This is a Convex component that integrates with +[Expo's push notification API](https://docs.expo.dev/push-notifications/overview/) +to allow sending mobile push notifications to users of your app. It will batch +calls to Expo's API and handle retrying delivery.
Demo GIF @@ -51,11 +53,12 @@ export const sendPushNotification = mutation({ ## Pre-requisite: Convex -You'll need an existing Convex project to use the component. -Convex is a hosted backend platform, including a database, serverless functions, -and a ton more you can learn about [here](https://docs.convex.dev/get-started). +You'll need an existing Convex project to use the component. Convex is a hosted +backend platform, including a database, serverless functions, and a ton more you +can learn about [here](https://docs.convex.dev/get-started). -Run `npm create convex` or follow any of the [quickstarts](https://docs.convex.dev/home) to set one up. +Run `npm create convex` or follow any of the +[quickstarts](https://docs.convex.dev/home) to set one up. ## Installation @@ -65,12 +68,13 @@ Install the component package: npm i @convex-dev/expo-push-notifications ``` -Create a `convex.config.ts` file in your app's `convex/` folder and install the component by calling `use`: +Create a `convex.config.ts` file in your app's `convex/` folder and install the +component by calling `use`: ```ts // convex/convex.config.ts import { defineApp } from "convex/server"; -import pushNotifications from "@convex-dev/expo-push-notifications/convex.config"; +import pushNotifications from "@convex-dev/expo-push-notifications/convex.config.js"; const app = defineApp(); app.use(pushNotifications); @@ -88,7 +92,8 @@ import { PushNotifications } from "@convex-dev/expo-push-notifications"; const pushNotifications = new PushNotifications(components.pushNotifications); ``` -It takes in an optional type parameter (defaulting to `Id<"users">`) for the type to use as a unique identifier for push notification recipients: +It takes in an optional type parameter (defaulting to `Id<"users">`) for the +type to use as a unique identifier for push notification recipients: ```ts import { PushNotifications } from "@convex-dev/expo-push-notifications"; @@ -96,13 +101,15 @@ import { PushNotifications } from "@convex-dev/expo-push-notifications"; export type Email = string & { __isEmail: true }; const pushNotifications = new PushNotifications( - components.pushNotifications + components.pushNotifications, ); ``` ## Registering a user for push notifications -Get a user's push notification token following the Expo documentation [here](https://docs.expo.dev/push-notifications/push-notifications-setup/#registering-for-push-notifications), and record it using a Convex mutation: +Get a user's push notification token following the Expo documentation +[here](https://docs.expo.dev/push-notifications/push-notifications-setup/#registering-for-push-notifications), +and record it using a Convex mutation: ```ts // convex/example.ts @@ -118,9 +125,11 @@ export const recordPushNotificationToken = mutation({ }); ``` -You can pause and resume push notification sending for a user using the `pausePushNotifications` and `resumePushNotifications` methods. +You can pause and resume push notification sending for a user using the +`pausePushNotifications` and `resumePushNotifications` methods. -To determine if a user has a token and their pause status, you can use `getStatusForUser`. +To determine if a user has a token and their pause status, you can use +`getStatusForUser`. ## Send notifications @@ -139,14 +148,16 @@ export const sendPushNotification = mutation({ }); ``` -You can use the ID returned from `sendPushNotifications` to query the status of the notification using `getNotification`. -Using this in a query allows you to subscribe to the status of a notification. +You can use the ID returned from `sendPushNotifications` to query the status of +the notification using `getNotification`. Using this in a query allows you to +subscribe to the status of a notification. You can also view all notifications for a user with `getNotificationsForUser`. ## Troubleshooting -To add more logging, provide `PushNotifications` with a `logLevel` in the constructor: +To add more logging, provide `PushNotifications` with a `logLevel` in the +constructor: ```ts const pushNotifications = new PushNotifications(components.pushNotifications, { @@ -154,6 +165,7 @@ const pushNotifications = new PushNotifications(components.pushNotifications, { }); ``` -The push notification sender can be shutdown gracefully, and then restarted using the `shutdown` and `restart` methods. +The push notification sender can be shutdown gracefully, and then restarted +using the `shutdown` and `restart` methods. diff --git a/commonjs.json b/commonjs.json deleted file mode 100644 index 67a53cb..0000000 --- a/commonjs.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["src/**/*"], - "exclude": ["src/**/*.test.*", "../src/package.json"], - "compilerOptions": { - "noEmit": false, - "outDir": "./dist/commonjs" - } -} diff --git a/convex.json b/convex.json new file mode 100644 index 0000000..8cd4cfd --- /dev/null +++ b/convex.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/convex/schemas/convex.schema.json", + "functions": "example/convex", + "codegen": { + "legacyComponentApi": false + } +} diff --git a/eslint.config.js b/eslint.config.js index 6f7c313..921025c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,34 +3,42 @@ import pluginJs from "@eslint/js"; import tseslint from "typescript-eslint"; export default [ - { files: ["src/**/*.{js,mjs,cjs,ts,tsx}"] }, { ignores: [ "dist/**", - "eslint.config.js", + "*.config.{js,mjs,ts}", "**/_generated/", "node10stubs.mjs", + "example/.expo/**", + "example/{app,assets,components,constants,hooks,scripts}/**", + "example/**/*.config.{js,mjs,ts}", + "example/expo-env.d.ts", ], }, { + files: [ + "src/**/*.{js,mjs,cjs,ts,tsx}", + "example/convex/*.{js,mjs,cjs,ts,tsx}", + ], languageOptions: { - globals: globals.worker, parser: tseslint.parser, - parserOptions: { - project: true, - tsconfigRootDir: ".", + project: ["./tsconfig.json", "./example/convex/tsconfig.json"], + tsconfigRootDir: import.meta.dirname, }, }, }, pluginJs.configs.recommended, ...tseslint.configs.recommended, + // Convex code - Worker environment { + files: ["src/**/*.{ts,tsx}", "example/convex/**/*.{ts,tsx}"], + languageOptions: { + globals: globals.worker, + }, rules: { "@typescript-eslint/no-floating-promises": "error", - "eslint-comments/no-unused-disable": "off", - - // allow (_arg: number) => {} and const _foo = 1; + "@typescript-eslint/no-explicit-any": "off", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": [ "warn", @@ -39,6 +47,15 @@ export default [ varsIgnorePattern: "^_", }, ], + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": [ + "error", + { + allowShortCircuit: true, + allowTernary: true, + allowTaggedTemplates: true, + }, + ], }, }, ]; diff --git a/esm.json b/esm.json deleted file mode 100644 index cee9034..0000000 --- a/esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["src/**/*"], - "exclude": ["src/**/*.test.*", "../src/package.json"], - "compilerOptions": { - "noEmit": false, - "outDir": "./dist/esm" - } -} diff --git a/example/.env.local.example b/example/.env.local.example new file mode 100644 index 0000000..b46862e --- /dev/null +++ b/example/.env.local.example @@ -0,0 +1,2 @@ +# Get this from ../../.env.local 'CONVEX_URL' +EXPO_PUBLIC_CONVEX_URL=https://your-animal-123.convex.cloud diff --git a/example/.expo/README.md b/example/.expo/README.md deleted file mode 100644 index f7eb5fe..0000000 --- a/example/.expo/README.md +++ /dev/null @@ -1,8 +0,0 @@ -> Why do I have a folder named ".expo" in my project? -The ".expo" folder is created when an Expo project is started using "expo start" command. -> What do the files contain? -- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds. -- "settings.json": contains the server configuration that is used to serve the application manifest. -> Should I commit the ".expo" folder? -No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine. -Upon project creation, the ".expo" folder is already added to your ".gitignore" file. diff --git a/example/.expo/devices.json b/example/.expo/devices.json deleted file mode 100644 index 5efff6c..0000000 --- a/example/.expo/devices.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "devices": [] -} diff --git a/example/.expo/types/router.d.ts b/example/.expo/types/router.d.ts deleted file mode 100644 index c2171ea..0000000 --- a/example/.expo/types/router.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable */ -import * as Router from 'expo-router'; - -export * from 'expo-router'; - -declare module 'expo-router' { - export namespace ExpoRouter { - export interface __routes { - hrefInputParams: { pathname: Router.RelativePathString, params?: Router.UnknownInputParams } | { pathname: Router.ExternalPathString, params?: Router.UnknownInputParams } | { pathname: `/App`; params?: Router.UnknownInputParams; } | { pathname: `/Demo`; params?: Router.UnknownInputParams; } | { pathname: `/_sitemap`; params?: Router.UnknownInputParams; }; - hrefOutputParams: { pathname: Router.RelativePathString, params?: Router.UnknownOutputParams } | { pathname: Router.ExternalPathString, params?: Router.UnknownOutputParams } | { pathname: `/App`; params?: Router.UnknownOutputParams; } | { pathname: `/Demo`; params?: Router.UnknownOutputParams; } | { pathname: `/_sitemap`; params?: Router.UnknownOutputParams; }; - href: Router.RelativePathString | Router.ExternalPathString | `/App${`?${string}` | `#${string}` | ''}` | `/Demo${`?${string}` | `#${string}` | ''}` | `/_sitemap${`?${string}` | `#${string}` | ''}` | { pathname: Router.RelativePathString, params?: Router.UnknownInputParams } | { pathname: Router.ExternalPathString, params?: Router.UnknownInputParams } | { pathname: `/App`; params?: Router.UnknownInputParams; } | { pathname: `/Demo`; params?: Router.UnknownInputParams; } | { pathname: `/_sitemap`; params?: Router.UnknownInputParams; }; - } - } -} diff --git a/example/.gitignore b/example/.gitignore index 60c36f1..f8c6c2e 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1,22 +1,43 @@ -!**/glob-import/dir/node_modules -.DS_Store -.idea -*.cpuprofile -*.local -*.log -/.vscode/ -/docs/.vitepress/cache -dist -dist-ssr -explorations -node_modules -playground-temp -temp -TODOs.md -.eslintcache - -# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb -# The following patterns were generated by expo-cli +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ +# Expo +.expo/ +dist/ +web-build/ expo-env.d.ts -# @end expo-cli \ No newline at end of file + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +app-example + +# generated native folders +/ios +/android diff --git a/example/.vscode/extensions.json b/example/.vscode/extensions.json new file mode 100644 index 0000000..b7ed837 --- /dev/null +++ b/example/.vscode/extensions.json @@ -0,0 +1 @@ +{ "recommendations": ["expo.vscode-expo-tools"] } diff --git a/example/.vscode/settings.json b/example/.vscode/settings.json new file mode 100644 index 0000000..e2798e4 --- /dev/null +++ b/example/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit", + "source.sortMembers": "explicit" + } +} diff --git a/example/README.md b/example/README.md index c894719..fa437f8 100644 --- a/example/README.md +++ b/example/README.md @@ -1,5 +1,74 @@ -# Example app +# Welcome to your Expo app 👋 -Components need an app that uses them in order to run codegen. An example app is also useful -for testing and documentation. +This is an [Expo](https://expo.dev) project created with +[`create-expo-app`](https://www.npmjs.com/package/create-expo-app). +## How this was modified from the standard Expo template + +```sh +npx create-expo-app@latest # name it example +cd example +npm i convex +npx expo install expo-notifications expo-device expo-constants +``` + +- Create App.tsx based on the push notifications setup instructions. +- Copy in the Demo.tsx and convex/ folder from the old project. +- Add Convex to the layout and show the App there. +- Add `"extra": { "eas": { "projectId": "your-project-id" } }` to app.json. + +## Get started + +1. Install dependencies + + ```bash + npm install + ``` + +2. Start the app + + ```bash + npx expo start + ``` + +In the output, you'll find options to open the app in a + +- [development build](https://docs.expo.dev/develop/development-builds/introduction/) +- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) +- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) +- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app + development with Expo + +You can start developing by editing the files inside the **app** directory. This +project uses [file-based routing](https://docs.expo.dev/router/introduction). + +## Get a fresh project + +When you're ready, run: + +```bash +npm run reset-project +``` + +This command will move the starter code to the **app-example** directory and +create a blank **app** directory where you can start developing. + +## Learn more + +To learn more about developing your project with Expo, look at the following +resources: + +- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into + advanced topics with our [guides](https://docs.expo.dev/guides). +- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a + step-by-step tutorial where you'll create a project that runs on Android, iOS, + and the web. + +## Join the community + +Join our community of developers creating universal apps. + +- [Expo on GitHub](https://github.com/expo/expo): View our open source platform + and contribute. +- [Discord community](https://chat.expo.dev): Chat with Expo users and ask + questions. diff --git a/example/app.json b/example/app.json index 6d076bd..a95ebfa 100644 --- a/example/app.json +++ b/example/app.json @@ -1,40 +1,54 @@ { "expo": { - "name": "components", - "slug": "push-notifications", + "name": "example", + "slug": "example", "version": "1.0.0", "orientation": "portrait", - "scheme": "myapp", + "icon": "./assets/images/icon.png", + "scheme": "expo54", "userInterfaceStyle": "automatic", - "extra": { - "eas": { - "projectId": "5b3c07e0-6b9f-4a1f-a9b9-ac4aa70fc3d5" - }, - "router": { - "origin": false - } - }, - "splash": { - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, + "newArchEnabled": true, "ios": { "supportsTablet": true }, "android": { "adaptiveIcon": { - "backgroundColor": "#ffffff" - } + "backgroundColor": "#E6F4FE", + "foregroundImage": "./assets/images/android-icon-foreground.png", + "backgroundImage": "./assets/images/android-icon-background.png", + "monochromeImage": "./assets/images/android-icon-monochrome.png" + }, + "edgeToEdgeEnabled": true, + "predictiveBackGestureEnabled": false }, "web": { - "bundler": "metro", - "output": "static" + "output": "static", + "favicon": "./assets/images/favicon.png" }, - "plugins": ["expo-router"], - "experiments": { - "typedRoutes": true + "extra": { + "eas": { + "projectId": "5b3c07e0-6b9f-4a1f-a9b9-ac4aa70fc3d5" + } }, - "owner": "sshader", - "entryPoint": "app/App.tsx" + "plugins": [ + "expo-router", + "expo-notifications", + [ + "expo-splash-screen", + { + "image": "./assets/images/splash-icon.png", + "imageWidth": 200, + "resizeMode": "contain", + "backgroundColor": "#ffffff", + "dark": { + "backgroundColor": "#000000" + } + } + ] + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + } } } diff --git a/example/app/(tabs)/_layout.tsx b/example/app/(tabs)/_layout.tsx new file mode 100644 index 0000000..892de32 --- /dev/null +++ b/example/app/(tabs)/_layout.tsx @@ -0,0 +1,29 @@ +import { Tabs } from "expo-router"; +import React from "react"; + +import { IconSymbol } from "@/components/ui/icon-symbol"; +import { Colors } from "@/constants/theme"; +import { useColorScheme } from "@/hooks/use-color-scheme"; + +export default function TabLayout() { + const colorScheme = useColorScheme(); + + return ( + + ( + + ), + }} + /> + + ); +} diff --git a/example/app/(tabs)/index.tsx b/example/app/(tabs)/index.tsx new file mode 100644 index 0000000..6655161 --- /dev/null +++ b/example/app/(tabs)/index.tsx @@ -0,0 +1,157 @@ +import Constants from "expo-constants"; +import * as Device from "expo-device"; +import * as Notifications from "expo-notifications"; +import { useEffect, useState } from "react"; +import { Platform } from "react-native"; +import { Demo } from "../../components/Demo"; +import { ThemedView } from "../../components/themed-view"; +import { ThemedText } from "../../components/themed-text"; + +Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldPlaySound: true, + shouldSetBadge: true, + shouldShowBanner: true, + shouldShowList: true, + }), +}); + +// async function sendPushNotification(expoPushToken: string) { +// const message = { +// to: expoPushToken, +// sound: "default", +// title: "Original Title", +// body: "And here is the body!", +// data: { someData: "goes here" }, +// }; + +// await fetch("https://exp.host/--/api/v2/push/send", { +// method: "POST", +// headers: { +// Accept: "application/json", +// "Accept-encoding": "gzip, deflate", +// "Content-Type": "application/json", +// }, +// body: JSON.stringify(message), +// }); +// } + +function handleRegistrationError(errorMessage: string) { + alert(errorMessage); + throw new Error(errorMessage); +} + +async function registerForPushNotificationsAsync() { + if (Platform.OS === "android") { + await Notifications.setNotificationChannelAsync("default", { + name: "default", + importance: Notifications.AndroidImportance.MAX, + vibrationPattern: [0, 250, 250, 250], + lightColor: "#FF231F7C", + }); + } + + if (Device.isDevice) { + const { status: existingStatus } = + await Notifications.getPermissionsAsync(); + let finalStatus = existingStatus; + if (existingStatus !== "granted") { + const { status } = await Notifications.requestPermissionsAsync(); + finalStatus = status; + } + if (finalStatus !== "granted") { + handleRegistrationError( + "Permission not granted to get push token for push notification!", + ); + return; + } + const projectId = + Constants?.expoConfig?.extra?.eas?.projectId ?? + Constants?.easConfig?.projectId; + if (!projectId) { + handleRegistrationError("Project ID not found"); + } + try { + const pushTokenString = ( + await Notifications.getExpoPushTokenAsync({ + projectId, + }) + ).data; + console.log(pushTokenString); + return pushTokenString; + } catch (e: unknown) { + handleRegistrationError(`${e}`); + } + } else { + handleRegistrationError("Must use physical device for push notifications"); + } +} + +export default function App() { + const [expoPushToken, setExpoPushToken] = useState(""); + const [error, setError] = useState(undefined); + const [notification, setNotification] = useState< + Notifications.Notification | undefined + >(undefined); + + useEffect(() => { + // check if it's on the web + if (Platform.OS === "web") { + setError("Push notifications are not supported on web"); + return; + } + registerForPushNotificationsAsync() + .then((token) => { + setExpoPushToken(token ?? ""); + setError(undefined); + }) + .catch((error: any) => setError(`${error}`)); + + const notificationListener = Notifications.addNotificationReceivedListener( + (notification) => { + setNotification(notification); + }, + ); + + const responseListener = + Notifications.addNotificationResponseReceivedListener((response) => { + console.log(response); + }); + + return () => { + notificationListener.remove(); + responseListener.remove(); + }; + }, []); + + return ( + + {error ? ( + Error: {error} + ) : ( + Your Expo push token: {expoPushToken} + )} + + + Title: {notification && notification.request.content.title}{" "} + + + Body: {notification && notification.request.content.body} + + + Data:{" "} + {notification && JSON.stringify(notification.request.content.data)} + + + + {/*