diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..6a31108 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,27 @@ +name: Setup +description: Setup Node.js and install dependencies + +runs: + using: composite + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + + - name: Cache dependencies + id: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn/install-state.gz + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }} + restore-keys: | + ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + ${{ runner.os }}-yarn- + + - name: Install dependencies + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install --immutable + shell: bash diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2a72cda..c710f4f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,10 +10,9 @@ Please explain the changes you made Screenshots if the PR has visual changes. -| android | ios | -|--------|--------| -| Screenshot 2025-03-27 at 9 11 22 PM | Screenshot 2025-03-27 at 9 11 22 PM | -| Screenshot 2025-03-27 at 9 11 22 PM | Screenshot 2025-03-27 at 9 11 22 PM | +| android | ios | +| ------------------ | -------------- | +| ANDROID_SCREENSHOT | IOS_SCREENSHOT | ## 🧐 Testing diff --git a/.github/workflows/build-and-commit.yml b/.github/workflows/build-and-commit.yml new file mode 100644 index 0000000..8c8d836 --- /dev/null +++ b/.github/workflows/build-and-commit.yml @@ -0,0 +1,56 @@ +# name: Build and Commit + +# on: +# push: +# branches: +# - main +# pull_request: +# branches: +# - main +# workflow_dispatch: + +# permissions: +# contents: write # Required for committing changes + +# jobs: +# build-and-commit: +# runs-on: ubuntu-latest +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# with: +# fetch-depth: 0 # Fetch all history for proper versioning +# ref: ${{ github.event.pull_request.head.sha || github.sha }} + +# - name: Setup Node.js +# uses: actions/setup-node@v4 +# with: +# node-version: '20' +# cache: 'yarn' + +# - name: Install dependencies +# run: yarn install + +# - name: Build package +# run: yarn prepare + +# - name: Configure Git +# run: | +# git config --global user.name 'GitHub Actions' +# git config --global user.email 'github-actions@github.com' + +# - name: Commit build results +# run: | +# git add build/ -f +# if git diff --quiet && git diff --staged --quiet; then +# echo "No changes to commit" +# exit 0 +# fi + +# if [[ "${{ github.event_name }}" == "pull_request" ]]; then +# git commit -m "chore: update build files [skip ci]" +# git push origin HEAD:refs/heads/${{ github.head_ref }} +# else +# git commit -m "chore: update build files [skip ci]" +# git push origin HEAD:refs/heads/${{ github.ref_name }} +# fi diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..8e718c2 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,74 @@ +name: Build and Lint +on: + push: + branches: + - main + pull_request: + branches: + - main + merge_group: + types: + - checks_requested + +permissions: + contents: read # Required for checking out code + checks: write # Required for updating check status + security-events: write # Required for security scanning + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Dependency Review + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: high + + type-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Type check + run: yarn tsc --noEmit + build-library: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Build package + run: yarn prepare + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Lint files + run: yarn lint + + misspell: + name: runner / misspell + runs-on: ubuntu-latest + steps: + - name: Check out code. + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: misspell + uses: reviewdog/action-misspell@9daa94af4357dddb6fd3775de806bc0a8e98d3e4 # v1.26.3 + with: + github_token: ${{ secrets.github_token }} + locale: 'US' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..6d75a05 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,50 @@ +name: Unit Tests +on: + push: + branches: + - main + pull_request: + branches: + - main + merge_group: + types: + - checks_requested + +permissions: + contents: read # Required for checking out code + checks: write # Required for updating check status + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run unit tests with coverage + run: yarn test:coverage --maxWorkers=2 + + - name: Check coverage thresholds + run: | + COVERAGE_THRESHOLD=80 + COVERAGE=$(cat coverage/coverage-summary.json | jq -r '.total.lines.pct') + if (( $(echo "$COVERAGE < $COVERAGE_THRESHOLD" | bc -l) )); then + echo "Coverage ($COVERAGE%) is below threshold ($COVERAGE_THRESHOLD%)" + exit 1 + fi + echo "Coverage ($COVERAGE%) is above threshold ($COVERAGE_THRESHOLD%)" + + - name: Upload coverage to QLTY + uses: qltysh/qlty-action/coverage@v1 + with: + token: ${{ secrets.QLTY_COVERAGE_TOKEN }} + files: coverage/lcov.info + - name: Add test coverage comment + id: coverageComment + uses: MishaKav/jest-coverage-comment@main + with: + hide-comment: false + coverage-summary-path: ./coverage/coverage-summary.json diff --git a/README.md b/README.md index df1d8de..dfcfd6e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ -![@iterable/expo-plugin](./assets/Iterable-Logo.png "@iterable/expo-plugin") -# @iterable/expo-plugin +![@iterable/expo-plugin](./assets/Iterable-Logo.png '@iterable/expo-plugin') +# @iterable/expo-plugin +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/your-username/your-gist-id/raw/coverage.json)](https://github.com/Iterable/iterable-expo-plugin/actions/workflows/test.yml) +[![Code +Coverage](https://qlty.sh/badges/7b2b3c7b-27c0-4fed-af07-57b151b30a59/test_coverage.svg)](https://qlty.sh/gh/Iterable/projects/iterable-expo-plugin) +[![Maintainability](https://qlty.sh/badges/7b2b3c7b-27c0-4fed-af07-57b151b30a59/maintainability.svg)](https://qlty.sh/gh/Iterable/projects/iterable-expo-plugin) This config plugin automatically configures your Expo app to work with [@iterable/react-native-sdk](https://github.com/Iterable/react-native-sdk) when the native code is generated through `expo prebuild`. - @@ -33,48 +37,58 @@ the native code is generated through `expo prebuild`. - ## 🚀 Quick Start 1. Install the plugin and `@iterable/react-native-sdk` by running the following in your terminal: - ```bash - npx expo install @iterable/expo-plugin @iterable/react-native-sdk - ``` + + ```bash + npx expo install @iterable/expo-plugin @iterable/react-native-sdk + ``` + 2. Add the plugin to to your `app.json` or `app.config.js` - ```json - { - "expo": { - "plugins": [ - ["@iterable/expo-plugin", {}] - ] - } - } - ``` + + ```json + { + "expo": { + "plugins": [["@iterable/expo-plugin", {}]] + } + } + ``` + 3. After installing and configuring the plugin, rebuild your native projects: - ```bash - npx expo prebuild --clean - ``` - **WARNING**: `prebuild` will delete everything in your ios/android directories. + + ```bash + npx expo prebuild --clean + ``` + + **WARNING**: `prebuild` will delete everything in your ios/android directories. + 4. Run your ios or android simulator: - - ios: - ```bash - npx expo run:ios - ``` - - android: - ```bash - npx expo run:android - ``` -5. Import `@iterable/react-native-sdk` and use as needed. EG: - ```tsx - import {useEffect} from 'react'; - import {Iterable, IterableConfig} from '@iterable/react-native-sdk'; - - const App = () => { - useEffect(() => { - Iterable.initialize('MY_API_KEY', new IterableConfig()); - }, []); - } - ``` + + - ios: + + ```bash + npx expo run:ios + ``` + + - android: + + ```bash + npx expo run:android + ``` + +5. Import `@iterable/react-native-sdk` and use as needed. EG: + + ```tsx + import { useEffect } from 'react'; + import { Iterable, IterableConfig } from '@iterable/react-native-sdk'; + + const App = () => { + useEffect(() => { + Iterable.initialize('MY_API_KEY', new IterableConfig()); + }, []); + }; + ``` ## 🔧 Configuration @@ -84,12 +98,15 @@ Add the plugin to your `app.json` or `app.config.js`: { "expo": { "plugins": [ - ["@iterable/expo-plugin", { - "appEnvironment": "development", - "autoConfigurePushNotifications": true, - "enableTimeSensitivePush": true, - "requestPermissionsForPushNotifications": true, - }] + [ + "@iterable/expo-plugin", + { + "appEnvironment": "development", + "autoConfigurePushNotifications": true, + "enableTimeSensitivePush": true, + "requestPermissionsForPushNotifications": true + } + ] ] } } @@ -97,15 +114,16 @@ Add the plugin to your `app.json` or `app.config.js`: ### Plugin Options -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `appEnvironment` | `'development'` \| `'production'` | `'development'` | The environment of your app | -| `autoConfigurePushNotifications` | boolean | `true` | Whether to automatically configure push notifications. Set to `false` if you want to configure push notifications manually.

**WARNING**: Iterable cannot guarantee compatibility with custom push notification configurations. | -| `enableTimeSensitivePush` | boolean | `true` | Whether to enable time-sensitive push notifications (iOS only) | -| `requestPermissionsForPushNotifications` | boolean | `false` | Whether to request permissions for push notifications (iOS only) | +| Option | Type | Default | Description | +| ---------------------------------------- | --------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `appEnvironment` | `'development'` \| `'production'` | `'development'` | The environment of your app | +| `autoConfigurePushNotifications` | boolean | `true` | Whether to automatically configure push notifications. Set to `false` if you want to configure push notifications manually.

**WARNING**: Iterable cannot guarantee compatibility with custom push notification configurations. | +| `enableTimeSensitivePush` | boolean | `true` | Whether to enable time-sensitive push notifications (iOS only) | +| `requestPermissionsForPushNotifications` | boolean | `false` | Whether to request permissions for push notifications (iOS only) | ### Disabling New Architecture -`@iterable/react-native-sdk` is *NOT* compatible with Reacts New Architecture, + +`@iterable/react-native-sdk` is _NOT_ compatible with Reacts New Architecture, so this needs to be disabled in your `app.json`: ```json @@ -118,16 +136,18 @@ so this needs to be disabled in your `app.json`: ### Adding push capabilities -#### iOS +#### iOS + - [Configure push notifications for iOS in Iterable](https://support.iterable.com/hc/en-us/articles/115000315806-Setting-up-iOS-Push-Notifications) #### Android - [Configure push notifications for Android in Iterable](https://support.iterable.com/hc/en-us/articles/115000331943-Setting-up-Android-Push-Notifications) -- Place your `google-services.json` file in the root of the *example* +- Place your `google-services.json` file in the root of the _example_ directory - In `app.json`, add the path to the `google-services.json` file to - `expo.android.googleServicesFile`. EG: + `expo.android.googleServicesFile`. EG: + ```json { "expo": { @@ -138,28 +158,30 @@ so this needs to be disabled in your `app.json`: } ``` -### Adding Deeplinks +### Adding Deeplinks Deep linking allows users to navigate to specific screens in your app using URLs. To set up deep linking in your **Expo** application, [configure deep links in Iterable](https://support.iterable.com/hc/en-us/articles/115002651226-Configuring-Deep-Links-for-Email-or-SMS), -then follow the below instructions. +then follow the below instructions. #### iOS + To add deeplinks to your Expo app for use with Iterable on iOS devices, add associated domains to your `app.json` under the iOS configuration. -EG: +EG: + ```json { "expo": { "ios": { "associatedDomains": [ - "applinks:expo.dev", - "applinks:iterable.com", - "applinks:links.anotherone.com" - ] + "applinks:expo.dev", + "applinks:iterable.com", + "applinks:links.anotherone.com" + ] } } } @@ -173,11 +195,13 @@ See further documentation about how expo setup of iOS Universal Links [here](https://docs.expo.dev/linking/ios-universal-links/). #### Android + To add deeplinks to your Expo app for use with Iterable on Android devices, add URL schemes and intent filters to your `app.json` under the Android -configuration. These would be in `expo.android.intentFilters`. +configuration. These would be in `expo.android.intentFilters`. EG: + ```json { "expo": { @@ -186,7 +210,7 @@ EG: { "action": "MAIN", "category": ["LAUNCHER"], - "autoVerify": true, + "autoVerify": true }, { "action": "VIEW", @@ -211,21 +235,26 @@ See further documentation about how expo setup of Android App Links [here](https://docs.expo.dev/linking/android-app-links/). ### Configuring [ProGuard](https://reactnative.dev/docs/signed-apk-android#enabling-proguard-to-reduce-the-size-of-the-apk-optional) + If you're using ProGuard when building your Android app, you will need to add this line of ProGuard configuration to your build: `-keep class org.json.** { *; }`. Below is how to do this using Expo: + 1. Add the [expo-build-properties](https://www.npmjs.com/package/expo-build-properties) - plugin by running: - ```bash - npx expo install expo-build-properties - ``` -2. Add the plugin to your *app.json* file + plugin by running: + + ```bash + npx expo install expo-build-properties + ``` + +2. Add the plugin to your _app.json_ file 3. To the plugin options, add `{"android":{"extraProguardRules":"-keep class org.json.** { *; }"}}` -The overall code in your *app.json* file should look something like this: +The overall code in your _app.json_ file should look something like this: + ```json { "expo": { @@ -248,18 +277,18 @@ Learn more in the [Configure Proguard](https://support.iterable.com/hc/en-us/art ## ✅ Requirements and Limitations - New Architecture needs to be disabled, as `@iterable/react-native-sdk` does - not support it. See [Disabling New Architecture](#disabling-new-architecture) + not support it. See [Disabling New Architecture](#disabling-new-architecture) for instructions on how to disable it. - Your expo app needs to be run as a [development build](https://docs.expo.dev/develop/development-builds/introduction/) instead - of through Expo Go. Both + of through Expo Go. Both `@iterable/iterable-expo-plugin` and `@iterable/react-native-sdk` will **NOT** work in Expo Go as they are reliant on native code, which Expo Go [does not support](https://expo.dev/blog/expo-go-vs-development-builds#expo-go-limitations). - `@iterable/iterable-expo-plugin` is intended for managed workflows, and will - overwrite the files in your `ios` and `android` directories. Any manual - changes to those directories will be overwritten on the next build. -- This plugin has been tested on Expo version 52+. While it may work on + overwrite the files in your `ios` and `android` directories. Any manual + changes to those directories will be overwritten on the next build. +- This plugin has been tested on Expo version 52+. While it may work on previous versions, they are not supported. ## 🎉 Features @@ -269,12 +298,14 @@ Learn more in the [Configure Proguard](https://support.iterable.com/hc/en-us/art The plugin automatically configures push notifications for both iOS and Android platforms. #### iOS + - Adds bridge to native Iterable code - Sets up notification service extension - Configures required entitlements - Handles notification permissions #### Android + - Adds bridge to native Iterable code - Configures Firebase integration - Sets up notification handling @@ -285,10 +316,12 @@ The plugin automatically configures push notifications for both iOS and Android The plugin configures deep linking capabilities for both platforms. #### iOS + - Sets up Universal Links - Configures associated domains #### Android + - Configures App Links - Sets up intent filters @@ -299,6 +332,7 @@ The plugin configures deep linking capabilities for both platforms. If you encounter the error "Your JavaScript code tried to access a native module that doesn't exist in this development client", try: 1. Clean your project: + ```bash rm -rf node_modules rm -rf ios/Pods @@ -306,11 +340,13 @@ yarn cache clean ``` 2. Reinstall dependencies: + ```bash yarn install ``` 3. Rebuild native projects: + ```bash npx expo prebuild --clean cd ios && pod install && cd .. @@ -318,8 +354,8 @@ cd ios && pod install && cd .. ### Failed to delete [ios|android] code: ENOTEMPTY: directory not empty -Sometimes this error appears when running `npx expo prebuild --clean`. It seems -to be an intermittent bug within expo. It usually works upon running the same +Sometimes this error appears when running `npx expo prebuild --clean`. It seems +to be an intermittent bug within expo. It usually works upon running the same command a second time, so just try again. ## 👏 Contributing @@ -334,11 +370,13 @@ for details. ## 💬 Support For support, please: + 1. Check the [documentation](https://github.com/Iterable/iterable-expo-plugin#readme) 2. Open an [issue](https://github.com/Iterable/iterable-expo-plugin/issues) 3. Contact [Iterable support](https://support.iterable.com/hc/en-us/requests/new) ## 📚 Further Reading + - [Installing Iterables React Native SDK](https://support.iterable.com/hc/en-us/articles/360045714132-Installing-Iterable-s-React-Native-SDK#step-3-7-add-support-for-deep-links) -- [Expo docs](https://docs.expo.dev/) \ No newline at end of file +- [Expo docs](https://docs.expo.dev/) diff --git a/build/ExpoAdapterIterableModule.d.ts b/build/ExpoAdapterIterableModule.d.ts new file mode 100644 index 0000000..5c73bf0 --- /dev/null +++ b/build/ExpoAdapterIterableModule.d.ts @@ -0,0 +1,6 @@ +import { NativeModule } from 'expo'; +declare class ExpoAdapterIterableModule extends NativeModule { +} +declare const _default: ExpoAdapterIterableModule; +export default _default; +//# sourceMappingURL=ExpoAdapterIterableModule.d.ts.map \ No newline at end of file diff --git a/build/ExpoAdapterIterableModule.d.ts.map b/build/ExpoAdapterIterableModule.d.ts.map new file mode 100644 index 0000000..467dc95 --- /dev/null +++ b/build/ExpoAdapterIterableModule.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"ExpoAdapterIterableModule.d.ts","sourceRoot":"","sources":["../src/ExpoAdapterIterableModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,OAAO,yBAA0B,SAAQ,YAAY;CAAG;;AAG/D,wBAEE"} \ No newline at end of file diff --git a/build/ExpoAdapterIterableModule.js b/build/ExpoAdapterIterableModule.js new file mode 100644 index 0000000..38aaa62 --- /dev/null +++ b/build/ExpoAdapterIterableModule.js @@ -0,0 +1,4 @@ +import { requireNativeModule } from 'expo'; +// This call loads the native module object from the JSI. +export default requireNativeModule('ExpoAdapterIterable'); +//# sourceMappingURL=ExpoAdapterIterableModule.js.map \ No newline at end of file diff --git a/build/ExpoAdapterIterableModule.js.map b/build/ExpoAdapterIterableModule.js.map new file mode 100644 index 0000000..f97c2c7 --- /dev/null +++ b/build/ExpoAdapterIterableModule.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ExpoAdapterIterableModule.js","sourceRoot":"","sources":["../src/ExpoAdapterIterableModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAIzD,yDAAyD;AACzD,eAAe,mBAAmB,CAChC,qBAAqB,CACtB,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\ndeclare class ExpoAdapterIterableModule extends NativeModule {}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule(\n 'ExpoAdapterIterable'\n);\n"]} \ No newline at end of file diff --git a/build/index.d.ts b/build/index.d.ts new file mode 100644 index 0000000..bd9f7b0 --- /dev/null +++ b/build/index.d.ts @@ -0,0 +1,3 @@ +import ExpoAdapterIterableModule from './ExpoAdapterIterableModule'; +export default ExpoAdapterIterableModule; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/build/index.d.ts.map b/build/index.d.ts.map new file mode 100644 index 0000000..ab71e42 --- /dev/null +++ b/build/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,yBAAyB,MAAM,6BAA6B,CAAC;AAEpE,eAAe,yBAAyB,CAAC"} \ No newline at end of file diff --git a/build/index.js b/build/index.js new file mode 100644 index 0000000..f20e8c8 --- /dev/null +++ b/build/index.js @@ -0,0 +1,4 @@ +// Reexport the native module. +import ExpoAdapterIterableModule from './ExpoAdapterIterableModule'; +export default ExpoAdapterIterableModule; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/build/index.js.map b/build/index.js.map new file mode 100644 index 0000000..902e01a --- /dev/null +++ b/build/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,OAAO,yBAAyB,MAAM,6BAA6B,CAAC;AAEpE,eAAe,yBAAyB,CAAC","sourcesContent":["// Reexport the native module.\nimport ExpoAdapterIterableModule from './ExpoAdapterIterableModule';\n\nexport default ExpoAdapterIterableModule;\n"]} \ No newline at end of file