├── .editorconfig ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── prepare.yml │ └── release.yml ├── .gitignore ├── .husky ├── .npmignore ├── commit-msg └── pre-commit ├── .yarnrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── android ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── pusherwebsocketreactnative │ ├── PusherEventEmitter.kt │ ├── PusherWebsocketReactNativeModule.kt │ └── PusherWebsocketReactNativePackage.kt ├── babel.config.js ├── example ├── android │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── pusherwebsocketreactnative │ │ │ │ └── ReactNativeFlipper.java │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── pusherwebsocketreactnative │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── babel.config.js ├── index.tsx ├── ios │ ├── File.swift │ ├── Podfile │ ├── Podfile.lock │ ├── PusherWebsocketReactNativeExample-Bridging-Header.h │ ├── PusherWebsocketReactNativeExample.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── PusherWebsocketReactNativeExample.xcscheme │ ├── PusherWebsocketReactNativeExample.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── PusherWebsocketReactNativeExample │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-100.png │ │ │ ├── icon-1024.png │ │ │ ├── icon-120.png │ │ │ ├── icon-128.png │ │ │ ├── icon-152.png │ │ │ ├── icon-16.png │ │ │ ├── icon-167.png │ │ │ ├── icon-172.png │ │ │ ├── icon-180.png │ │ │ ├── icon-196.png │ │ │ ├── icon-20.png │ │ │ ├── icon-216.png │ │ │ ├── icon-256.png │ │ │ ├── icon-29.png │ │ │ ├── icon-32.png │ │ │ ├── icon-40.png │ │ │ ├── icon-48.png │ │ │ ├── icon-512.png │ │ │ ├── icon-55.png │ │ │ ├── icon-58.png │ │ │ ├── icon-60.png │ │ │ ├── icon-64.png │ │ │ ├── icon-76.png │ │ │ ├── icon-80.png │ │ │ ├── icon-87.png │ │ │ └── icon-88.png │ │ ├── Contents.json │ │ └── Image.imageset │ │ │ ├── Contents.json │ │ │ └── icon-1024.png │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ └── main.m ├── metro.config.js ├── package-lock.json ├── package.json ├── src │ ├── App.tsx │ └── pusher.png └── yarn.lock ├── ios ├── PusherWebsocketReactNative-Bridging-Header.h ├── PusherWebsocketReactNative.m ├── PusherWebsocketReactNative.swift └── PusherWebsocketReactNative.xcodeproj │ └── project.pbxproj ├── package-lock.json ├── package.json ├── pusher-websocket-react-native.podspec ├── scripts └── bootstrap.js ├── src ├── __tests__ │ └── index.test.tsx └── index.tsx ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Add a short description of the change. If this is related to an issue, please add a reference to the issue. 4 | 5 | ## CHANGELOG 6 | 7 | * [CHANGED] Describe your change here. Look at CHANGELOG.md to see the format. -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: [ master ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Yarn install 12 | uses: actions/setup-node@v2 13 | with: 14 | node-version: ${{ matrix.node-version }} 15 | - run: yarn install --cwd example --frozen-lockfile 16 | - run: yarn install --frozen-lockfile 17 | - run: yarn typescript 18 | - run: yarn lint 19 | -------------------------------------------------------------------------------- /.github/workflows/prepare.yml: -------------------------------------------------------------------------------- 1 | name: Prepare release 2 | 3 | on: 4 | pull_request: 5 | types: [ labeled ] 6 | branches: 7 | - master 8 | 9 | jobs: 10 | prepare-release: 11 | name: Prepare release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Get current version 16 | run: | 17 | CURRENT_VERSION=$(npm view @pusher/pusher-websocket-react-native version) 18 | echo "CURRENT_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV 19 | - uses: actions/checkout@v2 20 | with: 21 | repository: pusher/actions 22 | token: ${{ secrets.PUSHER_CI_GITHUB_PRIVATE_TOKEN }} 23 | path: .github/actions 24 | - uses: ./.github/actions/prepare-version-bump 25 | id: bump 26 | with: 27 | current_version: ${{ env.CURRENT_VERSION }} 28 | - uses: actions/setup-node@v2 29 | with: 30 | node-version: 16.x 31 | - run: yarn install 32 | - name: Push 33 | shell: bash 34 | run: | 35 | echo "$(jq '.version = "${{ steps.bump.outputs.new_version }}"' package.json)" > package.json 36 | 37 | git add package.json CHANGELOG.md 38 | git commit -m "Bump to version ${{ steps.bump.outputs.new_version }}" 39 | git push 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | 5 | jobs: 6 | check-release-tag: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout code 10 | uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | - name: Prepare tag 14 | id: prepare_tag 15 | continue-on-error: true 16 | run: | 17 | export TAG=v$(jq -r '.version' package.json) 18 | echo "TAG=$TAG" >> $GITHUB_ENV 19 | export CHECK_TAG=$(git tag | grep $TAG) 20 | if [[ $CHECK_TAG ]]; then 21 | echo "Skipping because release tag already exists" 22 | exit 1 23 | fi 24 | - name: Output 25 | id: release_output 26 | if: ${{ steps.prepare_tag.outcome == 'success' }} 27 | run: | 28 | echo "::set-output name=tag::${{ env.TAG }}" 29 | outputs: 30 | tag: ${{ steps.release_output.outputs.tag }} 31 | 32 | build: 33 | runs-on: ubuntu-latest 34 | needs: check-release-tag 35 | if: ${{ needs.check-release-tag.outputs.tag }} 36 | steps: 37 | - uses: actions/checkout@v2 38 | - uses: actions/setup-node@v2 39 | with: 40 | node-version: 16.x 41 | - run: yarn install 42 | 43 | publish-npm: 44 | runs-on: ubuntu-latest 45 | needs: build 46 | if: ${{ needs.check-release-tag.outputs.tag }} 47 | steps: 48 | - uses: actions/checkout@v2 49 | - uses: actions/setup-node@v2 50 | with: 51 | node-version: 16.x 52 | registry-url: https://registry.npmjs.org/ 53 | scope: '@pusher' 54 | - run: yarn install 55 | - run: yarn publish --verbose --access public 56 | env: 57 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} 58 | 59 | create-github-release: 60 | runs-on: ubuntu-latest 61 | needs: publish-npm 62 | if: ${{ needs.check-release-tag.outputs.tag }} 63 | steps: 64 | - uses: actions/checkout@v2 65 | - name: Prepare tag 66 | run: | 67 | export TAG=v$(jq -r '.version' package.json) 68 | echo "PRE_RELEASE=false" >> $GITHUB_ENV 69 | echo "TAG=$TAG" >> $GITHUB_ENV 70 | - name: Check pre release 71 | if: contains(env.TAG, 'beta') 72 | run: echo "PRE_RELEASE=true" >> $GITHUB_ENV 73 | - name: Setup git 74 | run: | 75 | git config user.email "pusher-ci@pusher.com" 76 | git config user.name "Pusher CI" 77 | - name: Prepare description 78 | run: | 79 | csplit -s CHANGELOG.md "/##/" {1} 80 | cat xx01 > CHANGELOG.tmp 81 | - name: Create Release 82 | uses: actions/create-release@v1 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | with: 86 | tag_name: ${{ env.TAG }} 87 | release_name: ${{ env.TAG }} 88 | body_path: CHANGELOG.tmp 89 | draft: false 90 | prerelease: ${{ env.PRE_RELEASE }} 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | 32 | # Android/IJ 33 | # 34 | .classpath 35 | .cxx 36 | .gradle 37 | .idea 38 | .project 39 | .settings 40 | local.properties 41 | android.iml 42 | 43 | # Cocoapods 44 | # 45 | example/ios/Pods 46 | 47 | # node.js 48 | # 49 | node_modules/ 50 | npm-debug.log 51 | yarn-debug.log 52 | yarn-error.log 53 | 54 | # BUCK 55 | buck-out/ 56 | \.buckd/ 57 | android/app/libs 58 | android/keystores/debug.keystore 59 | 60 | # Expo 61 | .expo/* 62 | 63 | # generated by bob 64 | lib/ 65 | -------------------------------------------------------------------------------- /.husky/.npmignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint -E HUSKY_GIT_PARAMS 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint && yarn typescript 5 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # Override Yarn command so we can automatically setup the repo on running `yarn` 2 | 3 | yarn-path "scripts/bootstrap.js" 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.3.1 4 | 5 | * [CHANGED] Update Pusher Swift SDK to 10.1.5 6 | 7 | ## 1.3.0 8 | 9 | * [ADDED] Add `reset` function to `Pusher` instance to reset all handlers and subscriptions (#110) 10 | * [FIXED] Multiple listeners are registered whenever `init function is called 11 | 12 | ## 1.2.3 13 | 14 | * [FIXED] Handle exceptions properly while subscribing to a channel on Android (#104) 15 | 16 | ## 1.2.2 17 | 18 | * [FIXED] Crash when a user subscribes to a channel twice on Android 19 | * [FIXED] Wait for unsubscription before deleting the local channel (#88) 20 | 21 | ## 1.2.1 22 | 23 | * [FIXED] Fixed event name conflicts with other libs using RCTDeviceEventEmitter 24 | 25 | ## 1.2.0 26 | 27 | * [CHANGED] Remove mutex locks in favor of storing callbacks so onAuthorizer does no longer freeze the app on iOS 28 | 29 | ## 1.1.1 30 | 31 | * [CHANGED] Allow re-init of the Pusher singleton. 32 | * [CHANGED] Update dependencies 33 | 34 | ## 1.1.0 35 | 36 | * [CHANGED] Add support for the new subscription_count event 37 | * [CHANGED] Using latest pusher-websocket-java and pusher-websocket-swift 38 | 39 | ## 1.0.2 40 | 41 | * [CHANGED] Use latest pusher websocket java sdk. 42 | * [ADDED] Example to use a custom authorizer. 43 | 44 | ## 1.0.1 45 | 46 | * [ADDED] Add onAuthorizer support to iOS 47 | 48 | ## 1.0.0 49 | 50 | * [CHANGED] Removed unsupported functions from README 51 | * [FIXED] Fixed build error on Example app 52 | * [FIXED] Fixed CHANGELOG error on release workflow 53 | 54 | ## 1.0.0-beta1 55 | 56 | * [FIXED] Fixed required dependencies on README 57 | * [ADDED] Add Lint support for Pull Requests 58 | * [CHANGED] Executed Lint on Example app 59 | 60 | ## 0.0.1-beta1 61 | 62 | * [ADDED] First beta release 🥳 63 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. 4 | 5 | ## Prerequesites 6 | 7 | ### NVM 8 | You can find the installation guide [here](https://heynode.com/tutorial/install-nodejs-locally-nvm/) 9 | 10 | ### Yarn 11 | On MacOS 12 | ```bash 13 | brew install yarn 14 | ``` 15 | 16 | ## Development workflow 17 | 18 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package: 19 | 20 | ```sh 21 | yarn 22 | ``` 23 | 24 | > While it's possible to use [`npm`](https://github.com/npm/cli), the tooling is built around [`yarn`](https://classic.yarnpkg.com/), so you'll have an easier time if you use `yarn` for development. 25 | 26 | While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app. 27 | 28 | To start the packager: 29 | 30 | ```sh 31 | yarn example start 32 | ``` 33 | 34 | To run the example app on Android: 35 | 36 | ```sh 37 | yarn example android 38 | ``` 39 | 40 | To run the example app on iOS: 41 | 42 | ```sh 43 | yarn example ios 44 | ``` 45 | 46 | Make sure your code passes TypeScript and ESLint. Run the following to verify: 47 | 48 | ```sh 49 | yarn typescript 50 | yarn lint 51 | ``` 52 | 53 | To fix formatting errors, run the following: 54 | 55 | ```sh 56 | yarn lint --fix 57 | ``` 58 | 59 | Remember to add tests for your change if possible. Run the unit tests by: 60 | 61 | ```sh 62 | yarn test 63 | ``` 64 | 65 | To edit the Objective-C files, open `example/ios/PusherWebsocketReactNativeExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > pusher-websocket-react-native`. 66 | 67 | To edit the Kotlin files, open `example/android` in Android studio and find the source files at `pusherwebsocketreactnative` under `Android`. 68 | 69 | ### Commit message convention 70 | 71 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages: 72 | 73 | - `fix`: bug fixes, e.g. fix crash due to deprecated method. 74 | - `feat`: new features, e.g. add new method to the module. 75 | - `refactor`: code refactor, e.g. migrate from class components to hooks. 76 | - `docs`: changes into documentation, e.g. add usage example for the module.. 77 | - `test`: adding or updating tests, e.g. add integration tests using detox. 78 | - `chore`: tooling changes, e.g. change CI config. 79 | 80 | Our pre-commit hooks verify that your commit message matches this format when committing. 81 | 82 | ### Linting and tests 83 | 84 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/) 85 | 86 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. 87 | 88 | Our pre-commit hooks verify that the linter and tests pass when committing. 89 | 90 | ### Publishing to npm 91 | 92 | We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc. 93 | 94 | To publish new versions, run the following: 95 | 96 | ```sh 97 | yarn release 98 | ``` 99 | 100 | ### Scripts 101 | 102 | The `package.json` file contains various scripts for common tasks: 103 | 104 | - `yarn bootstrap`: setup project by installing all dependencies and pods. 105 | - `yarn typescript`: type-check files with TypeScript. 106 | - `yarn lint`: lint files with ESLint. 107 | - `yarn test`: run unit tests with Jest. 108 | - `yarn example start`: start the Metro server for the example app. 109 | - `yarn example android`: run the example app on Android. 110 | - `yarn example ios`: run the example app on iOS. 111 | 112 | ### Sending a pull request 113 | 114 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). 115 | 116 | When you're sending a pull request: 117 | 118 | - Prefer small pull requests focused on one change. 119 | - Verify that linters and tests are passing. 120 | - Review the documentation to make sure it looks good. 121 | - Follow the pull request template when opening a pull request. 122 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. 123 | 124 | ## Code of Conduct 125 | 126 | ### Our Pledge 127 | 128 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 129 | 130 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 131 | 132 | ### Our Standards 133 | 134 | Examples of behavior that contributes to a positive environment for our community include: 135 | 136 | - Demonstrating empathy and kindness toward other people 137 | - Being respectful of differing opinions, viewpoints, and experiences 138 | - Giving and gracefully accepting constructive feedback 139 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 140 | - Focusing on what is best not just for us as individuals, but for the overall community 141 | 142 | Examples of unacceptable behavior include: 143 | 144 | - The use of sexualized language or imagery, and sexual attention or 145 | advances of any kind 146 | - Trolling, insulting or derogatory comments, and personal or political attacks 147 | - Public or private harassment 148 | - Publishing others' private information, such as a physical or email 149 | address, without their explicit permission 150 | - Other conduct which could reasonably be considered inappropriate in a 151 | professional setting 152 | 153 | ### Enforcement Responsibilities 154 | 155 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 156 | 157 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 158 | 159 | ### Scope 160 | 161 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 162 | 163 | ### Enforcement 164 | 165 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. 166 | 167 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 168 | 169 | ### Enforcement Guidelines 170 | 171 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 172 | 173 | #### 1. Correction 174 | 175 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 176 | 177 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 178 | 179 | #### 2. Warning 180 | 181 | **Community Impact**: A violation through a single incident or series of actions. 182 | 183 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 184 | 185 | #### 3. Temporary Ban 186 | 187 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 188 | 189 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 190 | 191 | #### 4. Permanent Ban 192 | 193 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 194 | 195 | **Consequence**: A permanent ban from any sort of public interaction within the community. 196 | 197 | ### Attribution 198 | 199 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 200 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 201 | 202 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 203 | 204 | [homepage]: https://www.contributor-covenant.org 205 | 206 | For answers to common questions about this code of conduct, see the FAQ at 207 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 208 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pusher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pusher React Native Websocket Client 2 | 3 | [![Twitter](https://img.shields.io/badge/twitter-@Pusher-blue.svg?style=flat)](http://twitter.com/Pusher) 4 | [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/master/LICENSE) 5 | [![npm version](https://badge.fury.io/js/@pusher%2Fpusher-websocket-react-native.svg)](https://badge.fury.io/js/@pusher%2Fpusher-websocket-react-native) 6 | 7 | This is the [Pusher Channels](https://pusher.com/channels) React Native client. 8 | 9 | For tutorials and more in-depth information about Pusher Channels, visit our [official docs](https://pusher.com/docs/channels). 10 | 11 | ## Supported Mobile platforms 12 | 13 | - Android through [pusher-websocket-java](https://github.com/pusher/pusher-websocket-java) 14 | - iOS through [pusher-websocket-swift](https://github.com/pusher/pusher-websocket-swift) 15 | 16 | ### Deployment targets 17 | 18 | - iOS 13.0 and above 19 | - Android 7 and above. Android 6 will require [desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring). 20 | 21 | ## Example Application 22 | 23 | By cloning this repository you can check the React Native example application, 24 | a minimal application to connect to a channel and send events. 25 | 26 | - https://github.com/pusher/pusher-websocket-react-native/tree/master/example 27 | 28 | ## Table of Contents 29 | 30 | - [Pusher React Native Websocket Client](#pusher-react-native-websocket-client) 31 | - [Supported Mobile platforms](#supported-mobile-platforms) 32 | - [Deployment targets](#deployment-targets) 33 | - [Example Application](#example-application) 34 | - [Table of Contents](#table-of-contents) 35 | - [Installation](#installation) 36 | - [iOS specific installation](#ios-specific-installation) 37 | - [Android specific installation](#android-specific-installation) 38 | - [Initialization](#initialization) 39 | - [Configuration](#configuration) 40 | - [`activityTimeout (double)`](#activitytimeout-double) 41 | - [`apiKey (string)`](#apikey-string) 42 | - [`authEndpoint (string)`](#authendpoint-string) 43 | - [`cluster (string)`](#cluster-string) 44 | - [`useTLS (bool)`](#usetls-bool) 45 | - [Event Callback parameters](#event-callback-parameters) 46 | - [`onEvent`](#onevent) 47 | - [`onSubscriptionSucceeded`](#onsubscriptionsucceeded) 48 | - [`onSubscriptionError`](#onsubscriptionerror) 49 | - [`onDecryptionFailure`](#ondecryptionfailure) 50 | - [`onSubscriptionCount`](#onsubscriptioncount) 51 | - [`onMemberAdded`](#onmemberadded) 52 | - [`onMemberRemoved`](#onmemberremoved) 53 | - [`onAuthorizer`](#onauthorizer) 54 | - [`onConnectionStateChange`](#onconnectionstatechange) 55 | - [`onError`](#onerror) 56 | - [Connection handling](#connection-handling) 57 | - [Connecting](#connecting) 58 | - [Disconnecting](#disconnecting) 59 | - [Reconnection](#reconnection) 60 | - [Subscribing](#subscribing) 61 | - [Public channels](#public-channels) 62 | - [Private channels](#private-channels) 63 | - [Private encrypted channels](#private-encrypted-channels) 64 | - [Limitations](#limitations) 65 | - [Presence channels](#presence-channels) 66 | - [Unsubscribing](#unsubscribing) 67 | - [Binding to events](#binding-to-events) 68 | - [Per-channel events](#per-channel-events) 69 | - [Global events](#global-events) 70 | - [PusherEvent](#pusherevent) 71 | - [Parsing event data](#parsing-event-data) 72 | - [Receiving errors](#receiving-errors) 73 | - [Triggering events](#triggering-events) 74 | - [Get a channel by name](#get-a-channel-by-name) 75 | - [Socket information](#socket-information) 76 | - [Communication](#communication) 77 | - [Credits](#credits) 78 | - [License](#license) 79 | 80 | ## Installation 81 | 82 | To integrate the plugin in your React Native App, you need 83 | to add the plugin to your `package.json`: 84 | 85 | ```bash 86 | npm install @pusher/pusher-websocket-react-native 87 | ``` 88 | or 89 | ```bash 90 | yarn add @pusher/pusher-websocket-react-native 91 | ``` 92 | 93 | ### iOS specific installation 94 | 95 | The Pusher Channels React Native plugin adds the 96 | pusher-websocket-swift cocoapod to your project. 97 | You probably need to run a 98 | 99 | ```bash 100 | $ pod install 101 | ``` 102 | 103 | in the ios directory. 104 | 105 | ### Android specific installation 106 | 107 | Gradle should automatically include the 108 | pusher-websocket-java dependency. 109 | 110 | ## Initialization 111 | 112 | The Pusher class is a singleton that 113 | can be instantiated with `getInstance()`. Then you need to initialize the client with several configuration options. Here is a quick example with several callbacks options: 114 | 115 | ```typescript 116 | import { 117 | Pusher, 118 | PusherMember, 119 | PusherChannel, 120 | PusherEvent, 121 | } from '@pusher/pusher-websocket-react-native'; 122 | 123 | const pusher = Pusher.getInstance(); 124 | 125 | try { 126 | await pusher.init({ 127 | apiKey: APP_KEY, 128 | cluster: APP_CLUSTER, 129 | // authEndpoint: '', 130 | onConnectionStateChange, 131 | onError, 132 | onEvent, 133 | onSubscriptionSucceeded, 134 | onSubscriptionError, 135 | onDecryptionFailure, 136 | onMemberAdded, 137 | onMemberRemoved, 138 | onSubscriptionCount, 139 | }); 140 | 141 | await pusher.subscribe({ channelName }); 142 | await pusher.connect(); 143 | } catch (e) { 144 | console.log(`ERROR: ${e}`); 145 | } 146 | ``` 147 | 148 | After calling `init(...)` you can connect to the Pusher servers. 149 | You can subscribe to channels before calling `connect()`. 150 | 151 | ## Configuration 152 | 153 | There are a few configuration parameters which can be set for the Pusher client. The following table 154 | describes available parameters for each platform: 155 | 156 | | parameter | Android | iOS | 157 | | -------------------------- | ------- | --- | 158 | | activityTimeout | ✅ | ✅ | 159 | | apiKey | ✅ | ✅ | 160 | | authEndpoint | ✅ | ✅ | 161 | | cluster | ✅ | ✅ | 162 | | maxReconnectGapInSeconds | ✅ | ✅ | 163 | | maxReconnectionAttempts | ✅ | ✅ | 164 | | pongTimeout | ✅ | ✅ | 165 | | proxy | ✅ | ⬜️ | 166 | | useTLS | ✅ | ✅ | 167 | | authorizerTimeoutInSeconds | ⬜️ | ✅ | 168 | 169 | #### `activityTimeout (double)` 170 | 171 | If no messages are received after this time period (in seconds), the ping message is sent to check if the connection is still working. The server supplies the default value, low values result in unnecessary traffic. 172 | 173 | #### `apiKey (string)` 174 | 175 | You can get your `APP_KEY` and `APP_CLUSTER` from the the App page on the App Keys section in your [Pusher Channels Dashboard](https://dashboard.pusher.com/) 176 | 177 | #### `authEndpoint (string)` 178 | 179 | The authEndpoint provides a URL that the 180 | Pusher client will call to authorize users 181 | for a presence channel. Learn [how to implement 182 | an authorization service](https://pusher.com/docs/channels/server_api/authenticating-users/) 183 | 184 | #### `cluster (string)` 185 | 186 | Specifies the cluster that pusher-js should connect to. Here's the full list of [Pusher clusters](https://pusher.com/docs/clusters). If you do not specify a cluster, `mt1` will be used by default. 187 | 188 | #### `useTLS (bool)` 189 | 190 | Whether or not you would like to use TLS encrypted transport or not, default is `true`. 191 | 192 | #### `authorizerTimeoutInSeconds (double)` 193 | 194 | If onAuthorizer callback is not called in Javascript before this time period (in seconds), the authorization for the channel will timeout on the native side. Default value: 10 seconds. iOS only. 195 | 196 | 197 | ## Event Callback parameters 198 | 199 | The following functions are callbacks that can be passed to the `init()` method. All are optional. 200 | 201 | #### `onEvent` 202 | 203 | ```typescript 204 | function onEvent(PusherEvent event) { 205 | console.log(`onEvent: ${event}`); 206 | } 207 | ``` 208 | 209 | Called when an event is received by the client. 210 | The global event handler will trigger events from any channel. 211 | 212 | #### `onSubscriptionSucceeded` 213 | 214 | ```typescript 215 | function onSubscriptionSucceeded(channelName:string, data:any) { 216 | console.log(`onSubscriptionSucceeded: ${channelName} data: ${data}`); 217 | } 218 | ``` 219 | 220 | Use this if you want to be informed when a channel has successfully been subscribed to. This is useful if you want to perform actions that are only relevant after a subscription has succeeded. For example, querying the members for presence channel. 221 | 222 | #### `onSubscriptionError` 223 | 224 | ```typescript 225 | function onSubscriptionError(channelName: string, message:string, e:any) { 226 | console.log(`onSubscriptionError: ${message} channelName: ${channelName} Exception: ${e}`); 227 | } 228 | ``` 229 | 230 | Use this if you want to be informed of a failed subscription attempt. You can use this to re-attempt the subscription or make a call to a service you use to track errors. 231 | 232 | #### `onDecryptionFailure` 233 | 234 | ```typescript 235 | function onDecryptionFailure(event:string, string reason:string) { 236 | console.log(`onDecryptionFailure: ${event} reason: ${reason}`); 237 | } 238 | ``` 239 | Used with private channels only. Use this if you want to be notified if any messages fail to decrypt. 240 | 241 | #### `onSubscriptionCount` 242 | 243 | ```typescript 244 | function onSubscriptionCount(subscriptionCount:number) { 245 | console.log(`onSubscriptionSucceeded: ${subscriptionCount}`); 246 | } 247 | ``` 248 | 249 | is an event that can be manually enabled on the server to count the number of connections that are currently subscribed to a particular channel. They work with all channel types, except presence channels. 250 | See [Counting live users at scale with subscription_count events](https://blog.pusher.com/counting-live-users-at-scale-with-subscription-count-events/) for more information. 251 | 252 | #### `onMemberAdded` 253 | 254 | ```typescript 255 | function onMemberAdded(channelName:string, member:PusherMember) { 256 | console.log(`onMemberAdded: ${channelName} member: ${member}`); 257 | } 258 | ``` 259 | 260 | Called when a member is added to the presence channel. 261 | 262 | #### `onMemberRemoved` 263 | 264 | ```typescript 265 | function onMemberRemoved(channelName:string, member:PusherMember) { 266 | console.log(`onMemberRemoved: ${channelName} member: ${member}`); 267 | } 268 | ``` 269 | 270 | Called when a member is removed from the presence channel. 271 | 272 | #### `onAuthorizer` 273 | 274 | When passing the `onAuthorizer()` callback to the `init()` method, this callback is called to request auth information. Learn how 275 | to [generate the correct auth signatures](https://pusher.com/docs/channels/library_auth_reference/auth-signatures/) 276 | 277 | ```typescript 278 | async function onAuthorizer(channelName:string, socketId:string):Promise { 279 | return { 280 | auth: "foo:bar", 281 | channel_data: '{"user_id": 1}', 282 | shared_secret: "foobar" 283 | }; 284 | } 285 | ``` 286 | 287 | #### `onConnectionStateChange` 288 | 289 | ```typescript 290 | function onConnectionStateChange(currentState:string, previousState:string) { 291 | console.log(`Connection: ${currentState}`); 292 | } 293 | ``` 294 | 295 | Use this if you want to use connection state changes to perform different actions/UI updates. 296 | The connection can have different states, as follows: 297 | 298 | - `CONNECTING` - Currently attempting to establish a connection 299 | - `CONNECTED` - Connection successfully established 300 | - `DISCONNECTING` - Connection is about to be disconnected. 301 | - `DISCONNECTED` - Connection has been disconnected with no attempts to automatically reconnect. 302 | - `RECONNECTING` - Atempting to re-establish the connection. 303 | 304 | #### `onError` 305 | 306 | ```typescript 307 | function onError(message:string, code:int, e:any) { 308 | console.log(`onError: $message code: ${code} exception: ${e}`); 309 | } 310 | ``` 311 | 312 | Use this if you want to be informed about errors received from Pusher Channels. For example, `Application is over connection quota`. For more details, refer to [Pusher error codes](https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol#error-codes). 313 | 314 | ## Connection handling 315 | 316 | ### Connecting 317 | 318 | To connect to the Pusher network, call the `connect()` method. 319 | 320 | ```typescript 321 | await pusher.connect(); 322 | ``` 323 | 324 | ### Disconnecting 325 | 326 | To disconnect from the Pusher network, just call the `disconnect()` method. 327 | 328 | ```typescript 329 | await pusher.disconnect(); 330 | ``` 331 | 332 | ### Reconnection 333 | 334 | There are three main ways why a connection could be disconnected: 335 | 336 | - The client explicitly calls disconnect and a close frame is sent over the websocket connection. 337 | - The client experiences some form of network degradation which leads to a heartbeat (ping/pong) message being missed and thus the client disconnects. 338 | - The Pusher server closes the websocket connection; typically this will only occur during a restart of the Pusher socket servers and almost immediatelly it will reconnect. 339 | 340 | In the case of the first type of disconnection, the library will (as you would hope) not attempt to reconnect. 341 | 342 | ## Subscribing 343 | 344 | ### Public channels 345 | 346 | The default method for subscribing to a channel involves invoking the `subscribe` method of your client object: 347 | 348 | ```typescript 349 | const myChannel = await pusher.subscribe({channelName: "my-channel"}); 350 | ``` 351 | 352 | ### Private channels 353 | 354 | Private channels are created in exactly the same way as public channels, except that they reside in the 'private-' namespace. This means prefixing the channel name: 355 | 356 | ```typescript 357 | const myPrivateChannel = await pusher.subscribe({channelName: "private-my-channel"}) 358 | ``` 359 | 360 | To subscribe to a private channel, the client needs to be authenticated. Refer to the [Configuration](#configuration) section for the authenticated channel example. 361 | 362 | ### Private encrypted channels 363 | 364 | Similar to Private channels, you can also subscribe to a [private encrypted channel](https://pusher.com/docs/channels/using_channels/encrypted-channels). This library now fully supports end-to-end encryption. This means that only you and your connected clients will be able to read your messages. Pusher cannot decrypt them. 365 | 366 | Like with private channels, you must provide an authentication endpoint. That endpoint must be using a [server client that supports end-to-end encryption](https://pusher.com/docs/channels/using_channels/encrypted-channels#server). There is a [demonstration endpoint to look at using nodejs](https://github.com/pusher/pusher-channels-auth-example#using-e2e-encryption). 367 | 368 | The shared secret used to decrypt events is loaded from the same auth endpoint request that is used to authorize your subscription. There is also a mechanism for reloading the shared secret if your encryption master key changes. If an event is encountered that cannot be decrypted, a request is made to your auth endpoint to attempt to load the new shared secret. If that request fails or if the returned secret still cannot decrypt the event then that event will be skipped, the `onDecryptionFailure` callback function will be called, and the next received event will be processed. 369 | 370 | #### Limitations 371 | 372 | - Client events are not supported on encrypted channels 373 | 374 | ```typescript 375 | const privateEncryptedChannel = await pusher.subscribe({channelName: "private-encrypted-my-channel"}) 376 | ``` 377 | 378 | There is also an optional callback in the connection delegate when you can listen for 379 | any failed decryption events: 380 | 381 | ```typescript 382 | void onDecryptionFailure(event:string, reason:string) 383 | ``` 384 | 385 | ### Presence channels 386 | 387 | Presence channels are channels whose names are prefixed by `presence-`. 388 | 389 | The resulting channel object has a member: `members` that contains the active members of the channel. 390 | 391 | ```typescript 392 | const myPresenceChannel = await pusher.subscribe({channelName: "presence-my-channel"}) 393 | ``` 394 | 395 | You can also provide functions that will be called when members are either added to or removed from the channel. These are available as parameters to `init()` globally, or to `subscribe()` per channel. 396 | 397 | ```typescript 398 | void onMemberAdded(channelName:string, member:PusherMember) { 399 | console.log(`onMemberAdded: ${channelName} user: ${member}`); 400 | } 401 | ``` 402 | 403 | ```typescript 404 | void onMemberRemoved(channelName:string, member:PusherMember) { 405 | console.log(`onMemberRemoved: ${channelName} user: ${member}`); 406 | } 407 | ``` 408 | 409 | **Note**: The `members` property of `PusherChannel` objects will only be set once subscription to the channel has succeeded. 410 | 411 | The easiest way to find out when a channel has been successfully subscribed to is to bind to the callback named `onSubscriptionSucceeded` on the channel you're interested in. It would look something like this: 412 | 413 | ```typescript 414 | const pusher = Pusher.getInstance(); 415 | 416 | await pusher.init({ 417 | apiKey: API_KEY, 418 | cluster: API_CLUSTER, 419 | authEndPoint: "https://your-server.com/pusher/auth" 420 | }); 421 | const myChannel = await pusher.subscribe( 422 | channelName:'presence-my-channel', 423 | onSubscriptionSucceeded: (channelName, data) => { 424 | console.log(`Subscribed to ${channelName}`); 425 | console.log(`I can now access me: ${myChannel.me}`) 426 | console.log(`And here are the channel members: ${myChannel.members}`) 427 | }, 428 | onMemberAdded: (member) => { 429 | console.log(`Member added: ${member}`); 430 | }, 431 | onMemberRemoved: (member) => { 432 | console.log(`Member removed: ${member}`); 433 | }, 434 | onEvent: (event) => { 435 | console.log(`Event received: ${event}`); 436 | }, 437 | ); 438 | ``` 439 | 440 | Note that both private and presence channels require the user to be authenticated to subscribe to the channel. This authentication can either happen inside the library if you configured your Pusher object with your app's secret, or an authentication request is made to an authentication endpoint that you provide, again when initializing your Pusher object. 441 | 442 | We recommend that you use an authentication endpoint over including your app's secret in your app in the vast majority of use cases. If you are completely certain that there's no risk to you including your app's secret in your app. For example, if your app is just for internal use at your company, then it can make things easier than setting up an authentication endpoint. 443 | 444 | ### Unsubscribing 445 | 446 | To unsubscribe from a channel, call the `unsubscribe()` method: 447 | 448 | ```typescript 449 | await pusher.unsubscribe({channelName:"my-channel"}); 450 | ``` 451 | 452 | ## Binding to events 453 | 454 | Events can be bound to at two levels; globally and per channel. There is an example of this below. 455 | 456 | ### Per-channel events 457 | 458 | These are bound to a specific channel. You can reuse event names in different parts of your client application. 459 | 460 | ```typescript 461 | const pusher = Pusher.getInstance(); 462 | await pusher.init({ 463 | apiKey: API_KEY, 464 | cluster: API_CLUSTER 465 | }); 466 | const myChannel = await pusher.subscribe({ 467 | channelName: "my-channel", 468 | onEvent: (event) => { 469 | console.log(`Got channel event: ${event}`); 470 | } 471 | }); 472 | await pusher.connect(); 473 | ``` 474 | 475 | ### Global events 476 | 477 | You can attach behavior to these events regardless of the channel the event is broadcast to. 478 | 479 | ```typescript 480 | const pusher = Pusher.getInstance(); 481 | await pusher.init({ 482 | apiKey: API_KEY, 483 | cluster: API_CLUSTER, 484 | onEvent: (event) { 485 | console.log(`Got event: ${event}`); 486 | } 487 | }); 488 | const myChannel = await pusher.subscribe({ 489 | channelName: "my-channel" 490 | }); 491 | ``` 492 | 493 | ### PusherEvent 494 | 495 | The callbacks you bind receive a `PusherEvent`: 496 | 497 | ```typescript 498 | class PusherEvent { 499 | channelName:string; // Name of the channel. 500 | eventName:string; // Name of the event. 501 | data:any; // Data, usually JSON string. See [parsing event data](#parsing-event-data). 502 | userId:string; // UserId of the sending event, only for client events on presence channels. 503 | } 504 | ``` 505 | 506 | #### Parsing event data 507 | 508 | The `data` property of [`PusherEvent`](#pusherevent) contains the string representation of the data that you passed when you triggered the event. If you passed an object then that object will have been serialized to JSON. You can parse that JSON as appropriate. 509 | 510 | ### Receiving errors 511 | 512 | Errors received from Pusher Channels can be accessed via the `onError` callback. 513 | 514 | ```typescript 515 | void onError(message:string, code:int, e:any) { 516 | console.log(`onError: ${message} code: ${code} exception: ${e}`); 517 | } 518 | ``` 519 | 520 | ## Triggering events 521 | 522 | Once a [private](https://pusher.com/docs/channels/using_channels/private-channels) or [presence](https://pusher.com/docs/channels/using_channels/presence-channels) subscription has been authorized (see [authenticating users](https://pusher.com/docs/channels/server_api/authenticating-users)) and the subscription has succeeded, it is possible to trigger events on those channels. You can do this per channel, or 523 | on the global `Pusher` instance. 524 | 525 | ```typescript 526 | await myChannel.trigger({eventName: "client-my-event", data: {"myName": "Bob"}}); 527 | ``` 528 | 529 | Or on the global pusher instance: 530 | 531 | ```typescript 532 | await pusher.trigger({channelName: "my-channel", eventName: "client-my-event", data: {"myName": "Bob"}}); 533 | ``` 534 | 535 | Events triggered by clients are called [client events](https://pusher.com/docs/channels/using_channels/events#triggering-client-events). Because they are being triggered from a client which may not be trusted, there are a number of enforced rules when using them. Some of these rules include: 536 | 537 | - Event names must have a `client-` prefix 538 | - Rate limits 539 | - You can only trigger an event when the subscription has succeeded 540 | 541 | For more details, refer to [client events](https://pusher.com/docs/channels/using_channels/events#triggering-client-events). 542 | 543 | ## Get a channel by name 544 | 545 | To get the `PusherChannel` instance from the `Pusher` instance you can use the `getChannel()` method: 546 | 547 | ```typescript 548 | const channel = pusher.getChannel("presence-channel"); 549 | ``` 550 | 551 | ## Socket information 552 | 553 | To get information from the current socket call the `getSocketId()` method: 554 | 555 | ```typescript 556 | const socketId = await pusher.getSocketId(); 557 | ``` 558 | 559 | ## Communication 560 | 561 | - If you have found a bug, please open an issue. 562 | - If you have a feature request, open an issue. 563 | - If you want to contribute, submit a pull request. 564 | - If you do not receive a timely response, feel free to check our [support portal](https://docs.bird.com/pusher). 565 | 566 | ## Credits 567 | 568 | Pusher is owned and maintained by [Pusher](https://pusher.com). 569 | 570 | ## License 571 | 572 | Pusher is released under the MIT license. Refer to [LICENSE](https://github.com/pusher/pusher-websocket-react-native/blob/master/LICENSE) for more details. 573 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | // Buildscript is evaluated before everything else so we can't use getExtOrDefault 3 | def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['PusherWebsocketReactNative_kotlinVersion'] 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:7.3.0' 12 | // noinspection DifferentKotlinGradleVersion 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: 'kotlin-android' 19 | 20 | def getExtOrDefault(name) { 21 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['PusherWebsocketReactNative_' + name] 22 | } 23 | 24 | def getExtOrIntegerDefault(name) { 25 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['PusherWebsocketReactNative_' + name]).toInteger() 26 | } 27 | 28 | android { 29 | compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') 30 | defaultConfig { 31 | minSdkVersion 21 32 | targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') 33 | 34 | } 35 | 36 | buildTypes { 37 | release { 38 | minifyEnabled false 39 | } 40 | } 41 | lintOptions { 42 | disable 'GradleCompatible' 43 | } 44 | compileOptions { 45 | sourceCompatibility JavaVersion.VERSION_1_8 46 | targetCompatibility JavaVersion.VERSION_1_8 47 | } 48 | } 49 | 50 | repositories { 51 | mavenCentral() 52 | google() 53 | 54 | def found = false 55 | def defaultDir = null 56 | def androidSourcesName = 'React Native sources' 57 | 58 | if (rootProject.ext.has('reactNativeAndroidRoot')) { 59 | defaultDir = rootProject.ext.get('reactNativeAndroidRoot') 60 | } else { 61 | defaultDir = new File( 62 | projectDir, 63 | '/../../../node_modules/react-native/android' 64 | ) 65 | } 66 | 67 | if (defaultDir.exists()) { 68 | maven { 69 | url defaultDir.toString() 70 | name androidSourcesName 71 | } 72 | 73 | logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}") 74 | found = true 75 | } else { 76 | def parentDir = rootProject.projectDir 77 | 78 | 1.upto(5, { 79 | if (found) return true 80 | parentDir = parentDir.parentFile 81 | 82 | def androidSourcesDir = new File( 83 | parentDir, 84 | 'node_modules/react-native' 85 | ) 86 | 87 | def androidPrebuiltBinaryDir = new File( 88 | parentDir, 89 | 'node_modules/react-native/android' 90 | ) 91 | 92 | if (androidPrebuiltBinaryDir.exists()) { 93 | maven { 94 | url androidPrebuiltBinaryDir.toString() 95 | name androidSourcesName 96 | } 97 | 98 | logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}") 99 | found = true 100 | } else if (androidSourcesDir.exists()) { 101 | maven { 102 | url androidSourcesDir.toString() 103 | name androidSourcesName 104 | } 105 | 106 | logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}") 107 | found = true 108 | } 109 | }) 110 | } 111 | 112 | if (!found) { 113 | throw new GradleException( 114 | "${project.name}: unable to locate React Native android sources. " + 115 | "Ensure you have you installed React Native as a dependency in your project and try again." 116 | ) 117 | } 118 | } 119 | 120 | def kotlin_version = getExtOrDefault('kotlinVersion') 121 | 122 | dependencies { 123 | // noinspection GradleDynamicVersion 124 | api 'com.facebook.react:react-native:+' 125 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 126 | api 'com.pusher:pusher-java-client:2.+' 127 | api 'com.google.code.gson:gson:2.+' 128 | } 129 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | # 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 8 | # 9 | # When configured, Gradle will run in incubating parallel mode. 10 | # This option should only be used with decoupled projects. More details, visit 11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 12 | # org.gradle.parallel=true 13 | #Fri Aug 19 11:19:42 CEST 2022 14 | PusherWebsocketReactNative_kotlinVersion=1.7.21 15 | PusherWebsocketReactNative_targetSdkVersion=33 16 | PusherWebsocketReactNative_compileSdkVersion=33 17 | android.useAndroidX=true 18 | android.enableJetifier=true 19 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 30 17:56:47 CEST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/java/com/pusherwebsocketreactnative/PusherEventEmitter.kt: -------------------------------------------------------------------------------- 1 | package com.pusherwebsocketreactnative 2 | 3 | import com.facebook.react.bridge.Arguments 4 | import com.facebook.react.bridge.ReactApplicationContext 5 | import com.facebook.react.modules.core.DeviceEventManagerModule 6 | 7 | class PusherEventEmitter(private val context: ReactApplicationContext) { 8 | companion object { 9 | private const val EVENT_PREFIX = "PusherReactNative" 10 | } 11 | 12 | fun emit(eventName: String, params: Any?) { 13 | val jsModule = this.context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) 14 | val pusherEventName = "${EVENT_PREFIX}:${eventName}" 15 | 16 | if (params is Map<*, *>) { 17 | jsModule.emit(pusherEventName, Arguments.makeNativeMap(params as Map)) 18 | } 19 | 20 | if (params is String) { 21 | jsModule.emit(pusherEventName, params) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/src/main/java/com/pusherwebsocketreactnative/PusherWebsocketReactNativeModule.kt: -------------------------------------------------------------------------------- 1 | package com.pusherwebsocketreactnative 2 | 3 | import android.util.Log 4 | import com.facebook.react.bridge.* 5 | import com.google.gson.Gson 6 | import com.pusher.client.ChannelAuthorizer 7 | import com.pusher.client.Pusher 8 | import com.pusher.client.PusherOptions 9 | import com.pusher.client.channel.* 10 | import com.pusher.client.connection.ConnectionEventListener 11 | import com.pusher.client.connection.ConnectionState 12 | import com.pusher.client.connection.ConnectionStateChange 13 | import com.pusher.client.util.HttpChannelAuthorizer 14 | import java.net.InetSocketAddress 15 | import java.net.Proxy 16 | import java.util.concurrent.Semaphore 17 | 18 | class PusherWebsocketReactNativeModule(reactContext: ReactApplicationContext) : 19 | ReactContextBaseJavaModule(reactContext), 20 | ConnectionEventListener, ChannelEventListener, SubscriptionEventListener, 21 | PrivateChannelEventListener, PrivateEncryptedChannelEventListener, PresenceChannelEventListener, 22 | ChannelAuthorizer { 23 | 24 | private var pusher: Pusher? = null 25 | private val TAG = "PusherReactNative" 26 | private val authorizerMutex = mutableMapOf() 27 | private val authorizerResult = mutableMapOf() 28 | 29 | private val pusherEventEmitter = PusherEventEmitter(reactContext) 30 | 31 | override fun getName(): String { 32 | return "PusherWebsocketReactNative" 33 | } 34 | 35 | @ReactMethod 36 | fun addListener(eventName: String?) { 37 | // Keep: Required for RN built in Event Emitter Calls. 38 | } 39 | 40 | @ReactMethod 41 | fun removeListeners(count: Int?) { 42 | // Keep: Required for RN built in Event Emitter Calls. 43 | } 44 | 45 | @ReactMethod 46 | fun initialize( 47 | arguments: ReadableMap, 48 | promise: Promise 49 | ) { 50 | try { 51 | if (pusher != null) { 52 | pusher!!.disconnect() 53 | } 54 | val options = PusherOptions() 55 | if (arguments.hasKey("cluster")) options.setCluster(arguments.getString("cluster")) 56 | if (arguments.hasKey("useTLS")) options.isUseTLS = 57 | arguments.getBoolean("useTLS") 58 | if (arguments.hasKey("activityTimeout")) options.activityTimeout = 59 | arguments.getInt("activityTimeout").toLong() 60 | if (arguments.hasKey("pongTimeout")) options.pongTimeout = 61 | arguments.getInt("pongTimeout").toLong() 62 | if (arguments.hasKey("maxReconnectionAttempts")) options.maxReconnectionAttempts = 63 | arguments.getInt("maxReconnectionAttempts") 64 | if (arguments.hasKey("maxReconnectGapInSeconds")) options.maxReconnectGapInSeconds = 65 | arguments.getInt("maxReconnectGapInSeconds") 66 | if (arguments.hasKey("authEndpoint")) options.channelAuthorizer = 67 | HttpChannelAuthorizer(arguments.getString("authEndpoint")) 68 | if (arguments.hasKey("authorizer") && arguments.getBoolean("authorizer")) options.channelAuthorizer = 69 | this 70 | if (arguments.hasKey("proxy")) { 71 | val (host, port) = arguments.getString("proxy")!!.split(':') 72 | options.proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port.toInt())) 73 | } 74 | pusher = Pusher(arguments.getString("apiKey"), options) 75 | Log.i(TAG, "Start $pusher") 76 | promise.resolve(null) 77 | } catch (e: Exception) { 78 | promise.reject(TAG, e.message, null) 79 | } 80 | } 81 | 82 | @ReactMethod 83 | fun connect(promise: Promise) { 84 | pusher!!.connect(this, ConnectionState.ALL) 85 | promise.resolve(null) 86 | } 87 | 88 | @ReactMethod 89 | fun disconnect(promise: Promise) { 90 | pusher!!.disconnect() 91 | promise.resolve(null) 92 | } 93 | 94 | @ReactMethod 95 | fun subscribe(channelName: String, promise: Promise) { 96 | try { 97 | val channel = when { 98 | channelName.startsWith("private-encrypted-") -> pusher!!.subscribePrivateEncrypted( 99 | channelName, this 100 | ) 101 | channelName.startsWith("private-") -> pusher!!.subscribePrivate(channelName, this) 102 | channelName.startsWith("presence-") -> pusher!!.subscribePresence( 103 | channelName, this 104 | ) 105 | else -> pusher!!.subscribe(channelName, this) 106 | } 107 | channel.bindGlobal(this) 108 | promise.resolve(null) 109 | } catch (e: Exception) { 110 | promise.reject("Error", "Failed to subscribe to channel: $channelName", e) 111 | } 112 | } 113 | 114 | @ReactMethod 115 | fun unsubscribe(channelName: String, promise: Promise) { 116 | pusher!!.unsubscribe(channelName) 117 | promise.resolve(null) 118 | } 119 | 120 | @ReactMethod 121 | fun trigger(channelName: String, eventName: String, data: String, promise: Promise) { 122 | try { 123 | when { 124 | channelName.startsWith("private-encrypted-") -> throw Exception("It's not currently possible to send a message using private encrypted channels.") 125 | channelName.startsWith("private-") -> pusher!!.getPrivateChannel(channelName) 126 | .trigger(eventName, data) 127 | channelName.startsWith("presence-") -> pusher!!.getPresenceChannel(channelName) 128 | .trigger(eventName, data) 129 | else -> throw Exception("Messages can only be sent to private and presence channels.") 130 | } 131 | promise.resolve(null) 132 | } catch (e: Exception) { 133 | promise.reject(e) 134 | } 135 | } 136 | 137 | @ReactMethod 138 | fun getSocketId(promise: Promise) { 139 | val socketId = pusher!!.connection.socketId 140 | promise.resolve(socketId) 141 | } 142 | 143 | override fun authorize(channelName: String, socketId: String): String? { 144 | pusherEventEmitter.emit( 145 | "onAuthorizer", mapOf( 146 | "channelName" to channelName, 147 | "socketId" to socketId 148 | ) 149 | ) 150 | val key = channelName + socketId 151 | authorizerMutex[key] = Semaphore(0) 152 | authorizerMutex[key]!!.acquire() 153 | val authParams = authorizerResult.remove(key)!! 154 | val gson = Gson() 155 | return gson.toJson(authParams.toHashMap()) 156 | } 157 | 158 | @ReactMethod 159 | fun onAuthorizer(channelName: String, socketId: String, data: ReadableMap, promise: Promise) { 160 | val key = channelName + socketId 161 | authorizerResult[key] = data 162 | authorizerMutex[key]!!.release() 163 | authorizerMutex.remove(key) 164 | promise.resolve(null) 165 | } 166 | 167 | // Event handlers 168 | override fun onConnectionStateChange(change: ConnectionStateChange) { 169 | pusherEventEmitter.emit( 170 | "onConnectionStateChange", mapOf( 171 | "previousState" to change.previousState.toString(), 172 | "currentState" to change.currentState.toString() 173 | ) 174 | ) 175 | } 176 | 177 | override fun onSubscriptionSucceeded(channelName: String) { 178 | // For presence channels we wait for the onUsersInformationReceived event. 179 | if (!channelName.startsWith("presence-")) { 180 | pusherEventEmitter.emit( 181 | "onEvent", mapOf( 182 | "channelName" to channelName, 183 | "eventName" to "pusher_internal:subscription_succeeded", 184 | "data" to emptyMap() 185 | ) 186 | ) 187 | } 188 | } 189 | 190 | override fun onEvent(event: PusherEvent) { 191 | // The java sdk transforms some events from pusher_internal 192 | // to pusher:... events, we translate them back. 193 | val finalEvent = if (event.eventName === "pusher:subscription_count") { 194 | PusherEvent( 195 | "pusher_internal:subscription_count", 196 | event.channelName, 197 | event.userId, 198 | event.data) 199 | } else { 200 | event 201 | } 202 | pusherEventEmitter.emit( 203 | "onEvent", mapOf( 204 | "channelName" to finalEvent.channelName, 205 | "eventName" to finalEvent.eventName, 206 | "userId" to finalEvent.userId, 207 | "data" to finalEvent.data 208 | ) 209 | ) 210 | } 211 | 212 | override fun onAuthenticationFailure(message: String, e: Exception) { 213 | pusherEventEmitter.emit( 214 | "onSubscriptionError", mapOf( 215 | "message" to message, 216 | "error" to e.toString() 217 | ) 218 | ) 219 | } // Other ChannelEventListener methods 220 | 221 | override fun onUsersInformationReceived(channelName: String?, users: MutableSet?) { 222 | val gson = Gson() 223 | val channel = pusher!!.getPresenceChannel(channelName) 224 | val hash = mutableMapOf() 225 | // convert users back to original structure. 226 | for (user in users!!) { 227 | hash[user.id] = gson.fromJson(user.info, Map::class.java) 228 | } 229 | val data = mapOf( 230 | "presence" to mapOf( 231 | "count" to users.size, 232 | "ids" to users.map { it.id }, 233 | "hash" to hash 234 | ) 235 | ) 236 | pusherEventEmitter.emit( 237 | "onEvent", mapOf( 238 | "channelName" to channelName, 239 | "eventName" to "pusher_internal:subscription_succeeded", 240 | "userId" to channel.me.id, 241 | "data" to data 242 | ) 243 | ) 244 | } 245 | 246 | override fun onDecryptionFailure(event: String?, reason: String?) { 247 | pusherEventEmitter.emit( 248 | "onDecryptionFailure", mapOf( 249 | "event" to event, 250 | "reason" to reason 251 | ) 252 | ) 253 | } 254 | 255 | override fun userSubscribed(channelName: String, user: User) { 256 | val gson = Gson() 257 | pusherEventEmitter.emit( 258 | "onMemberAdded", mapOf( 259 | "channelName" to channelName, 260 | "user" to mapOf( 261 | "userId" to user.id, 262 | "userInfo" to gson.fromJson(user.info, Map::class.java) 263 | ) 264 | ) 265 | ) 266 | } 267 | 268 | override fun userUnsubscribed(channelName: String, user: User) { 269 | val gson = Gson() 270 | pusherEventEmitter.emit( 271 | "onMemberRemoved", mapOf( 272 | "channelName" to channelName, 273 | "user" to mapOf( 274 | "userId" to user.id, 275 | "userInfo" to gson.fromJson(user.info, Map::class.java) 276 | ) 277 | ) 278 | ) 279 | } // Other ChannelEventListener methods 280 | 281 | override fun onError(message: String, code: String?, e: Exception?) { 282 | pusherEventEmitter.emit( 283 | "onError", mapOf( 284 | "message" to message, 285 | "code" to code, 286 | "error" to e.toString() 287 | ) 288 | ) 289 | } 290 | 291 | override fun onError(message: String, e: Exception) { 292 | onError(message, "", e) 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /android/src/main/java/com/pusherwebsocketreactnative/PusherWebsocketReactNativePackage.kt: -------------------------------------------------------------------------------- 1 | package com.pusherwebsocketreactnative 2 | 3 | import com.facebook.react.ReactPackage 4 | import com.facebook.react.bridge.NativeModule 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.uimanager.ViewManager 7 | 8 | class PusherWebsocketReactNativePackage : ReactPackage { 9 | override fun createNativeModules(reactContext: ReactApplicationContext): List { 10 | return listOf(PusherWebsocketReactNativeModule(reactContext)) 11 | } 12 | 13 | override fun createViewManagers(reactContext: ReactApplicationContext): List> { 14 | return emptyList() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | import org.apache.tools.ant.taskdefs.condition.Os 5 | 6 | /** 7 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 8 | * and bundleReleaseJsAndAssets). 9 | * These basically call `react-native bundle` with the correct arguments during the Android build 10 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 11 | * bundle directly from the development server. Below you can see all the possible configurations 12 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 13 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 14 | * 15 | * project.ext.react = [ 16 | * // the name of the generated asset file containing your JS bundle 17 | * bundleAssetName: "index.android.bundle", 18 | * 19 | * // the entry file for bundle generation. If none specified and 20 | * // "index.android.js" exists, it will be used. Otherwise "index.js" is 21 | * // default. Can be overridden with ENTRY_FILE environment variable. 22 | * entryFile: "index.android.js", 23 | * 24 | * // https://reactnative.dev/docs/performance#enable-the-ram-format 25 | * bundleCommand: "ram-bundle", 26 | * 27 | * // whether to bundle JS and assets in debug mode 28 | * bundleInDebug: false, 29 | * 30 | * // whether to bundle JS and assets in release mode 31 | * bundleInRelease: true, 32 | * 33 | * // whether to bundle JS and assets in another build variant (if configured). 34 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 35 | * // The configuration property can be in the following formats 36 | * // 'bundleIn${productFlavor}${buildType}' 37 | * // 'bundleIn${buildType}' 38 | * // bundleInFreeDebug: true, 39 | * // bundleInPaidRelease: true, 40 | * // bundleInBeta: true, 41 | * 42 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 43 | * // for example: to disable dev mode in the staging build type (if configured) 44 | * devDisabledInStaging: true, 45 | * // The configuration property can be in the following formats 46 | * // 'devDisabledIn${productFlavor}${buildType}' 47 | * // 'devDisabledIn${buildType}' 48 | * 49 | * // the root of your project, i.e. where "package.json" lives 50 | * root: "../../", 51 | * 52 | * // where to put the JS bundle asset in debug mode 53 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 54 | * 55 | * // where to put the JS bundle asset in release mode 56 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 57 | * 58 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 59 | * // require('./image.png')), in debug mode 60 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 61 | * 62 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 63 | * // require('./image.png')), in release mode 64 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 65 | * 66 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 67 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 68 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 69 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 70 | * // for example, you might want to remove it from here. 71 | * inputExcludes: ["android/**", "ios/**"], 72 | * 73 | * // override which node gets called and with what additional arguments 74 | * nodeExecutableAndArgs: ["node"], 75 | * 76 | * // supply additional arguments to the packager 77 | * extraPackagerArgs: [] 78 | * ] 79 | */ 80 | 81 | project.ext.react = [ 82 | enableHermes: true, // clean and rebuild if changing 83 | ] 84 | 85 | apply from: "../../node_modules/react-native/react.gradle" 86 | 87 | /** 88 | * Set this to true to create two separate APKs instead of one: 89 | * - An APK that only works on ARM devices 90 | * - An APK that only works on x86 devices 91 | * The advantage is the size of the APK is reduced by about 4MB. 92 | * Upload all the APKs to the Play Store and people will download 93 | * the correct one based on the CPU architecture of their device. 94 | */ 95 | def enableSeparateBuildPerCPUArchitecture = false 96 | 97 | /** 98 | * Run Proguard to shrink the Java bytecode in release builds. 99 | */ 100 | def enableProguardInReleaseBuilds = false 101 | 102 | /** 103 | * The preferred build flavor of JavaScriptCore. 104 | * 105 | * For example, to use the international variant, you can use: 106 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 107 | * 108 | * The international variant includes ICU i18n library and necessary data 109 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 110 | * give correct results when using with locales other than en-US. Note that 111 | * this variant is about 6MiB larger per architecture than default. 112 | */ 113 | def jscFlavor = 'org.webkit:android-jsc:+' 114 | 115 | /** 116 | * Whether to enable the Hermes VM. 117 | * 118 | * This should be set on project.ext.react and that value will be read here. If it is not set 119 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode 120 | * and the benefits of using Hermes will therefore be sharply reduced. 121 | */ 122 | def enableHermes = project.ext.react.get("enableHermes", false); 123 | 124 | /** 125 | * Architectures to build native code for. 126 | */ 127 | def reactNativeArchitectures() { 128 | def value = project.getProperties().get("reactNativeArchitectures") 129 | return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] 130 | } 131 | 132 | android { 133 | ndkVersion rootProject.ext.ndkVersion 134 | 135 | compileSdkVersion rootProject.ext.compileSdkVersion 136 | 137 | defaultConfig { 138 | applicationId "com.example.pusherwebsocketreactnative" 139 | minSdkVersion rootProject.ext.minSdkVersion 140 | targetSdkVersion rootProject.ext.targetSdkVersion 141 | versionCode 1 142 | versionName "1.0" 143 | buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() 144 | 145 | if (isNewArchitectureEnabled()) { 146 | // We configure the CMake build only if you decide to opt-in for the New Architecture. 147 | externalNativeBuild { 148 | cmake { 149 | arguments "-DPROJECT_BUILD_DIR=$buildDir", 150 | "-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid", 151 | "-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build", 152 | "-DNODE_MODULES_DIR=$rootDir/../node_modules", 153 | "-DANDROID_STL=c++_shared" 154 | } 155 | } 156 | if (!enableSeparateBuildPerCPUArchitecture) { 157 | ndk { 158 | abiFilters (*reactNativeArchitectures()) 159 | } 160 | } 161 | } 162 | } 163 | 164 | if (isNewArchitectureEnabled()) { 165 | // We configure the NDK build only if you decide to opt-in for the New Architecture. 166 | externalNativeBuild { 167 | cmake { 168 | path "$projectDir/src/main/jni/CMakeLists.txt" 169 | } 170 | } 171 | def reactAndroidProjectDir = project(':ReactAndroid').projectDir 172 | def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { 173 | dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") 174 | from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") 175 | into("$buildDir/react-ndk/exported") 176 | } 177 | def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { 178 | dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") 179 | from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") 180 | into("$buildDir/react-ndk/exported") 181 | } 182 | afterEvaluate { 183 | // If you wish to add a custom TurboModule or component locally, 184 | // you should uncomment this line. 185 | // preBuild.dependsOn("generateCodegenArtifactsFromSchema") 186 | preDebugBuild.dependsOn(packageReactNdkDebugLibs) 187 | preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) 188 | 189 | // Due to a bug inside AGP, we have to explicitly set a dependency 190 | // between configureCMakeDebug* tasks and the preBuild tasks. 191 | // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732 192 | configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild) 193 | configureCMakeDebug.dependsOn(preDebugBuild) 194 | reactNativeArchitectures().each { architecture -> 195 | tasks.findByName("configureCMakeDebug[${architecture}]")?.configure { 196 | dependsOn("preDebugBuild") 197 | } 198 | tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure { 199 | dependsOn("preReleaseBuild") 200 | } 201 | } 202 | } 203 | } 204 | 205 | splits { 206 | abi { 207 | reset() 208 | enable enableSeparateBuildPerCPUArchitecture 209 | universalApk false // If true, also generate a universal APK 210 | include (*reactNativeArchitectures()) 211 | } 212 | } 213 | namespace 'com.example.pusherwebsocketreactnative' 214 | signingConfigs { 215 | debug { 216 | storeFile file('debug.keystore') 217 | storePassword 'android' 218 | keyAlias 'androiddebugkey' 219 | keyPassword 'android' 220 | } 221 | } 222 | buildTypes { 223 | debug { 224 | signingConfig signingConfigs.debug 225 | } 226 | release { 227 | // Caution! In production, you need to generate your own keystore file. 228 | // see https://reactnative.dev/docs/signed-apk-android. 229 | signingConfig signingConfigs.debug 230 | minifyEnabled enableProguardInReleaseBuilds 231 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 232 | } 233 | } 234 | 235 | // applicationVariants are e.g. debug, release 236 | applicationVariants.all { variant -> 237 | variant.outputs.each { output -> 238 | // For each separate APK per architecture, set a unique version code as described here: 239 | // https://developer.android.com/studio/build/configure-apk-splits.html 240 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. 241 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 242 | def abi = output.getFilter(OutputFile.ABI) 243 | if (abi != null) { // null for the universal-debug, universal-release variants 244 | output.versionCodeOverride = 245 | defaultConfig.versionCode * 1000 + versionCodes.get(abi) 246 | } 247 | 248 | } 249 | } 250 | } 251 | 252 | dependencies { 253 | implementation fileTree(dir: "libs", include: ["*.jar"]) 254 | 255 | //noinspection GradleDynamicVersion 256 | implementation "com.facebook.react:react-native:+" // From node_modules 257 | 258 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' 259 | 260 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { 261 | exclude group:'com.facebook.fbjni' 262 | } 263 | 264 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { 265 | exclude group:'com.facebook.flipper' 266 | exclude group:'com.squareup.okhttp3', module:'okhttp' 267 | } 268 | 269 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { 270 | exclude group:'com.facebook.flipper' 271 | } 272 | 273 | if (enableHermes) { 274 | //noinspection GradleDynamicVersion 275 | implementation("com.facebook.react:hermes-engine:+") { // From node_modules 276 | exclude group:'com.facebook.fbjni' 277 | } 278 | } else { 279 | implementation jscFlavor 280 | } 281 | implementation project(':pusher-websocket-react-native') 282 | } 283 | 284 | if (isNewArchitectureEnabled()) { 285 | // If new architecture is enabled, we let you build RN from source 286 | // Otherwise we fallback to a prebuilt .aar bundled in the NPM package. 287 | // This will be applied to all the imported transtitive dependency. 288 | configurations.all { 289 | resolutionStrategy.dependencySubstitution { 290 | substitute(module("com.facebook.react:react-native")) 291 | .using(project(":ReactAndroid")) 292 | .because("On New Architecture we're building React Native from source") 293 | substitute(module("com.facebook.react:hermes-engine")) 294 | .using(project(":ReactAndroid:hermes-engine")) 295 | .because("On New Architecture we're building Hermes from source") 296 | } 297 | } 298 | } 299 | 300 | // Run this once to be able to run the application with BUCK 301 | // puts all compile dependencies into folder libs for BUCK to use 302 | task copyDownloadableDepsToLibs(type: Copy) { 303 | from configurations.implementation 304 | into 'libs' 305 | } 306 | 307 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 308 | 309 | def isNewArchitectureEnabled() { 310 | // To opt-in for the New Architecture, you can either: 311 | // - Set `newArchEnabled` to true inside the `gradle.properties` file 312 | // - Invoke gradle with `-newArchEnabled=true` 313 | // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` 314 | return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" 315 | } 316 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/android/app/debug.keystore -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/debug/java/com/example/pusherwebsocketreactnative/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.example.pusherwebsocketreactnative; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin; 21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | public class ReactNativeFlipper { 28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 29 | if (FlipperUtils.shouldEnableFlipper(context)) { 30 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 31 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 32 | client.addPlugin(new ReactFlipperPlugin()); 33 | client.addPlugin(new DatabasesFlipperPlugin(context)); 34 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 35 | client.addPlugin(CrashReporterPlugin.getInstance()); 36 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 37 | NetworkingModule.setCustomClientBuilder( 38 | new NetworkingModule.CustomClientBuilder() { 39 | @Override 40 | public void apply(OkHttpClient.Builder builder) { 41 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 42 | } 43 | }); 44 | client.addPlugin(networkFlipperPlugin); 45 | client.start(); 46 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 47 | // Hence we run if after all native modules have been initialized 48 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 49 | if (reactContext == null) { 50 | reactInstanceManager.addReactInstanceEventListener( 51 | new ReactInstanceManager.ReactInstanceEventListener() { 52 | @Override 53 | public void onReactContextInitialized(ReactContext reactContext) { 54 | reactInstanceManager.removeReactInstanceEventListener(this); 55 | reactContext.runOnNativeModulesQueueThread( 56 | new Runnable() { 57 | @Override 58 | public void run() { 59 | client.addPlugin(new FrescoFlipperPlugin()); 60 | } 61 | }); 62 | } 63 | }); 64 | } else { 65 | client.addPlugin(new FrescoFlipperPlugin()); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/pusherwebsocketreactnative/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.pusherwebsocketreactnative; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.facebook.react.ReactActivityDelegate; 5 | import com.facebook.react.ReactRootView; 6 | 7 | public class MainActivity extends ReactActivity { 8 | 9 | /** 10 | * Returns the name of the main component registered from JavaScript. This is used to schedule 11 | * rendering of the component. 12 | */ 13 | @Override 14 | protected String getMainComponentName() { 15 | return "PusherWebsocketReactNativeExample"; 16 | } 17 | 18 | /** 19 | * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and 20 | * you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer 21 | * (Paper). 22 | */ 23 | @Override 24 | protected ReactActivityDelegate createReactActivityDelegate() { 25 | return new MainActivityDelegate(this, getMainComponentName()); 26 | } 27 | 28 | public static class MainActivityDelegate extends ReactActivityDelegate { 29 | public MainActivityDelegate(ReactActivity activity, String mainComponentName) { 30 | super(activity, mainComponentName); 31 | } 32 | 33 | @Override 34 | protected ReactRootView createRootView() { 35 | ReactRootView reactRootView = new ReactRootView(getContext()); 36 | // If you opted-in for the New Architecture, we enable the Fabric Renderer. 37 | reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); 38 | return reactRootView; 39 | } 40 | 41 | @Override 42 | protected boolean isConcurrentRootEnabled() { 43 | // If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18). 44 | // More on this on https://reactjs.org/blog/2022/03/29/react-v18.html 45 | return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/pusherwebsocketreactnative/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.pusherwebsocketreactnative; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import com.facebook.react.PackageList; 6 | import com.facebook.react.ReactApplication; 7 | import com.facebook.react.ReactNativeHost; 8 | import com.facebook.react.ReactPackage; 9 | import com.facebook.react.ReactInstanceManager; 10 | import com.facebook.soloader.SoLoader; 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.util.List; 13 | import com.pusherwebsocketreactnative.PusherWebsocketReactNativePackage; 14 | 15 | public class MainApplication extends Application implements ReactApplication { 16 | 17 | private final ReactNativeHost mReactNativeHost = 18 | new ReactNativeHost(this) { 19 | @Override 20 | public boolean getUseDeveloperSupport() { 21 | return BuildConfig.DEBUG; 22 | } 23 | 24 | @Override 25 | protected List getPackages() { 26 | @SuppressWarnings("UnnecessaryLocalVariable") 27 | List packages = new PackageList(this).getPackages(); 28 | // Packages that cannot be autolinked yet can be added manually here, for PusherWebsocketReactNativeExample: 29 | // packages.add(new MyReactNativePackage()); 30 | packages.add(new PusherWebsocketReactNativePackage()); 31 | return packages; 32 | } 33 | 34 | @Override 35 | protected String getJSMainModuleName() { 36 | return "index"; 37 | } 38 | }; 39 | 40 | @Override 41 | public ReactNativeHost getReactNativeHost() { 42 | return mReactNativeHost; 43 | } 44 | 45 | @Override 46 | public void onCreate() { 47 | super.onCreate(); 48 | SoLoader.init(this, /* native exopackage */ false); 49 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); // Remove this line if you don't want Flipper enabled 50 | } 51 | 52 | /** 53 | * Loads Flipper in React Native templates. 54 | * 55 | * @param context 56 | */ 57 | private static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 58 | if (BuildConfig.DEBUG) { 59 | try { 60 | /* 61 | We use reflection here to pick up the class that initializes Flipper, 62 | since Flipper library is not available in release mode 63 | */ 64 | Class aClass = Class.forName("com.example.pusherwebsocketreactnative.ReactNativeFlipper"); 65 | aClass 66 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) 67 | .invoke(null, context, reactInstanceManager); 68 | } catch (ClassNotFoundException e) { 69 | e.printStackTrace(); 70 | } catch (NoSuchMethodException e) { 71 | e.printStackTrace(); 72 | } catch (IllegalAccessException e) { 73 | e.printStackTrace(); 74 | } catch (InvocationTargetException e) { 75 | e.printStackTrace(); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PusherWebsocketReactNative Example 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | minSdkVersion = 21 6 | compileSdkVersion = 33 7 | targetSdkVersion = 33 8 | 9 | if (System.properties['os.arch'] == "aarch64") { 10 | // For M1 Users we need to use the NDK 24 which added support for aarch64 11 | ndkVersion = "24.0.8215888" 12 | } else { 13 | // Otherwise we default to the side-by-side NDK version from AGP. 14 | ndkVersion = "21.4.7075529" 15 | } 16 | } 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | dependencies { 22 | classpath('com.android.tools.build:gradle:7.3.1') 23 | classpath("com.facebook.react:react-native-gradle-plugin") 24 | classpath("de.undercouch:gradle-download-task:5.0.1") 25 | // NOTE: Do not place your application dependencies here; they belong 26 | // in the individual module build.gradle files 27 | } 28 | } 29 | 30 | allprojects { 31 | repositories { 32 | mavenLocal() 33 | maven { 34 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 35 | url("$rootDir/../node_modules/react-native/android") 36 | } 37 | maven { 38 | // Android JSC is installed from npm 39 | url("$rootDir/../node_modules/jsc-android/dist") 40 | } 41 | 42 | google() 43 | mavenCentral() 44 | maven { url 'https://www.jitpack.io' } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.125.0 29 | 30 | # Use this property to specify which architecture you want to build. 31 | # You can also override it from the CLI using 32 | # ./gradlew -PreactNativeArchitectures=x86_64 33 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 34 | 35 | # Use this property to enable support to the new architecture. 36 | # This will allow you to use TurboModules and the Fabric render in 37 | # your application. You should enable this flag either if you want 38 | # to write custom TurboModules/Fabric components OR use libraries that 39 | # are providing them. 40 | newArchEnabled=false 41 | kotlinVersion=1.7.21 42 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 30 18:12:52 CEST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem http://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'PusherWebsocketReactNativeExample' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | 5 | include ':pusher-websocket-react-native' 6 | project(':pusher-websocket-react-native').projectDir = new File(rootProject.projectDir, '../../android') 7 | 8 | includeBuild('../node_modules/react-native-gradle-plugin') 9 | 10 | if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") { 11 | include(":ReactAndroid") 12 | project(":ReactAndroid").projectDir = file('../node_modules/react-native/ReactAndroid') 13 | include(":ReactAndroid:hermes-engine") 14 | project(":ReactAndroid:hermes-engine").projectDir = file('../node_modules/react-native/ReactAndroid/hermes-engine') 15 | } 16 | 17 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PusherWebsocketReactNativeExample", 3 | "displayName": "PusherWebsocketReactNative Example" 4 | } 5 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pak = require('../package.json'); 3 | 4 | module.exports = { 5 | presets: ['module:metro-react-native-babel-preset'], 6 | plugins: [ 7 | [ 8 | 'module-resolver', 9 | { 10 | extensions: ['.tsx', '.ts', '.js', '.json'], 11 | alias: { 12 | [pak.name]: path.join(__dirname, '..', pak.source), 13 | }, 14 | }, 15 | ], 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /example/index.tsx: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './src/App'; 3 | import { name as appName } from './app.json'; 4 | 5 | AppRegistry.registerComponent(appName, () => App); 6 | -------------------------------------------------------------------------------- /example/ios/File.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // PusherWebsocketReactNativeExample 4 | // 5 | 6 | import Foundation 7 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, '13.0' 5 | 6 | install! 'cocoapods', :deterministic_uuids => false 7 | 8 | target 'PusherWebsocketReactNativeExample' do 9 | config = use_native_modules! 10 | 11 | # Flags change depending on the env values. 12 | flags = get_default_flags() 13 | 14 | pod 'pusher-websocket-react-native', :path => '../..' 15 | use_react_native!( 16 | :path => config[:reactNativePath], 17 | # Hermes is now enabled by default. Disable by setting this flag to false. 18 | # Upcoming versions of React Native may rely on get_default_flags(), but 19 | # we make it explicit here to aid in the React Native upgrade process. 20 | :hermes_enabled => true, 21 | :fabric_enabled => false, 22 | # Enables Flipper. 23 | # 24 | # Note that if you have use_frameworks! enabled, Flipper will not work and 25 | # you should disable the next line. 26 | :flipper_configuration => FlipperConfiguration.enabled, 27 | # An absolute path to your application root. 28 | :app_path => "#{Pod::Config.instance.installation_root}/.." 29 | ) 30 | 31 | post_install do |installer| 32 | react_native_post_install( 33 | installer, 34 | # Set `mac_catalyst_enabled` to `true` in order to apply patches 35 | # necessary for Mac Catalyst builds 36 | :mac_catalyst_enabled => false 37 | ) 38 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample.xcodeproj/xcshareddata/xcschemes/PusherWebsocketReactNativeExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 55 | 56 | 66 | 68 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic, strong) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | #import "AppDelegate.h" 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | #import 14 | 15 | #if RCT_NEW_ARCH_ENABLED 16 | #import 17 | #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | 23 | #import 24 | 25 | static NSString *const kRNConcurrentRoot = @"concurrentRoot"; 26 | 27 | @interface AppDelegate () { 28 | RCTTurboModuleManager *_turboModuleManager; 29 | RCTSurfacePresenterBridgeAdapter *_bridgeAdapter; 30 | std::shared_ptr _reactNativeConfig; 31 | facebook::react::ContextContainer::Shared _contextContainer; 32 | } 33 | @end 34 | #endif 35 | 36 | @implementation AppDelegate 37 | 38 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 39 | { 40 | RCTAppSetupPrepareApp(application); 41 | 42 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 43 | 44 | #if RCT_NEW_ARCH_ENABLED 45 | _contextContainer = std::make_shared(); 46 | _reactNativeConfig = std::make_shared(); 47 | _contextContainer->insert("ReactNativeConfig", _reactNativeConfig); 48 | _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer]; 49 | bridge.surfacePresenter = _bridgeAdapter.surfacePresenter; 50 | #endif 51 | 52 | NSDictionary *initProps = [self prepareInitialProps]; 53 | UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"PusherWebsocketReactNativeExample", initProps); 54 | 55 | if (@available(iOS 13.0, *)) { 56 | rootView.backgroundColor = [UIColor systemBackgroundColor]; 57 | } else { 58 | rootView.backgroundColor = [UIColor whiteColor]; 59 | } 60 | 61 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 62 | UIViewController *rootViewController = [UIViewController new]; 63 | rootViewController.view = rootView; 64 | self.window.rootViewController = rootViewController; 65 | [self.window makeKeyAndVisible]; 66 | return YES; 67 | } 68 | 69 | /// This method controls whether the `concurrentRoot`feature of React18 is turned on or off. 70 | /// 71 | /// @see: https://reactjs.org/blog/2022/03/29/react-v18.html 72 | /// @note: This requires to be rendering on Fabric (i.e. on the New Architecture). 73 | /// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`. 74 | - (BOOL)concurrentRootEnabled 75 | { 76 | // Switch this bool to turn on and off the concurrent root 77 | return false; 78 | } 79 | 80 | - (NSDictionary *)prepareInitialProps 81 | { 82 | NSMutableDictionary *initProps = [NSMutableDictionary new]; 83 | 84 | #ifdef RCT_NEW_ARCH_ENABLED 85 | initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]); 86 | #endif 87 | 88 | return initProps; 89 | } 90 | 91 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 92 | { 93 | #if DEBUG 94 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 95 | #else 96 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 97 | #endif 98 | } 99 | 100 | #if RCT_NEW_ARCH_ENABLED 101 | 102 | #pragma mark - RCTCxxBridgeDelegate 103 | 104 | - (std::unique_ptr)jsExecutorFactoryForBridge:(RCTBridge *)bridge 105 | { 106 | _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge 107 | delegate:self 108 | jsInvoker:bridge.jsCallInvoker]; 109 | return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager); 110 | } 111 | 112 | #pragma mark RCTTurboModuleManagerDelegate 113 | 114 | - (Class)getModuleClassFromName:(const char *)name 115 | { 116 | return RCTCoreModulesClassProvider(name); 117 | } 118 | 119 | - (std::shared_ptr)getTurboModule:(const std::string &)name 120 | jsInvoker:(std::shared_ptr)jsInvoker 121 | { 122 | return nullptr; 123 | } 124 | 125 | - (std::shared_ptr)getTurboModule:(const std::string &)name 126 | initParams: 127 | (const facebook::react::ObjCTurboModule::InitParams &)params 128 | { 129 | return nullptr; 130 | } 131 | 132 | - (id)getModuleInstanceFromClass:(Class)moduleClass 133 | { 134 | return RCTAppSetupDefaultModuleFromClass(moduleClass); 135 | } 136 | 137 | #endif 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "icon-60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "icon-58.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "icon-87.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "icon-80.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "icon-120.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "icon-120.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "icon-180.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "icon-20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "icon-40.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "icon-29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "icon-58.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "icon-40.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "icon-80.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "icon-76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "icon-152.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "icon-167.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "icon-1024.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | }, 111 | { 112 | "filename" : "icon-120.png", 113 | "idiom" : "car", 114 | "scale" : "2x", 115 | "size" : "60x60" 116 | }, 117 | { 118 | "filename" : "icon-180.png", 119 | "idiom" : "car", 120 | "scale" : "3x", 121 | "size" : "60x60" 122 | }, 123 | { 124 | "filename" : "icon-48.png", 125 | "idiom" : "watch", 126 | "role" : "notificationCenter", 127 | "scale" : "2x", 128 | "size" : "24x24", 129 | "subtype" : "38mm" 130 | }, 131 | { 132 | "filename" : "icon-55.png", 133 | "idiom" : "watch", 134 | "role" : "notificationCenter", 135 | "scale" : "2x", 136 | "size" : "27.5x27.5", 137 | "subtype" : "42mm" 138 | }, 139 | { 140 | "filename" : "icon-58.png", 141 | "idiom" : "watch", 142 | "role" : "companionSettings", 143 | "scale" : "2x", 144 | "size" : "29x29" 145 | }, 146 | { 147 | "filename" : "icon-87.png", 148 | "idiom" : "watch", 149 | "role" : "companionSettings", 150 | "scale" : "3x", 151 | "size" : "29x29" 152 | }, 153 | { 154 | "idiom" : "watch", 155 | "role" : "notificationCenter", 156 | "scale" : "2x", 157 | "size" : "33x33", 158 | "subtype" : "45mm" 159 | }, 160 | { 161 | "filename" : "icon-80.png", 162 | "idiom" : "watch", 163 | "role" : "appLauncher", 164 | "scale" : "2x", 165 | "size" : "40x40", 166 | "subtype" : "38mm" 167 | }, 168 | { 169 | "filename" : "icon-88.png", 170 | "idiom" : "watch", 171 | "role" : "appLauncher", 172 | "scale" : "2x", 173 | "size" : "44x44", 174 | "subtype" : "40mm" 175 | }, 176 | { 177 | "idiom" : "watch", 178 | "role" : "appLauncher", 179 | "scale" : "2x", 180 | "size" : "46x46", 181 | "subtype" : "41mm" 182 | }, 183 | { 184 | "filename" : "icon-100.png", 185 | "idiom" : "watch", 186 | "role" : "appLauncher", 187 | "scale" : "2x", 188 | "size" : "50x50", 189 | "subtype" : "44mm" 190 | }, 191 | { 192 | "idiom" : "watch", 193 | "role" : "appLauncher", 194 | "scale" : "2x", 195 | "size" : "51x51", 196 | "subtype" : "45mm" 197 | }, 198 | { 199 | "filename" : "icon-172.png", 200 | "idiom" : "watch", 201 | "role" : "quickLook", 202 | "scale" : "2x", 203 | "size" : "86x86", 204 | "subtype" : "38mm" 205 | }, 206 | { 207 | "filename" : "icon-196.png", 208 | "idiom" : "watch", 209 | "role" : "quickLook", 210 | "scale" : "2x", 211 | "size" : "98x98", 212 | "subtype" : "42mm" 213 | }, 214 | { 215 | "filename" : "icon-216.png", 216 | "idiom" : "watch", 217 | "role" : "quickLook", 218 | "scale" : "2x", 219 | "size" : "108x108", 220 | "subtype" : "44mm" 221 | }, 222 | { 223 | "idiom" : "watch", 224 | "role" : "quickLook", 225 | "scale" : "2x", 226 | "size" : "117x117", 227 | "subtype" : "45mm" 228 | }, 229 | { 230 | "filename" : "icon-1024.png", 231 | "idiom" : "watch-marketing", 232 | "scale" : "1x", 233 | "size" : "1024x1024" 234 | }, 235 | { 236 | "filename" : "icon-16.png", 237 | "idiom" : "mac", 238 | "scale" : "1x", 239 | "size" : "16x16" 240 | }, 241 | { 242 | "filename" : "icon-32.png", 243 | "idiom" : "mac", 244 | "scale" : "2x", 245 | "size" : "16x16" 246 | }, 247 | { 248 | "filename" : "icon-32.png", 249 | "idiom" : "mac", 250 | "scale" : "1x", 251 | "size" : "32x32" 252 | }, 253 | { 254 | "filename" : "icon-64.png", 255 | "idiom" : "mac", 256 | "scale" : "2x", 257 | "size" : "32x32" 258 | }, 259 | { 260 | "filename" : "icon-128.png", 261 | "idiom" : "mac", 262 | "scale" : "1x", 263 | "size" : "128x128" 264 | }, 265 | { 266 | "filename" : "icon-256.png", 267 | "idiom" : "mac", 268 | "scale" : "2x", 269 | "size" : "128x128" 270 | }, 271 | { 272 | "filename" : "icon-256.png", 273 | "idiom" : "mac", 274 | "scale" : "1x", 275 | "size" : "256x256" 276 | }, 277 | { 278 | "filename" : "icon-512.png", 279 | "idiom" : "mac", 280 | "scale" : "2x", 281 | "size" : "256x256" 282 | }, 283 | { 284 | "filename" : "icon-512.png", 285 | "idiom" : "mac", 286 | "scale" : "1x", 287 | "size" : "512x512" 288 | }, 289 | { 290 | "filename" : "icon-1024.png", 291 | "idiom" : "mac", 292 | "scale" : "2x", 293 | "size" : "512x512" 294 | } 295 | ], 296 | "info" : { 297 | "author" : "xcode", 298 | "version" : 1 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-100.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-120.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-128.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-152.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-16.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-167.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-172.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-180.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-196.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-20.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-216.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-256.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-32.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-48.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-512.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-55.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-58.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-60.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-64.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-80.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-87.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/AppIcon.appiconset/icon-88.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-1024.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Images.xcassets/Image.imageset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/pusher-websocket-react-native/0795a0f7afadd2b08c018a59b60651146b77a699/example/ios/PusherWebsocketReactNativeExample/Images.xcassets/Image.imageset/icon-1024.png -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | PusherWebsocketReactNative Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSExceptionDomains 32 | 33 | localhost 34 | 35 | NSExceptionAllowsInsecureHTTPLoads 36 | 37 | 38 | 39 | 40 | NSLocationWhenInUseUsageDescription 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UIViewControllerBasedStatusBarAppearance 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/ios/PusherWebsocketReactNativeExample/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const exclusionList = require('metro-config/src/defaults/exclusionList'); 3 | const escape = require('escape-string-regexp'); 4 | const pak = require('../package.json'); 5 | 6 | const root = path.resolve(__dirname, '..'); 7 | 8 | const modules = Object.keys({ 9 | ...pak.peerDependencies, 10 | }); 11 | 12 | module.exports = { 13 | projectRoot: __dirname, 14 | watchFolders: [root], 15 | 16 | // We need to make sure that only one version is loaded for peerDependencies 17 | // So we blacklist them at the root, and alias them to the versions in example's node_modules 18 | resolver: { 19 | blacklistRE: exclusionList( 20 | modules.map( 21 | (m) => 22 | new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) 23 | ) 24 | ), 25 | 26 | extraNodeModules: modules.reduce((acc, name) => { 27 | acc[name] = path.join(__dirname, 'node_modules', name); 28 | return acc; 29 | }, {}), 30 | }, 31 | 32 | transformer: { 33 | getTransformOptions: async () => ({ 34 | transform: { 35 | experimentalImportSupport: false, 36 | inlineRequires: true, 37 | }, 38 | }), 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pusher-websocket-react-native-example", 3 | "description": "Example app for pusher-websocket-react-native", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "android": "npx react-native run-android", 8 | "ios": "npx react-native run-ios", 9 | "start": "npx react-native start" 10 | }, 11 | "dependencies": { 12 | "@react-native-async-storage/async-storage": "^1.17.10", 13 | "crypto-es": "^1.2.7", 14 | "react": "18.2.0", 15 | "react-native": "^0.70.5" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.20.2", 19 | "@babel/runtime": "^7.20.1", 20 | "babel-plugin-module-resolver": "^4.1.0", 21 | "metro-react-native-babel-preset": "^0.73.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { 4 | StyleSheet, 5 | View, 6 | Text, 7 | SafeAreaView, 8 | TextInput, 9 | Button, 10 | Image, 11 | ScrollView, 12 | FlatList, 13 | } from 'react-native'; 14 | import AsyncStorage from '@react-native-async-storage/async-storage'; 15 | import { 16 | Pusher, 17 | PusherMember, 18 | PusherChannel, 19 | PusherEvent, 20 | PusherAuthorizerResult, 21 | } from '../../src'; // This links the example app to the current SDK implementation 22 | 23 | export default function App() { 24 | let logLines: string[] = []; 25 | const pusher = Pusher.getInstance(); 26 | 27 | const [apiKey, onChangeApiKey] = React.useState(''); 28 | const [cluster, onChangeCluster] = React.useState(''); 29 | const [channelName, onChangeChannelName] = React.useState(''); 30 | const [eventName, onChangeEventName] = React.useState(''); 31 | const [eventData, onChangeEventData] = React.useState(''); 32 | const [members, onChangeMembers] = React.useState([]); 33 | const [logText, setLog] = React.useState(''); 34 | 35 | const log = async (line: string) => { 36 | logLines.push(line); 37 | setLog(logLines.join('\n')); 38 | }; 39 | 40 | React.useEffect(() => { 41 | const getFromStorage = async () => { 42 | onChangeApiKey((await AsyncStorage.getItem('APIKEY')) || ''); 43 | onChangeCluster((await AsyncStorage.getItem('CLUSTER')) || ''); 44 | onChangeChannelName((await AsyncStorage.getItem('CHANNEL')) || ''); 45 | onChangeEventName((await AsyncStorage.getItem('EVENT')) || ''); 46 | onChangeEventData((await AsyncStorage.getItem('DATA')) || ''); 47 | }; 48 | getFromStorage().catch((e) => log('ERROR: ' + e)); 49 | 50 | // eslint-disable-next-line react-hooks/exhaustive-deps 51 | }, []); 52 | 53 | const connect = async () => { 54 | try { 55 | await AsyncStorage.multiSet([ 56 | ['APIKEY', apiKey], 57 | ['CLUSTER', cluster], 58 | ['CHANNEL', channelName], 59 | ]); 60 | 61 | await pusher.init({ 62 | apiKey, 63 | cluster, 64 | // authEndpoint 65 | // ============ 66 | // You can let the pusher library call an endpoint URL, 67 | // Please look here to implement a server side authorizer: 68 | // https://pusher.com/docs/channels/server_api/authenticating-users/ 69 | // 70 | // authEndpoint: '', 71 | // 72 | // onAuthorizer 73 | // ============ 74 | // Or you can implement your own authorizer callback. 75 | // See https://pusher.com/docs/channels/library_auth_reference/auth-signatures/ 76 | // for the format of this object, you need your key and secret from your Pusher 77 | // Account. 78 | onAuthorizer, 79 | onConnectionStateChange, 80 | onError, 81 | onEvent, 82 | onSubscriptionSucceeded, 83 | onSubscriptionError, 84 | onSubscriptionCount, 85 | onDecryptionFailure, 86 | onMemberAdded, 87 | onMemberRemoved, 88 | }); 89 | 90 | await pusher.connect(); 91 | await pusher.subscribe({ channelName }); 92 | } catch (e) { 93 | log('ERROR: ' + e); 94 | } 95 | }; 96 | 97 | const onConnectionStateChange = ( 98 | currentState: string, 99 | previousState: string 100 | ) => { 101 | log( 102 | `onConnectionStateChange. previousState=${previousState} newState=${currentState}` 103 | ); 104 | }; 105 | 106 | const onError = (message: string, code: Number, error: any) => { 107 | log(`onError: ${message} code: ${code} exception: ${error}`); 108 | }; 109 | 110 | const onEvent = (event: any) => { 111 | log(`onEvent: ${event}`); 112 | }; 113 | 114 | const onSubscriptionSucceeded = (channelName: string, data: any) => { 115 | log( 116 | `onSubscriptionSucceeded: ${channelName} data: ${JSON.stringify(data)}` 117 | ); 118 | const channel: PusherChannel | undefined = pusher.getChannel(channelName); 119 | 120 | if (!channel) { 121 | return; 122 | } 123 | 124 | const me = channel.me; 125 | onChangeMembers([...channel.members.values()]); 126 | log(`Me: ${me}`); 127 | }; 128 | 129 | const onSubscriptionCount = ( 130 | channelName: string, 131 | subscriptionCount: Number 132 | ) => { 133 | log( 134 | `onSubscriptionCount: ${subscriptionCount}, channelName: ${channelName}` 135 | ); 136 | }; 137 | 138 | const onSubscriptionError = ( 139 | channelName: string, 140 | message: string, 141 | e: any 142 | ) => { 143 | log(`onSubscriptionError: ${message}, channelName: ${channelName} e: ${e}`); 144 | }; 145 | 146 | const onDecryptionFailure = (eventName: string, reason: string) => { 147 | log(`onDecryptionFailure: ${eventName} reason: ${reason}`); 148 | }; 149 | 150 | const onMemberAdded = (channelName: string, member: PusherMember) => { 151 | log(`onMemberAdded: ${channelName} user: ${member}`); 152 | const channel: PusherChannel | undefined = pusher.getChannel(channelName); 153 | 154 | if (!channel) { 155 | return; 156 | } 157 | 158 | onChangeMembers([...channel.members.values()]); 159 | }; 160 | 161 | const onMemberRemoved = (channelName: string, member: PusherMember) => { 162 | log(`onMemberRemoved: ${channelName} user: ${member}`); 163 | const channel: PusherChannel | undefined = pusher.getChannel(channelName); 164 | 165 | if (!channel) { 166 | return; 167 | } 168 | 169 | onChangeMembers([...channel.members.values()]); 170 | }; 171 | 172 | // See https://pusher.com/docs/channels/library_auth_reference/auth-signatures/ for the format of this object. 173 | const onAuthorizer = async (channelName: string, socketId: string) => { 174 | log( 175 | `calling onAuthorizer. channelName=${channelName}, socketId=${socketId}` 176 | ); 177 | 178 | const response = await fetch('some_url', { 179 | method: 'POST', 180 | headers: { 181 | 'Content-Type': 'application/json', 182 | }, 183 | body: JSON.stringify({ 184 | socket_id: socketId, 185 | channel_name: channelName, 186 | }), 187 | }); 188 | 189 | const body = (await response.json()) as PusherAuthorizerResult; 190 | 191 | log(`response: ${JSON.stringify(body)}`); 192 | return body; 193 | }; 194 | 195 | const trigger = async () => { 196 | try { 197 | await AsyncStorage.multiSet([ 198 | ['EVENT', eventName], 199 | ['DATA', eventData], 200 | ]); 201 | 202 | await pusher.trigger( 203 | new PusherEvent({ channelName, eventName, data: eventData }) 204 | ); 205 | } catch (e) { 206 | log('ERROR: ' + e); 207 | } 208 | }; 209 | 210 | return ( 211 | 212 | 213 | 214 | 215 | {pusher.connectionState === 'DISCONNECTED' 216 | ? 'Pusher Channels React Native Example' 217 | : channelName} 218 | 219 | 220 | {pusher.connectionState !== 'CONNECTED' ? ( 221 | <> 222 | 229 | 237 | 245 |