├── .changeset ├── README.md └── config.json ├── .eslintrc.js ├── .github ├── CODEOWNERS └── workflows │ ├── androidTests.yml │ ├── dev-release.yml │ ├── docs.yml │ ├── lint.yml │ ├── release.yml │ ├── test.yml │ ├── triage.yml │ └── tsc.yml ├── .gitignore ├── .node-version ├── .npmignore ├── .nvmrc ├── .prettierrc.cjs ├── .swiftformat ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── expo │ └── modules │ └── xmtpreactnativesdk │ ├── XMTPModule.kt │ └── wrappers │ ├── AuthParamsWrapper.kt │ ├── ClientWrapper.kt │ ├── ConsentWrapper.kt │ ├── ContentJson.kt │ ├── ConversationDebugInfoWrapper.kt │ ├── ConversationWrapper.kt │ ├── CreateGroupParamsWrapper.kt │ ├── DecryptedLocalAttachment.kt │ ├── DisappearingMessageSettingsWrapper.kt │ ├── DmWrapper.kt │ ├── EncryptedAttachmentMetadata.kt │ ├── EncryptedLocalAttachment.kt │ ├── GroupWrapper.kt │ ├── InboxStateWrapper.kt │ ├── KeyPackageStatusWrapper.kt │ ├── LifetimeWrapper.kt │ ├── MemberWrapper.kt │ ├── MessageWrapper.kt │ ├── NetworkDebugInfoWrapper.kt │ ├── PermissionPolicySetWrapper.kt │ └── PublicIdentityWrapper.kt ├── example ├── .gitignore ├── App.tsx ├── EXAMPLE.env ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── expo │ │ │ │ └── modules │ │ │ │ └── xmtpreactnativesdk │ │ │ │ └── example │ │ │ │ ├── EspressoViewFinder.kt │ │ │ │ └── MainActivityTest.kt │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── expo │ │ │ │ │ └── modules │ │ │ │ │ └── xmtpreactnativesdk │ │ │ │ │ └── example │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ └── MainApplication.kt │ │ │ └── res │ │ │ │ ├── drawable-hdpi │ │ │ │ └── splashscreen_image.png │ │ │ │ ├── drawable-mdpi │ │ │ │ └── splashscreen_image.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ └── splashscreen_image.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ └── splashscreen_image.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ └── splashscreen_image.png │ │ │ │ ├── drawable │ │ │ │ ├── rn_edit_text_material.xml │ │ │ │ └── splashscreen.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── values-night │ │ │ │ └── colors.xml │ │ │ │ └── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── release │ │ │ └── java │ │ │ └── expo │ │ │ └── modules │ │ │ └── xmtpreactnativesdk │ │ │ └── example │ │ │ └── ReactNativeFlipper.java │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── assets │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ └── splash.png ├── dev │ └── local │ │ ├── docker-compose.yml │ │ ├── test │ │ ├── Dockerfile │ │ └── script.js │ │ └── upload-service │ │ ├── .gitignore │ │ ├── Caddyfile │ │ ├── Dockerfile │ │ └── app.rb ├── index.js ├── ios │ ├── .gitignore │ ├── .xcode.env │ ├── Podfile │ ├── Podfile.lock │ ├── Podfile.properties.json │ ├── xmtpreactnativesdkexample.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── xmtpreactnativesdkexample.xcscheme │ ├── xmtpreactnativesdkexample.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ ├── xmtpreactnativesdkexample │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── App-Icon-20x20@1x.png │ │ │ │ ├── App-Icon-20x20@2x.png │ │ │ │ ├── App-Icon-20x20@3x.png │ │ │ │ ├── App-Icon-29x29@1x.png │ │ │ │ ├── App-Icon-29x29@2x.png │ │ │ │ ├── App-Icon-29x29@3x.png │ │ │ │ ├── App-Icon-40x40@1x.png │ │ │ │ ├── App-Icon-40x40@2x.png │ │ │ │ ├── App-Icon-40x40@3x.png │ │ │ │ ├── App-Icon-60x60@2x.png │ │ │ │ ├── App-Icon-60x60@3x.png │ │ │ │ ├── App-Icon-76x76@1x.png │ │ │ │ ├── App-Icon-76x76@2x.png │ │ │ │ ├── App-Icon-83.5x83.5@2x.png │ │ │ │ ├── Contents.json │ │ │ │ └── ItunesArtwork@2x.png │ │ │ ├── SplashScreen.imageset │ │ │ │ └── image.png │ │ │ └── SplashScreenBackground.imageset │ │ │ │ └── image.png │ │ ├── Info.plist │ │ ├── PrivacyInfo.xcprivacy │ │ ├── SplashScreen.storyboard │ │ ├── Supporting │ │ │ └── Expo.plist │ │ ├── main.m │ │ ├── noop-file.swift │ │ └── xmtpreactnativesdkexample.entitlements │ └── xmtpreactnativesdkexampleUITests │ │ └── xmtpreactnativesdkexampleUITests.swift ├── metro.config.js ├── package.json ├── src │ ├── ConversationCreateScreen.tsx │ ├── ConversationScreen.tsx │ ├── GroupScreen.tsx │ ├── HomeScreen.tsx │ ├── LaunchScreen.tsx │ ├── Navigation.tsx │ ├── StreamScreen.tsx │ ├── TestScreen.tsx │ ├── contentTypes │ │ └── contentTypes.ts │ ├── hooks.tsx │ ├── storage.ts │ ├── tests │ │ ├── clientTests.ts │ │ ├── contentTypeTests.ts │ │ ├── conversationTests.ts │ │ ├── createdAtTests.ts │ │ ├── dmTests.ts │ │ ├── groupPerformanceTests.ts │ │ ├── groupPermissionsTests.ts │ │ ├── groupTests.ts │ │ ├── historySyncTests.ts │ │ ├── restartStreamsTests.ts │ │ └── test-utils.ts │ └── types │ │ ├── react-native-config.d.ts │ │ └── typeTests.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── expo-module.config.json ├── ios ├── ReactNativeSigner.swift ├── Wrappers │ ├── AuthParamsWrapper.swift │ ├── BinaryDataWrapper.swift │ ├── ClientWrapper.swift │ ├── ConsentWrapper.swift │ ├── ConversationDebugInfoWrapper.swift │ ├── ConversationWrapper.swift │ ├── CreateGroupParamsWrapper.swift │ ├── DisappearingMessageSettingsWrapper.swift │ ├── DmWrapper.swift │ ├── GroupWrapper.swift │ ├── InboxStateWrapper.swift │ ├── KeyPackageStatusWrapper.swift │ ├── MemberWrapper.swift │ ├── MessageWrapper.swift │ ├── NetworkDebugInfoWrapper.swift │ ├── PermissionPolicySetWrapper.swift │ ├── PublicIdentityWrapper.swift │ └── Wrapper.swift ├── XMTPModule.swift └── XMTPReactNative.podspec ├── package.json ├── packaging_notes.md ├── src ├── XMTPModule.ts ├── XMTPModule.web.ts ├── context │ ├── XmtpContext.tsx │ └── index.ts ├── hooks │ ├── index.ts │ ├── useClient.ts │ └── useXmtp.ts ├── index.ts ├── lib │ ├── Client.ts │ ├── ConsentRecord.ts │ ├── ContentCodec.ts │ ├── Conversation.ts │ ├── ConversationDebugInfo.ts │ ├── Conversations.ts │ ├── DecodedMessage.ts │ ├── DisappearingMessageSettings.ts │ ├── Dm.ts │ ├── Group.ts │ ├── InboxState.ts │ ├── Member.ts │ ├── NativeCodecs │ │ ├── GroupUpdatedCodec.ts │ │ ├── MultiRemoteAttachmentCodec.ts │ │ ├── ReactionCodec.ts │ │ ├── ReactionV2Codec.ts │ │ ├── ReadReceiptCodec.ts │ │ ├── RemoteAttachmentCodec.ts │ │ ├── ReplyCodec.ts │ │ ├── StaticAttachmentCodec.ts │ │ └── TextCodec.ts │ ├── PrivatePreferences.ts │ ├── PublicIdentity.ts │ ├── Signer.ts │ ├── XMTPDebugInformation.ts │ ├── XMTPPush.ts │ ├── types │ │ ├── ContentCodec.ts │ │ ├── ConversationCodecs.ts │ │ ├── ConversationOptions.ts │ │ ├── CreateGroupOptions.ts │ │ ├── DecodedMessageUnion.ts │ │ ├── DefaultContentType.ts │ │ ├── EventTypes.ts │ │ ├── ExtractDecodedType.ts │ │ ├── LogTypes.ts │ │ ├── MessagesOptions.ts │ │ ├── PermissionPolicySet.ts │ │ ├── SendOptions.ts │ │ └── index.ts │ └── util.ts └── utils │ └── address.ts ├── tsconfig.json ├── typedoc.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-git", 5 | { 6 | "repo": "xmtp/xmtp-react-native" 7 | } 8 | ], 9 | "commit": false, 10 | "fixed": [], 11 | "linked": [], 12 | "access": "public", 13 | "baseBranch": "main", 14 | "updateInternalDependencies": "patch", 15 | "ignore": [] 16 | } 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | parserOptions: { 5 | project: ["./tsconfig.json", "./example/tsconfig.json"] 6 | }, 7 | plugins: ["@typescript-eslint"], 8 | extends: ['universe/native', 'universe/web'], 9 | ignorePatterns: ['build'], 10 | plugins: ['prettier'], 11 | globals: { 12 | __dirname: true, 13 | }, 14 | rules: { 15 | "@typescript-eslint/no-floating-promises": ["error"], 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global rule: 2 | * @xmtp/protocol-sdk 3 | *.md @xmtp/documentation 4 | -------------------------------------------------------------------------------- /.github/workflows/androidTests.yml: -------------------------------------------------------------------------------- 1 | name: Android Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | env: 9 | GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | GPR_USER: ${{ secrets.GITHUB_ACTOR }} 11 | 12 | defaults: 13 | run: 14 | working-directory: ./example 15 | 16 | jobs: 17 | android-tests: 18 | name: Android Tests 19 | runs-on: macos-latest 20 | steps: 21 | - name: Checkout project sources 22 | uses: actions/checkout@v3 23 | 24 | - name: Setup Java 25 | uses: actions/setup-java@v3 26 | with: 27 | distribution: 'adopt' 28 | java-version: '11' 29 | 30 | - name: Setup Gradle 31 | uses: gradle/gradle-build-action@v2 32 | 33 | - name: Validate Gradle Wrapper 34 | uses: gradle/wrapper-validation-action@v1 35 | 36 | - name: Setup Node.js 37 | uses: actions/setup-node@v3 38 | 39 | - name: Install dependencies 40 | run: npm i --force 41 | 42 | - name: Install React Native CLI 43 | run: npm i -g react-native-cli --force 44 | 45 | - name: Install docker 46 | run: brew install docker docker-compose 47 | 48 | - name: Start colima 49 | run: colima start 50 | 51 | - name: Start local test server 52 | run: docker-compose -p xmtp -f dev/local/docker-compose.yml up -d 53 | 54 | - name: Gradle Run Integration Tests 55 | uses: reactivecircus/android-emulator-runner@v2 56 | with: 57 | working-directory: ./example 58 | api-level: 29 59 | script: | 60 | react-native run-android 61 | sleep 15 62 | cd android && ./gradlew connectedCheck 63 | 64 | - name: Stop local test server 65 | run: docker-compose -p xmtp -f dev/local/docker-compose.yml down 66 | -------------------------------------------------------------------------------- /.github/workflows/dev-release.yml: -------------------------------------------------------------------------------- 1 | name: Dev Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | dev-release: 8 | name: Dev Release 9 | runs-on: ubuntu-latest 10 | permissions: 11 | id-token: write 12 | contents: write 13 | pull-requests: write 14 | issues: write 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | persist-credentials: true 21 | 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version-file: ".nvmrc" 26 | cache: "yarn" 27 | 28 | - name: Update npm to latest 29 | run: npm install -g npm@latest 30 | 31 | - name: Install dependencies 32 | run: yarn install --frozen-lockfile 33 | 34 | - name: Validate package version format 35 | id: validate-version 36 | run: | 37 | PACKAGE_VERSION=$(node -p "require('./package.json').version") 38 | echo "Current package version: $PACKAGE_VERSION" 39 | 40 | if [[ ! $PACKAGE_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+-dev$ ]]; then 41 | echo "Error: Package version must be in format MAJOR.MINOR.PATCH-dev" 42 | echo "Current version is $PACKAGE_VERSION" 43 | exit 1 44 | fi 45 | 46 | echo "package_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT 47 | 48 | - name: Get commit SHA 49 | id: get-sha 50 | run: | 51 | SHA=$(git rev-parse --short=7 HEAD) 52 | echo "sha=$SHA" >> $GITHUB_OUTPUT 53 | 54 | - name: Create updated version 55 | id: create-version 56 | run: | 57 | UPDATED_VERSION="${{ steps.validate-version.outputs.package_version }}.${{ steps.get-sha.outputs.sha }}" 58 | echo "Updated version will be: $UPDATED_VERSION" 59 | echo "updated_version=$UPDATED_VERSION" >> $GITHUB_OUTPUT 60 | 61 | - name: Update package.json version 62 | run: | 63 | UPDATED_VERSION="${{ steps.create-version.outputs.updated_version }}" 64 | node -e " 65 | const fs = require('fs'); 66 | const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); 67 | packageJson.version = '$UPDATED_VERSION'; 68 | fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2) + '\n'); 69 | " 70 | echo "Updated package.json version to $UPDATED_VERSION" 71 | 72 | - name: Create and push Git tag 73 | run: | 74 | UPDATED_VERSION="${{ steps.create-version.outputs.updated_version }}" 75 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 76 | git config --local user.name "github-actions[bot]" 77 | git tag -a "v$UPDATED_VERSION" -m "Dev release v$UPDATED_VERSION" 78 | git push origin "v$UPDATED_VERSION" 79 | env: 80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | 82 | - name: Build package 83 | run: yarn build 84 | 85 | - name: Publish to NPM 86 | uses: JS-DevTools/npm-publish@v3 87 | with: 88 | token: ${{ secrets.NPM_TOKEN }} 89 | tag: prerelease 90 | dry-run: false 91 | env: 92 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build-and-deploy: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: 18.14.0 19 | - name: Install dependencies 20 | run: yarn install --frozen-lockfile 21 | - name: Build typedocs 22 | run: npm run typedoc 23 | - name: Deploy 24 | uses: peaceiris/actions-gh-pages@v3 25 | with: 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | publish_dir: ./docs 28 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | pull_request: 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | - uses: actions/setup-node@v3 12 | - run: npm install 13 | - run: npm run eslint 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | permissions: 15 | id-token: write 16 | contents: write 17 | pull-requests: write 18 | issues: write 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | persist-credentials: false 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version-file: ".nvmrc" 29 | cache: "yarn" 30 | - name: Update npm to latest 31 | run: npm install -g npm@latest 32 | - name: Install dependencies 33 | run: yarn install --frozen-lockfile 34 | - name: Publish 35 | uses: changesets/action@v1 36 | with: 37 | title: "release: version packages" 38 | commit: "release: version packages" 39 | publish: yarn run release 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | - uses: actions/setup-node@v3 12 | # Note: this triggers `prepare` which compiles the types. 13 | - run: yarn install --frozen-lockfile 14 | -------------------------------------------------------------------------------- /.github/workflows/triage.yml: -------------------------------------------------------------------------------- 1 | name: Add issues to project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | call-add-issues-to-project: 10 | uses: xmtp/workflow_calls/.github/workflows/add-issues-to-project.yml@main 11 | secrets: inherit -------------------------------------------------------------------------------- /.github/workflows/tsc.yml: -------------------------------------------------------------------------------- 1 | name: Typescript 2 | on: 3 | pull_request: 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | - uses: actions/setup-node@v3 12 | - run: yarn 13 | - run: yarn tsc 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # VSCode 6 | .vscode/ 7 | jsconfig.json 8 | 9 | # Xcode 10 | # 11 | build/ 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | *.xccheckout 22 | *.moved-aside 23 | DerivedData 24 | *.hmap 25 | *.ipa 26 | *.xcuserstate 27 | project.xcworkspace 28 | 29 | # Android/IJ 30 | # 31 | .classpath 32 | .cxx 33 | .gradle 34 | .idea 35 | .project 36 | .settings 37 | local.properties 38 | android.iml 39 | *google-services.json 40 | 41 | # Cocoapods 42 | # 43 | example/ios/Pods 44 | 45 | # Ruby 46 | example/vendor/ 47 | 48 | # node.js 49 | # 50 | node_modules/ 51 | npm-debug.log 52 | yarn-debug.log 53 | yarn-error.log 54 | 55 | # BUCK 56 | buck-out/ 57 | \.buckd/ 58 | android/app/libs 59 | android/keystores/debug.keystore 60 | 61 | # Expo 62 | .expo/* 63 | .env 64 | 65 | # Typedocs 66 | docs/ 67 | **/.yarn/* -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 20.17.0 2 | 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Exclude all top-level hidden directories by convention 2 | /.*/ 3 | 4 | __mocks__ 5 | __tests__ 6 | 7 | /babel.config.js 8 | /android/src/androidTest/ 9 | /android/src/test/ 10 | /android/build/ 11 | /example/ 12 | 13 | ios/.xcode.env.local 14 | 15 | # Exclude tarballs generated by `npm pack` 16 | /*.tgz -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.17.0 2 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | trailingComma: 'es5', 5 | arrowParens: 'always', 6 | printWidth: 80, 7 | } 8 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --indent tab 2 | --elseposition same-line 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 XMTP (xmtp.org) 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 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'maven-publish' 4 | 5 | group = 'expo.modules.xmtpreactnativesdk' 6 | version = '0.1.0' 7 | 8 | buildscript { 9 | def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") 10 | if (expoModulesCorePlugin.exists()) { 11 | apply from: expoModulesCorePlugin 12 | applyKotlinExpoModulesCorePlugin() 13 | } 14 | 15 | // Simple helper that allows the root project to override versions declared by this library. 16 | ext.safeExtGet = { prop, fallback -> 17 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 18 | } 19 | 20 | // Ensures backward compatibility 21 | ext.getKotlinVersion = { 22 | if (ext.has("kotlinVersion")) { 23 | ext.kotlinVersion() 24 | } else { 25 | ext.safeExtGet("kotlinVersion", "1.6.10") 26 | } 27 | } 28 | 29 | repositories { 30 | mavenCentral() 31 | } 32 | 33 | dependencies { 34 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") 35 | } 36 | } 37 | 38 | // Creating sources with comments 39 | if (GradleVersion.current() < GradleVersion.version('8.0')) { 40 | task androidSourcesJar(type: Jar) { 41 | classifier = 'sources' 42 | from android.sourceSets.main.java.srcDirs 43 | } 44 | } else { 45 | task androidSourcesJar(type: Jar) { 46 | archiveClassifier = 'sources' 47 | from android.sourceSets.main.java.srcDirs 48 | } 49 | } 50 | 51 | afterEvaluate { 52 | publishing { 53 | publications { 54 | release(MavenPublication) { 55 | from components.release 56 | // Add additional sourcesJar to artifacts 57 | artifact(androidSourcesJar) 58 | } 59 | } 60 | repositories { 61 | maven { 62 | url = mavenLocal().url 63 | } 64 | } 65 | } 66 | } 67 | 68 | android { 69 | compileSdkVersion safeExtGet("compileSdkVersion", 33) 70 | 71 | def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION 72 | if (agpVersion.tokenize('.')[0].toInteger() < 8) { 73 | compileOptions { 74 | sourceCompatibility JavaVersion.VERSION_11 75 | targetCompatibility JavaVersion.VERSION_11 76 | } 77 | 78 | kotlinOptions { 79 | jvmTarget = JavaVersion.VERSION_11.majorVersion 80 | } 81 | } 82 | 83 | defaultConfig { 84 | minSdkVersion safeExtGet("minSdkVersion", 23) 85 | targetSdkVersion safeExtGet("targetSdkVersion", 31) 86 | versionCode 1 87 | versionName "0.1.0" 88 | } 89 | lintOptions { 90 | abortOnError false 91 | } 92 | } 93 | 94 | repositories { 95 | mavenCentral() 96 | } 97 | 98 | dependencies { 99 | implementation project(':expo-modules-core') 100 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" 101 | implementation "org.xmtp:android:4.2.0" 102 | implementation 'com.google.code.gson:gson:2.10.1' 103 | implementation 'com.facebook.react:react-native:0.71.3' 104 | implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1" 105 | // xmtp-android local testing setup below (comment org.xmtp:android above) 106 | // implementation files('/xmtp-android/library/build/outputs/aar/library-debug.aar') 107 | // implementation 'com.google.crypto.tink:tink-android:1.8.0' 108 | // implementation 'io.grpc:grpc-kotlin-stub:1.4.1' 109 | // implementation 'io.grpc:grpc-okhttp:1.62.2' 110 | // implementation 'io.grpc:grpc-protobuf-lite:1.62.2' 111 | // implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0' 112 | // implementation 'org.web3j:crypto:4.9.4' 113 | // implementation "net.java.dev.jna:jna:5.14.0@aar" 114 | // api 'com.google.protobuf:protobuf-kotlin-lite:3.22.3' 115 | // api 'org.xmtp:proto-kotlin:3.72.4' 116 | } 117 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/AuthParamsWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.JsonParser 4 | import org.xmtp.android.library.SignerType 5 | 6 | class AuthParamsWrapper( 7 | val environment: String, 8 | val dbDirectory: String?, 9 | val historySyncUrl: String?, 10 | val customLocalUrl: String?, 11 | ) { 12 | companion object { 13 | fun authParamsFromJson(authParams: String): AuthParamsWrapper { 14 | val jsonOptions = JsonParser.parseString(authParams).asJsonObject 15 | return AuthParamsWrapper( 16 | jsonOptions.get("environment").asString, 17 | if (jsonOptions.has("dbDirectory")) jsonOptions.get("dbDirectory").asString else null, 18 | if (jsonOptions.has("historySyncUrl")) jsonOptions.get("historySyncUrl").asString else null, 19 | if (jsonOptions.has("customLocalUrl")) jsonOptions.get("customLocalUrl").asString else null, 20 | ) 21 | } 22 | } 23 | } 24 | 25 | class WalletParamsWrapper( 26 | val signerType: SignerType = SignerType.EOA, 27 | val chainId: Long?, 28 | val blockNumber: Long?, 29 | ) { 30 | companion object { 31 | fun walletParamsFromJson(walletParams: String): WalletParamsWrapper { 32 | val jsonOptions = JsonParser.parseString(walletParams).asJsonObject 33 | return WalletParamsWrapper( 34 | if (jsonOptions.has("signerType")) { 35 | when (jsonOptions.get("signerType").asString) { 36 | "SCW" -> SignerType.SCW 37 | else -> SignerType.EOA 38 | } 39 | } else { 40 | SignerType.EOA 41 | }, 42 | if (jsonOptions.has("chainId")) jsonOptions.get("chainId").asLong else null, 43 | if (jsonOptions.has("blockNumber")) jsonOptions.get("blockNumber").asLong else null, 44 | ) 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ClientWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import org.xmtp.android.library.Client 4 | 5 | class ClientWrapper { 6 | companion object { 7 | fun encodeToObj(client: Client): Map { 8 | return mapOf( 9 | "inboxId" to client.inboxId, 10 | "installationId" to client.installationId, 11 | "dbPath" to client.dbPath, 12 | "publicIdentity" to PublicIdentityWrapper.encode(client.publicIdentity) 13 | ) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConsentWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.GsonBuilder 4 | import com.google.gson.JsonParser 5 | import org.xmtp.android.library.ConsentRecord 6 | import org.xmtp.android.library.ConsentState 7 | import org.xmtp.android.library.EntryType 8 | import org.xmtp.android.library.XMTPException 9 | 10 | class ConsentWrapper { 11 | 12 | companion object { 13 | fun encode(model: ConsentRecord): String { 14 | val gson = GsonBuilder().create() 15 | val message = encodeMap(model) 16 | return gson.toJson(message) 17 | } 18 | 19 | fun encodeMap(model: ConsentRecord): Map = mapOf( 20 | "type" to model.entryType.name.lowercase(), 21 | "value" to model.value.lowercase(), 22 | "state" to consentStateToString(model.consentType), 23 | ) 24 | 25 | fun consentStateToString(state: ConsentState): String { 26 | return when (state) { 27 | ConsentState.ALLOWED -> "allowed" 28 | ConsentState.DENIED -> "denied" 29 | ConsentState.UNKNOWN -> "unknown" 30 | } 31 | } 32 | 33 | fun getConsentState(stateString: String): ConsentState { 34 | return when (stateString) { 35 | "allowed" -> ConsentState.ALLOWED 36 | "denied" -> ConsentState.DENIED 37 | else -> ConsentState.UNKNOWN 38 | } 39 | } 40 | 41 | fun getConsentStates(stateStrings: List): List { 42 | return stateStrings.map { stateString -> 43 | getConsentState(stateString) 44 | } 45 | } 46 | 47 | fun getEntryType(entryString: String): EntryType { 48 | return when (entryString) { 49 | "conversation_id" -> EntryType.CONVERSATION_ID 50 | "inbox_id" -> EntryType.INBOX_ID 51 | else -> throw XMTPException("Invalid entry type: $entryString") 52 | } 53 | } 54 | 55 | fun consentRecordFromJson(params: String): ConsentRecord { 56 | val jsonOptions = JsonParser.parseString(params).asJsonObject 57 | return ConsentRecord( 58 | jsonOptions.get("value").asString, 59 | getEntryType(jsonOptions.get("entryType").asString), 60 | getConsentState(jsonOptions.get("state").asString) 61 | ) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationDebugInfoWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import org.xmtp.android.library.libxmtp.ConversationDebugInfo 6 | 7 | class ConversationDebugInfoWrapper { 8 | companion object { 9 | val gson: Gson = GsonBuilder().create() 10 | private fun encodeToObj(info: ConversationDebugInfo): Map { 11 | return mapOf( 12 | "epoch" to info.epoch, 13 | "maybeForked" to info.maybeForked, 14 | "forkDetails" to info.forkDetails, 15 | ) 16 | } 17 | 18 | fun encode(info: ConversationDebugInfo): String { 19 | val obj = encodeToObj(info) 20 | return gson.toJson(obj) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.GsonBuilder 4 | import org.xmtp.android.library.Client 5 | import org.xmtp.android.library.Conversation 6 | 7 | class ConversationWrapper { 8 | 9 | companion object { 10 | suspend fun encodeToObj( 11 | client: Client, 12 | conversation: Conversation, 13 | conversationParams: ConversationParamsWrapper = ConversationParamsWrapper(), 14 | ): Map { 15 | return when (conversation.type) { 16 | Conversation.Type.GROUP -> { 17 | val group = (conversation as Conversation.Group).group 18 | GroupWrapper.encodeToObj(client, group, conversationParams) 19 | } 20 | 21 | Conversation.Type.DM -> { 22 | val dm = (conversation as Conversation.Dm).dm 23 | DmWrapper.encodeToObj(client, dm, conversationParams) 24 | } 25 | } 26 | } 27 | 28 | suspend fun encode( 29 | client: Client, 30 | conversation: Conversation, 31 | conversationParams: ConversationParamsWrapper = ConversationParamsWrapper(), 32 | ): String { 33 | val gson = GsonBuilder().create() 34 | val obj = 35 | encodeToObj(client, conversation, conversationParams) 36 | return gson.toJson(obj) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/CreateGroupParamsWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.JsonParser 4 | import org.xmtp.android.library.libxmtp.DisappearingMessageSettings 5 | 6 | class CreateGroupParamsWrapper( 7 | val groupName: String, 8 | val groupImageUrl: String, 9 | val groupDescription: String, 10 | val disappearingMessageSettings: DisappearingMessageSettings, 11 | ) { 12 | companion object { 13 | fun createGroupParamsFromJson(authParams: String): CreateGroupParamsWrapper { 14 | val jsonOptions = JsonParser.parseString(authParams).asJsonObject 15 | val settings = DisappearingMessageSettings( 16 | if (jsonOptions.has("disappearStartingAtNs")) jsonOptions.get("disappearStartingAtNs").asLong else 0, 17 | if (jsonOptions.has("retentionDurationInNs")) jsonOptions.get("retentionDurationInNs").asLong else 0 18 | ) 19 | 20 | return CreateGroupParamsWrapper( 21 | if (jsonOptions.has("name")) jsonOptions.get("name").asString else "", 22 | if (jsonOptions.has("imageUrl")) jsonOptions.get("imageUrl").asString else "", 23 | if (jsonOptions.has("description")) jsonOptions.get("description").asString else "", 24 | settings 25 | ) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecryptedLocalAttachment.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.GsonBuilder 4 | import com.google.gson.JsonObject 5 | import com.google.gson.JsonParser 6 | 7 | /** 8 | * Refers to a decrypted attachment that is stored locally on the device. 9 | */ 10 | class DecryptedLocalAttachment( 11 | val fileUri: String, 12 | val mimeType: String, 13 | val filename: String, 14 | ) { 15 | companion object { 16 | private fun fromJsonObject(obj: JsonObject) = DecryptedLocalAttachment( 17 | obj.get("fileUri").asString, 18 | obj.get("mimeType")?.asString ?: "", 19 | obj.get("filename")?.asString ?: "", 20 | ) 21 | 22 | fun fromJson(json: String): DecryptedLocalAttachment { 23 | val obj = JsonParser.parseString(json).asJsonObject 24 | return fromJsonObject(obj); 25 | } 26 | } 27 | 28 | private fun toJsonMap(): Map = mapOf( 29 | "fileUri" to fileUri, 30 | "mimeType" to mimeType, 31 | "filename" to filename, 32 | ) 33 | 34 | fun toJson(): String = GsonBuilder().create().toJson(toJsonMap()) 35 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DisappearingMessageSettingsWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.GsonBuilder 4 | import org.xmtp.android.library.libxmtp.DisappearingMessageSettings 5 | 6 | class DisappearingMessageSettingsWrapper { 7 | 8 | companion object { 9 | fun encode(model: DisappearingMessageSettings): String { 10 | val gson = GsonBuilder().create() 11 | val message = encodeMap(model) 12 | return gson.toJson(message) 13 | } 14 | 15 | fun encodeMap(model: DisappearingMessageSettings): Map = mapOf( 16 | "disappearStartingAtNs" to model.disappearStartingAtNs, 17 | "retentionDurationInNs" to model.retentionDurationInNs, 18 | ) 19 | } 20 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DmWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.GsonBuilder 4 | import org.xmtp.android.library.Client 5 | import org.xmtp.android.library.Dm 6 | 7 | class DmWrapper { 8 | companion object { 9 | suspend fun encodeToObj( 10 | client: Client, 11 | dm: Dm, 12 | dmParams: ConversationParamsWrapper = ConversationParamsWrapper(), 13 | ): Map { 14 | return buildMap { 15 | put("clientInboxId", client.inboxId) 16 | put("id", dm.id) 17 | put("createdAt", dm.createdAt.time) 18 | put("version", "DM") 19 | put("topic", dm.topic) 20 | put("peerInboxId", dm.peerInboxId) 21 | if (dmParams.consentState) { 22 | put("consentState", consentStateToString(dm.consentState())) 23 | } 24 | if (dmParams.lastMessage) { 25 | val lastMessage = dm.lastMessage() 26 | if (lastMessage != null) { 27 | put("lastMessage", MessageWrapper.encode(lastMessage)) 28 | } 29 | } 30 | } 31 | } 32 | 33 | suspend fun encode( 34 | client: Client, 35 | dm: Dm, 36 | dmParams: ConversationParamsWrapper = ConversationParamsWrapper(), 37 | ): String { 38 | val gson = GsonBuilder().create() 39 | val obj = encodeToObj(client, dm, dmParams) 40 | return gson.toJson(obj) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/EncryptedAttachmentMetadata.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.facebook.common.util.Hex 4 | import com.google.gson.JsonObject 5 | import com.google.protobuf.ByteString 6 | import com.google.protobuf.kotlin.toByteString 7 | import org.xmtp.android.library.codecs.Attachment 8 | import org.xmtp.android.library.codecs.EncryptedEncodedContent 9 | import org.xmtp.android.library.codecs.RemoteAttachment 10 | 11 | /** 12 | * Describes the metadata for an encrypted attachment used to encrypt/decrypt the payload. 13 | */ 14 | class EncryptedAttachmentMetadata( 15 | val filename: String, 16 | val secret: ByteString, 17 | val salt: ByteString, 18 | val nonce: ByteString, 19 | val contentDigest: String, 20 | val contentLength: Int, 21 | ) { 22 | companion object { 23 | fun fromAttachment( 24 | attachment: Attachment, 25 | encrypted: EncryptedEncodedContent 26 | ) = EncryptedAttachmentMetadata( 27 | attachment.filename, 28 | encrypted.secret, 29 | encrypted.salt, 30 | encrypted.nonce, 31 | encrypted.contentDigest, 32 | attachment.data.size(), 33 | ) 34 | 35 | fun fromRemoteAttachment( 36 | remoteAttachment: RemoteAttachment, 37 | ) = EncryptedAttachmentMetadata( 38 | remoteAttachment.filename ?: "", 39 | remoteAttachment.secret, 40 | remoteAttachment.salt, 41 | remoteAttachment.nonce, 42 | remoteAttachment.contentDigest, 43 | remoteAttachment.contentLength ?: 0, 44 | ) 45 | 46 | fun fromJsonObj( 47 | obj: JsonObject, 48 | ) = EncryptedAttachmentMetadata( 49 | obj.get("filename").asString, 50 | Hex.hexStringToByteArray(obj.get("secret").asString).toByteString(), 51 | Hex.hexStringToByteArray(obj.get("salt").asString).toByteString(), 52 | Hex.hexStringToByteArray(obj.get("nonce").asString).toByteString(), 53 | obj.get("contentDigest").asString, 54 | obj.get("contentLength")?.asString?.let { Integer.parseInt(it) } ?: 0, 55 | ) 56 | } 57 | 58 | fun toJsonMap(): Map = mapOf( 59 | "filename" to filename, 60 | "secret" to Hex.encodeHex(secret.toByteArray(), false), 61 | "salt" to Hex.encodeHex(salt.toByteArray(), false), 62 | "nonce" to Hex.encodeHex(nonce.toByteArray(), false), 63 | "contentDigest" to contentDigest, 64 | "contentLength" to contentLength.toString(), 65 | ) 66 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/EncryptedLocalAttachment.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import android.net.Uri 4 | import com.google.gson.GsonBuilder 5 | import com.google.gson.JsonObject 6 | import com.google.gson.JsonParser 7 | import org.xmtp.android.library.codecs.Attachment 8 | import org.xmtp.android.library.codecs.EncryptedEncodedContent 9 | 10 | /** 11 | * Refers to an encrypted attachment that is stored locally on the device 12 | * alongside the metadata that can be used to encrypt/decrypt it. 13 | */ 14 | class EncryptedLocalAttachment( 15 | val encryptedLocalFileUri: String, 16 | val metadata: EncryptedAttachmentMetadata, 17 | ) { 18 | companion object { 19 | fun from( 20 | attachment: Attachment, 21 | encrypted: EncryptedEncodedContent, 22 | encryptedFile: Uri, 23 | ) = EncryptedLocalAttachment( 24 | encryptedFile.toString(), 25 | EncryptedAttachmentMetadata.fromAttachment( 26 | attachment, 27 | encrypted 28 | ) 29 | ) 30 | 31 | fun fromJsonObject(obj: JsonObject) = EncryptedLocalAttachment( 32 | obj.get("encryptedLocalFileUri").asString, 33 | EncryptedAttachmentMetadata.fromJsonObj(obj.get("metadata").asJsonObject), 34 | ) 35 | 36 | fun fromJson(json: String): EncryptedLocalAttachment { 37 | val obj = JsonParser.parseString(json).asJsonObject 38 | return fromJsonObject(obj); 39 | } 40 | } 41 | 42 | private fun toJsonMap(): Map = mapOf( 43 | "encryptedLocalFileUri" to encryptedLocalFileUri, 44 | "metadata" to metadata.toJsonMap() 45 | ) 46 | 47 | fun toJson(): String = GsonBuilder().create().toJson(toJsonMap()) 48 | } 49 | -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/InboxStateWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import org.xmtp.android.library.libxmtp.InboxState 6 | 7 | class InboxStateWrapper { 8 | companion object { 9 | val gson: Gson = GsonBuilder().create() 10 | private fun encodeToObj(inboxState: InboxState): Map { 11 | return mapOf( 12 | "inboxId" to inboxState.inboxId, 13 | "identities" to inboxState.identities.map { PublicIdentityWrapper.encode(it) }, 14 | "installations" to inboxState.installations.map { 15 | gson.toJson( 16 | mapOf( 17 | "id" to it.installationId, 18 | "createdAt" to it.createdAt?.time 19 | ) 20 | ) 21 | }, 22 | "recoveryIdentity" to PublicIdentityWrapper.encode(inboxState.recoveryPublicIdentity) 23 | ) 24 | } 25 | 26 | fun encode(inboxState: InboxState): String { 27 | val obj = encodeToObj(inboxState) 28 | return gson.toJson(obj) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/KeyPackageStatusWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import org.xmtp.android.library.libxmtp.InboxState 6 | import uniffi.xmtpv3.FfiKeyPackageStatus 7 | 8 | class KeyPackageStatusWrapper { 9 | companion object { 10 | val gson: Gson = GsonBuilder().create() 11 | private fun encodeToObj(keyPackageStatus: FfiKeyPackageStatus): Map { 12 | return mapOf( 13 | "lifetime" to LifetimeWrapper.encode(keyPackageStatus.lifetime), 14 | "validationError" to (keyPackageStatus.validationError ?: ""), 15 | ) 16 | } 17 | 18 | fun encode(keyPackageStatus: FfiKeyPackageStatus): String { 19 | val obj = encodeToObj(keyPackageStatus) 20 | return gson.toJson(obj) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/LifetimeWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import org.xmtp.android.library.libxmtp.InboxState 6 | import uniffi.xmtpv3.FfiKeyPackageStatus 7 | import uniffi.xmtpv3.FfiLifetime 8 | 9 | class LifetimeWrapper { 10 | companion object { 11 | val gson: Gson = GsonBuilder().create() 12 | private fun encodeToObj(lifetime: FfiLifetime?): Map { 13 | return mapOf( 14 | "notBefore" to (lifetime?.notBefore?.toInt() ?: -1), 15 | "notAfter" to (lifetime?.notAfter?.toInt() ?: -1), 16 | ) 17 | } 18 | 19 | fun encode(lifetime: FfiLifetime?): String { 20 | val obj = encodeToObj(lifetime) 21 | return gson.toJson(obj) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/MemberWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.GsonBuilder 4 | import org.xmtp.android.library.libxmtp.Member 5 | import org.xmtp.android.library.libxmtp.PermissionLevel 6 | import org.xmtp.android.library.libxmtp.GroupMembershipResult 7 | 8 | class MemberWrapper { 9 | companion object { 10 | private fun encodeToObj(member: Member): Map { 11 | val permissionString = when (member.permissionLevel) { 12 | PermissionLevel.MEMBER -> "member" 13 | PermissionLevel.ADMIN -> "admin" 14 | PermissionLevel.SUPER_ADMIN -> "super_admin" 15 | } 16 | return mapOf( 17 | "inboxId" to member.inboxId, 18 | "identities" to member.identities.map { PublicIdentityWrapper.encode(it) }, 19 | "permissionLevel" to permissionString, 20 | "consentState" to consentStateToString(member.consentState) 21 | ) 22 | } 23 | 24 | fun encode(member: Member): String { 25 | val gson = GsonBuilder().create() 26 | val obj = encodeToObj(member) 27 | return gson.toJson(obj) 28 | } 29 | } 30 | } 31 | 32 | class MembershipResultWrapper { 33 | companion object { 34 | private fun encodeToObj(result: GroupMembershipResult): Map { 35 | return mapOf( 36 | "addedMembers" to result.addedMembers, 37 | "removedMembers" to result.removedMembers, 38 | "failedInstallationIds" to result.failedInstallationIds, 39 | ) 40 | } 41 | 42 | fun encode(result: GroupMembershipResult): String { 43 | val gson = GsonBuilder().create() 44 | val obj = encodeToObj(result) 45 | return gson.toJson(obj) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/MessageWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.GsonBuilder 4 | import org.xmtp.android.library.codecs.description 5 | import org.xmtp.android.library.libxmtp.DecodedMessage 6 | 7 | class MessageWrapper { 8 | 9 | companion object { 10 | fun encode(model: DecodedMessage): String { 11 | val gson = GsonBuilder().create() 12 | val message = encodeMap(model) 13 | return gson.toJson(message) 14 | } 15 | 16 | fun encodeMap(model: DecodedMessage): Map { 17 | // Kotlin/Java Protos don't support null values and will always put the default "" 18 | // Check if there is a fallback, if there is then make it the set fallback, if not null 19 | val fallback = if (model.encodedContent.hasFallback()) model.encodedContent.fallback else null 20 | return mapOf( 21 | "id" to model.id, 22 | "topic" to model.topic, 23 | "contentTypeId" to model.encodedContent.type.description, 24 | "content" to ContentJson(model.encodedContent).toJsonMap(), 25 | "senderInboxId" to model.senderInboxId, 26 | "sentNs" to model.sentAtNs, 27 | "fallback" to fallback, 28 | "deliveryStatus" to model.deliveryStatus.toString(), 29 | "childMessages" to model.childMessages?.map { childMessage -> encodeMap(childMessage) } 30 | ) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/NetworkDebugInfoWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import org.xmtp.android.library.ApiStats 6 | import org.xmtp.android.library.IdentityStats 7 | import org.xmtp.android.library.XMTPDebugInformation 8 | 9 | class NetworkDebugInfoWrapper { 10 | companion object { 11 | val gson: Gson = GsonBuilder().create() 12 | private fun encodeToObj(info: XMTPDebugInformation): Map { 13 | return mapOf( 14 | "apiStatistics" to ApiStatsWrapper.encode(info.apiStatistics), 15 | "identityStatistics" to IdentityStatsWrapper.encode(info.identityStatistics), 16 | "aggregateStatistics" to info.aggregateStatistics, 17 | ) 18 | } 19 | 20 | fun encode(info: XMTPDebugInformation): String { 21 | val obj = encodeToObj(info) 22 | return gson.toJson(obj) 23 | } 24 | } 25 | } 26 | 27 | private class ApiStatsWrapper { 28 | companion object { 29 | val gson: Gson = GsonBuilder().create() 30 | private fun encodeToObj(info: ApiStats): Map { 31 | return mapOf( 32 | "uploadKeyPackage" to info.uploadKeyPackage, 33 | "fetchKeyPackage" to info.fetchKeyPackage, 34 | "sendGroupMessages" to info.sendGroupMessages, 35 | "sendWelcomeMessages" to info.sendWelcomeMessages, 36 | "queryGroupMessages" to info.queryGroupMessages, 37 | "queryWelcomeMessages" to info.queryWelcomeMessages, 38 | "subscribeMessages" to info.subscribeMessages, 39 | "subscribeWelcomes" to info.subscribeWelcomes, 40 | ) 41 | } 42 | 43 | fun encode(info: ApiStats): String { 44 | val obj = encodeToObj(info) 45 | return gson.toJson(obj) 46 | } 47 | } 48 | } 49 | 50 | private class IdentityStatsWrapper { 51 | companion object { 52 | val gson: Gson = GsonBuilder().create() 53 | private fun encodeToObj(info: IdentityStats): Map { 54 | return mapOf( 55 | "publishIdentityUpdate" to info.publishIdentityUpdate, 56 | "getIdentityUpdatesV2" to info.getIdentityUpdatesV2, 57 | "getInboxIds" to info.getInboxIds, 58 | "verifySmartContractWalletSignature" to info.verifySmartContractWalletSignature, 59 | ) 60 | } 61 | 62 | fun encode(info: IdentityStats): String { 63 | val obj = encodeToObj(info) 64 | return gson.toJson(obj) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/PermissionPolicySetWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.GsonBuilder 4 | import com.google.gson.JsonParser 5 | import org.xmtp.android.library.libxmtp.PermissionOption 6 | import org.xmtp.android.library.libxmtp.PermissionPolicySet 7 | 8 | class PermissionPolicySetWrapper { 9 | 10 | companion object { 11 | private fun fromPermissionOption(permissionOption: PermissionOption): String { 12 | return when (permissionOption) { 13 | PermissionOption.Allow -> "allow" 14 | PermissionOption.Deny -> "deny" 15 | PermissionOption.Admin -> "admin" 16 | PermissionOption.SuperAdmin -> "superAdmin" 17 | PermissionOption.Unknown -> "unknown" 18 | } 19 | } 20 | 21 | private fun createPermissionOptionFromString(permissionOptionString: String): PermissionOption { 22 | return when (permissionOptionString) { 23 | "allow" -> PermissionOption.Allow 24 | "deny" -> PermissionOption.Deny 25 | "admin" -> PermissionOption.Admin 26 | "superAdmin" -> PermissionOption.SuperAdmin 27 | else -> PermissionOption.Unknown 28 | } 29 | } 30 | private fun encodeToObj(policySet: PermissionPolicySet): Map { 31 | return mapOf( 32 | "addMemberPolicy" to fromPermissionOption(policySet.addMemberPolicy), 33 | "removeMemberPolicy" to fromPermissionOption(policySet.removeMemberPolicy), 34 | "addAdminPolicy" to fromPermissionOption(policySet.addAdminPolicy), 35 | "removeAdminPolicy" to fromPermissionOption(policySet.removeAdminPolicy), 36 | "updateGroupNamePolicy" to fromPermissionOption(policySet.updateGroupNamePolicy), 37 | "updateGroupDescriptionPolicy" to fromPermissionOption(policySet.updateGroupDescriptionPolicy), 38 | "updateGroupImagePolicy" to fromPermissionOption(policySet.updateGroupImagePolicy), 39 | "updateMessageDisappearingPolicy" to fromPermissionOption(policySet.updateMessageDisappearingPolicy), 40 | ) 41 | } 42 | 43 | fun createPermissionPolicySetFromJson(permissionPolicySetJson: String): PermissionPolicySet { 44 | val jsonObj = JsonParser.parseString(permissionPolicySetJson).asJsonObject 45 | return PermissionPolicySet( 46 | addMemberPolicy = createPermissionOptionFromString(jsonObj.get("addMemberPolicy").asString), 47 | removeMemberPolicy = createPermissionOptionFromString(jsonObj.get("removeMemberPolicy").asString), 48 | addAdminPolicy = createPermissionOptionFromString(jsonObj.get("addAdminPolicy").asString), 49 | removeAdminPolicy = createPermissionOptionFromString(jsonObj.get("removeAdminPolicy").asString), 50 | updateGroupNamePolicy = createPermissionOptionFromString(jsonObj.get("updateGroupNamePolicy").asString), 51 | updateGroupDescriptionPolicy = createPermissionOptionFromString(jsonObj.get("updateGroupDescriptionPolicy").asString), 52 | updateGroupImagePolicy = createPermissionOptionFromString(jsonObj.get("updateGroupImagePolicy").asString), 53 | updateMessageDisappearingPolicy = createPermissionOptionFromString(jsonObj.get("updateMessageDisappearingPolicy").asString), 54 | ) 55 | } 56 | 57 | fun encodeToJsonString(policySet: PermissionPolicySet): String { 58 | val gson = GsonBuilder().create() 59 | val obj = encodeToObj(policySet) 60 | return gson.toJson(obj) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/PublicIdentityWrapper.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.wrappers 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import com.google.gson.JsonParser 6 | import org.xmtp.android.library.libxmtp.IdentityKind 7 | import org.xmtp.android.library.libxmtp.InboxState 8 | import org.xmtp.android.library.libxmtp.PublicIdentity 9 | 10 | class PublicIdentityWrapper( 11 | val identifier: String, 12 | val kind: IdentityKind, 13 | ) { 14 | companion object { 15 | fun publicIdentityFromJson(pubIdParams: String): PublicIdentity { 16 | val jsonOptions = JsonParser.parseString(pubIdParams).asJsonObject 17 | return PublicIdentity( 18 | when (jsonOptions.get("kind").asString) { 19 | "PASSKEY" -> IdentityKind.PASSKEY 20 | else -> IdentityKind.ETHEREUM 21 | }, 22 | jsonOptions.get("identifier").asString, 23 | ) 24 | } 25 | 26 | val gson: Gson = GsonBuilder().create() 27 | private fun encodeToObj(publicIdentity: PublicIdentity): Map { 28 | return mapOf( 29 | "identifier" to publicIdentity.identifier, 30 | "kind" to when (publicIdentity.kind) { 31 | IdentityKind.PASSKEY -> "PASSKEY" 32 | else -> "ETHEREUM" 33 | }, 34 | ) 35 | } 36 | 37 | fun encode(publicIdentity: PublicIdentity): String { 38 | val obj = encodeToObj(publicIdentity) 39 | return gson.toJson(obj) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | expo-env.d.ts 11 | 12 | # Native 13 | *.orig.* 14 | *.jks 15 | *.p8 16 | *.p12 17 | *.key 18 | *.mobileprovision 19 | 20 | # Metro 21 | .metro-health-check* 22 | 23 | # debug 24 | npm-debug.* 25 | yarn-debug.* 26 | yarn-error.* 27 | 28 | # macOS 29 | .DS_Store 30 | *.pem 31 | 32 | # local env files 33 | .env*.local 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | -------------------------------------------------------------------------------- /example/EXAMPLE.env: -------------------------------------------------------------------------------- 1 | TEST_PRIVATE_KEY=INSERT_TEST_PRIVATE_KEY_HERE 2 | THIRD_WEB_CLIENT_ID=INSERT_CLIENT_ID_HERE 3 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # XMTP React Native example app 2 | 3 | This basic messaging app has an intentionally unopinionated UI to help make it easier for you to build with. 4 | 5 | ## Run the example app 6 | Follow the [React Native guide](https://reactnative.dev/docs/environment-setup) to set up a CLI environment. 7 | 8 | ### To use the example app, run: 9 | 10 | ```bash 11 | yarn 12 | cd example 13 | yarn 14 | npx pod-install 15 | yarn run [ios or android] 16 | ``` 17 | 18 | ### If testing the remote attachment content type, run (from example directory): 19 | 20 | ```bash 21 | yarn run upload:up 22 | ``` 23 | 24 | If running on andoid also run: 25 | 26 | ```bash 27 | adb reverse tcp:8443 tcp:8443 28 | ``` 29 | 30 | To clean up after testing you can run: 31 | 32 | ```bash 33 | yarn run upload:down 34 | ``` 35 | 36 | ### Configure ThirdWeb Client API 37 | 38 | > Note - The connect wallet button will still work without adding a client id, you just may see some extra network errors when viewing account info in the Thirdweb button after connecting. 39 | 40 | First create a free account and download your client id from https://thirdweb.com/dashboard/settings/api-keys. Next create your .env file in the example directory 41 | 42 | ``` 43 | cd example 44 | cp EXAMPLE.env .env 45 | ``` 46 | Finally, insert your Thirdweb client id in specified location of `example/.env` file: 47 | ``` 48 | THIRD_WEB_CLIENT_ID=INSERT_CLIENT_ID_HERE 49 | ``` 50 | 51 | If your app doesn't appear to be picking up changes in the .env file, you can try editing the TypeScript file you're reading the env variable from (`App.tsx`) or building the app with the `--no-build-cache` flag added. 52 | 53 | 54 | ## Run example app unit tests on local emulators 55 | Running tests locally is useful when updating GitHub actions, or locally testing between changes. 56 | 57 | 1. [Install Docker](https://docs.docker.com/get-docker/) 58 | 59 | 2. Start a local XMTP server 60 | ```bash 61 | git clone https://github.com/xmtp/libxmtp.git 62 | cd libxmtp 63 | dev/up 64 | ``` 65 | 3. Verify the XMTP server is running 66 | ```bash 67 | docker-compose ls 68 | 69 | NAME STATUS CONFIG FILES 70 | libxmtp running(9) /libxmtp/dev/docker/docker-compose.yml 71 | ``` 72 | 4. You can now run unit tests on your local emulators 73 | 5. You can stop the XMTP server with the following command: 74 | ```bash 75 | dev/down 76 | ``` 77 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Android/IntelliJ 6 | # 7 | build/ 8 | .idea 9 | .gradle 10 | local.properties 11 | *.iml 12 | *.hprof 13 | .cxx/ 14 | 15 | # Bundle artifacts 16 | *.jsbundle 17 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/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 | # react-native-reanimated 11 | -keep class com.swmansion.reanimated.** { *; } 12 | -keep class com.facebook.react.turbomodule.** { *; } 13 | 14 | # Add any project specific keep options here: 15 | -------------------------------------------------------------------------------- /example/android/app/src/androidTest/java/expo/modules/xmtpreactnativesdk/example/EspressoViewFinder.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.example 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.test.espresso.Espresso.onView 6 | import androidx.test.espresso.PerformException 7 | import androidx.test.espresso.UiController 8 | import androidx.test.espresso.ViewAction 9 | import androidx.test.espresso.matcher.ViewMatchers 10 | import androidx.test.espresso.matcher.ViewMatchers.isRoot 11 | import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility 12 | import androidx.test.espresso.util.HumanReadables 13 | import androidx.test.espresso.util.TreeIterables 14 | import org.hamcrest.Matcher 15 | import java.util.concurrent.TimeoutException 16 | 17 | object EspressoViewFinder { 18 | private const val CHECK_INTERVAL = 150L 19 | private const val TIMEOUT_MS = 30 * 1000L 20 | 21 | /** 22 | * Waits for the view referenced in [viewMatcher] to become visible, with a timeout of [timeOut]. If it 23 | * becomes visible, [onDisplayedHandler] will be invoked. 24 | * 25 | * This method is needed because Espresso idling resources are not sufficient in combination with RN; 26 | * it does not wait on the javascript thread and the bridge. 27 | * 28 | * Throws a TimeoutException wrapped in a PerformException when the view is not displayed within [timeOut]. 29 | */ 30 | fun waitForDisplayed( 31 | viewMatcher: Matcher, 32 | timeOut: Long = TIMEOUT_MS, 33 | onDisplayedHandler: ((Matcher) -> Unit)? = null, 34 | ) { 35 | 36 | // wait for view 37 | onView(isRoot()).perform(createWaitForDisplayedViewAction(viewMatcher, timeOut)) 38 | 39 | // call handler 40 | onDisplayedHandler?.invoke(viewMatcher) 41 | } 42 | 43 | private fun createWaitForDisplayedViewAction( 44 | viewMatcher: Matcher, 45 | timeOut: Long = TIMEOUT_MS, 46 | ) = object : ViewAction { 47 | 48 | override fun getConstraints(): Matcher { 49 | return isRoot() 50 | } 51 | 52 | override fun getDescription(): String { 53 | return "waitForDisplayed on viewMatcher <$viewMatcher> without timeOut $timeOut ms." 54 | } 55 | 56 | override fun perform(uiController: UiController, view: View) { 57 | 58 | // wait for idle, so that we don't timeout while waiting on Espresso idling resources: 59 | uiController.loopMainThreadUntilIdle() 60 | 61 | val found = waitForView(uiController, view) 62 | 63 | if (!found) { 64 | throw createPerformException(view) 65 | } 66 | } 67 | 68 | private fun waitForView(uiController: UiController, view: View): Boolean { 69 | 70 | val timeOutTimeStamp = System.currentTimeMillis() + timeOut 71 | do { 72 | // find view with required matcher: 73 | for (child in TreeIterables.breadthFirstViewTraversal(view)) { 74 | if (viewMatcher.matches(child) && isDisplayed(child)) { 75 | return true 76 | } 77 | } 78 | uiController.loopMainThreadForAtLeast(CHECK_INTERVAL) 79 | } while (System.currentTimeMillis() < timeOutTimeStamp) 80 | 81 | return false 82 | } 83 | 84 | private fun createPerformException(view: View) = PerformException.Builder() 85 | .withActionDescription(this.description) 86 | .withViewDescription(HumanReadables.describe(view)) 87 | .withCause(TimeoutException()) 88 | .build() 89 | } 90 | 91 | private fun isDisplayed(view: View) = 92 | view.getGlobalVisibleRect(Rect()) && withEffectiveVisibility( 93 | ViewMatchers.Visibility.VISIBLE 94 | ).matches(view) 95 | } -------------------------------------------------------------------------------- /example/android/app/src/androidTest/java/expo/modules/xmtpreactnativesdk/example/MainActivityTest.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.example 2 | 3 | import android.util.Log 4 | import androidx.test.core.app.takeScreenshot 5 | import androidx.test.core.graphics.writeToTestStorage 6 | import androidx.test.espresso.Espresso.onView 7 | import androidx.test.espresso.action.ViewActions.click 8 | import androidx.test.espresso.matcher.ViewMatchers.withContentDescription 9 | import androidx.test.rule.ActivityTestRule 10 | import androidx.test.runner.AndroidJUnit4 11 | import expo.modules.xmtpreactnativesdk.example.EspressoViewFinder.waitForDisplayed 12 | import junit.framework.TestCase 13 | import org.junit.Rule 14 | import org.junit.Test 15 | import org.junit.rules.TestName 16 | import org.junit.runner.RunWith 17 | 18 | 19 | @RunWith(AndroidJUnit4::class) 20 | class MainActivityTest { 21 | 22 | @get:Rule 23 | val activityRule = ActivityTestRule(MainActivity::class.java) 24 | 25 | @get:Rule 26 | val nameRule = TestName() 27 | 28 | @Test 29 | fun testRunTests() { 30 | waitForDisplayed(withContentDescription("Unit-tests")) { button -> 31 | // Go to unit tests page 32 | onView(button).perform(click()) 33 | waitForDisplayed(withContentDescription("Test View")) { view -> 34 | waitForDisplayed(withContentDescription("tests-complete")) { complete -> 35 | try { 36 | waitForDisplayed(withContentDescription("FAIL")) { failure -> 37 | takeScreenshot().writeToTestStorage("${javaClass.simpleName}_${nameRule.methodName}") 38 | Log.d("MainActivityTest", "Test Fail") 39 | TestCase.fail("Test failed") 40 | } 41 | } catch (e: Exception) { 42 | Log.d("MainActivityTest", "Test Succeeded $e") 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/expo/modules/xmtpreactnativesdk/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.example; 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | 6 | import com.facebook.react.ReactActivity 7 | import com.facebook.react.ReactActivityDelegate 8 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 9 | import com.facebook.react.defaults.DefaultReactActivityDelegate 10 | 11 | import expo.modules.ReactActivityDelegateWrapper 12 | 13 | class MainActivity : ReactActivity() { 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | // Set the theme to AppTheme BEFORE onCreate to support 16 | // coloring the background, status bar, and navigation bar. 17 | // This is required for expo-splash-screen. 18 | setTheme(R.style.AppTheme); 19 | super.onCreate(null) 20 | } 21 | 22 | /** 23 | * Returns the name of the main component registered from JavaScript. This is used to schedule 24 | * rendering of the component. 25 | */ 26 | override fun getMainComponentName(): String = "main" 27 | 28 | /** 29 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 30 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 31 | */ 32 | override fun createReactActivityDelegate(): ReactActivityDelegate { 33 | return ReactActivityDelegateWrapper( 34 | this, 35 | BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, 36 | object : DefaultReactActivityDelegate( 37 | this, 38 | mainComponentName, 39 | fabricEnabled 40 | ){}) 41 | } 42 | 43 | /** 44 | * Align the back button behavior with Android S 45 | * where moving root activities to background instead of finishing activities. 46 | * @see onBackPressed 47 | */ 48 | override fun invokeDefaultOnBackPressed() { 49 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { 50 | if (!moveTaskToBack(false)) { 51 | // For non-root activities, use the default implementation to finish them. 52 | super.invokeDefaultOnBackPressed() 53 | } 54 | return 55 | } 56 | 57 | // Use the default back button implementation on Android S 58 | // because it's doing more than [Activity.moveTaskToBack] in fact. 59 | super.invokeDefaultOnBackPressed() 60 | } 61 | } -------------------------------------------------------------------------------- /example/android/app/src/main/java/expo/modules/xmtpreactnativesdk/example/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.xmtpreactnativesdk.example; 2 | 3 | import android.app.Application 4 | import android.content.res.Configuration 5 | 6 | import com.facebook.react.PackageList 7 | import com.facebook.react.ReactApplication 8 | import com.facebook.react.ReactNativeHost 9 | import com.facebook.react.ReactPackage 10 | import com.facebook.react.ReactHost 11 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 12 | import com.facebook.react.defaults.DefaultReactNativeHost 13 | import com.facebook.react.soloader.OpenSourceMergedSoMapping 14 | import com.facebook.soloader.SoLoader 15 | 16 | import expo.modules.ApplicationLifecycleDispatcher 17 | import expo.modules.ReactNativeHostWrapper 18 | 19 | class MainApplication : Application(), ReactApplication { 20 | 21 | override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( 22 | this, 23 | object : DefaultReactNativeHost(this) { 24 | override fun getPackages(): List { 25 | val packages = PackageList(this).packages 26 | // Packages that cannot be autolinked yet can be added manually here, for example: 27 | // packages.add(new MyReactNativePackage()); 28 | return packages 29 | } 30 | 31 | override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" 32 | 33 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 34 | 35 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 36 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 37 | } 38 | ) 39 | 40 | override val reactHost: ReactHost 41 | get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) 42 | 43 | override fun onCreate() { 44 | super.onCreate() 45 | SoLoader.init(this, OpenSourceMergedSoMapping) 46 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 47 | // If you opted-in for the New Architecture, we load the native entry point for this app. 48 | load() 49 | } 50 | ApplicationLifecycleDispatcher.onApplicationCreate(this) 51 | } 52 | 53 | override fun onConfigurationChanged(newConfig: Configuration) { 54 | super.onConfigurationChanged(newConfig) 55 | ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) 56 | } 57 | } -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-hdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/drawable-hdpi/splashscreen_image.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-mdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/drawable-mdpi/splashscreen_image.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/splashscreen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff 3 | #ffffff 4 | #023c69 5 | #ffffff 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | xmtp-react-native-sdk-example 3 | contain 4 | false 5 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | 17 | -------------------------------------------------------------------------------- /example/android/app/src/release/java/expo/modules/xmtpreactnativesdk/example/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and 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 expo.modules.xmtpreactnativesdk.example; 8 | 9 | import android.content.Context; 10 | import com.facebook.react.ReactInstanceManager; 11 | 12 | /** 13 | * Class responsible of loading Flipper inside your React Native application. This is the release 14 | * flavor of it so it's empty as we don't want to load Flipper. 15 | */ 16 | public class ReactNativeFlipper { 17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 18 | // Do nothing as we don't want to initialize Flipper on Release. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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 | buildToolsVersion = findProperty('android.buildToolsVersion') ?: '35.0.0' 6 | minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '24') 7 | compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '35') 8 | targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34') 9 | kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.25' 10 | 11 | ndkVersion = "26.1.10909125" 12 | } 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | dependencies { 18 | classpath('com.android.tools.build:gradle') 19 | classpath('com.facebook.react:react-native-gradle-plugin') 20 | classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') 21 | } 22 | } 23 | 24 | apply plugin: "com.facebook.react.rootproject" 25 | 26 | allprojects { 27 | repositories { 28 | maven { 29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 30 | url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android')) 31 | } 32 | maven { 33 | // Android JSC is installed from npm 34 | url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist')) 35 | } 36 | 37 | google() 38 | mavenCentral() 39 | maven { url 'https://www.jitpack.io' } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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 | 25 | # Enable AAPT2 PNG crunching 26 | android.enablePngCrunchInReleaseBuilds=true 27 | 28 | # Use this property to specify which architecture you want to build. 29 | # You can also override it from the CLI using 30 | # ./gradlew -PreactNativeArchitectures=x86_64 31 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 32 | 33 | # Use this property to enable support to the new architecture. 34 | # This will allow you to use TurboModules and the Fabric render in 35 | # your application. You should enable this flag either if you want 36 | # to write custom TurboModules/Fabric components OR use libraries that 37 | # are providing them. 38 | newArchEnabled=false 39 | 40 | # Use this property to enable or disable the Hermes JS engine. 41 | # If set to false, you will be using JSC instead. 42 | hermesEnabled=true 43 | 44 | # Enable GIF support in React Native images (~200 B increase) 45 | expo.gif.enabled=true 46 | # Enable webp support in React Native images (~85 KB increase) 47 | expo.webp.enabled=true 48 | # Enable animated webp support (~3.4 MB increase) 49 | # Disabled by default because iOS doesn't support animated webp 50 | expo.webp.animated=false 51 | 52 | # Enable network inspector 53 | EX_DEV_CLIENT_NETWORK_INSPECTOR=true 54 | 55 | # Use legacy packaging to compress native libraries in the resulting APK. 56 | expo.useLegacyPackaging=false 57 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /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 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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().toString()) 3 | } 4 | plugins { id("com.facebook.react.settings") } 5 | 6 | extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> 7 | if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') { 8 | ex.autolinkLibrariesFromCommand() 9 | } else { 10 | def command = [ 11 | 'node', 12 | '--no-warnings', 13 | '--eval', 14 | 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))', 15 | 'react-native-config', 16 | '--json', 17 | '--platform', 18 | 'android' 19 | ].toList() 20 | ex.autolinkLibrariesFromCommand(command) 21 | } 22 | } 23 | 24 | rootProject.name = 'xmtp-react-native-sdk-example' 25 | 26 | dependencyResolutionManagement { 27 | versionCatalogs { 28 | reactAndroidLibs { 29 | from(files(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../gradle/libs.versions.toml"))) 30 | } 31 | } 32 | } 33 | 34 | apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); 35 | useExpoModules() 36 | 37 | include ':app' 38 | includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile()) -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "xmtp-react-native-sdk-example", 4 | "slug": "xmtp-react-native-sdk-example", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": [ 15 | "**/*" 16 | ], 17 | "plugins": [ 18 | [ 19 | "expo-image-picker", 20 | { 21 | "photosPermission": "The app accesses your photos to let you attach them as messages.", 22 | "cameraPermission": "The app accesses your camera to let you attach photos as messages." 23 | } 24 | ] 25 | ], 26 | "ios": { 27 | "supportsTablet": true, 28 | "bundleIdentifier": "expo.modules.xmtpreactnativesdk.example" 29 | }, 30 | "android": { 31 | "adaptiveIcon": { 32 | "foregroundImage": "./assets/adaptive-icon.png", 33 | "backgroundColor": "#ffffff" 34 | }, 35 | "package": "expo.modules.xmtpreactnativesdk.example" 36 | }, 37 | "web": { 38 | "favicon": "./assets/favicon.png" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/assets/adaptive-icon.png -------------------------------------------------------------------------------- /example/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/assets/favicon.png -------------------------------------------------------------------------------- /example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/assets/icon.png -------------------------------------------------------------------------------- /example/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/assets/splash.png -------------------------------------------------------------------------------- /example/dev/local/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | 4 | upload-service: 5 | build: ./upload-service 6 | ports: 7 | - 4567:4567 8 | 9 | caddy: 10 | image: caddy:latest 11 | ports: 12 | - "8443:8443" 13 | volumes: 14 | - ./upload-service/Caddyfile:/etc/caddy/Caddyfile 15 | - ./upload-service/data/data:/data 16 | - ./upload-service/data/config:/config -------------------------------------------------------------------------------- /example/dev/local/test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:19-alpine 2 | 3 | WORKDIR /code 4 | ADD script.js script.js 5 | RUN apk update && apk add git 6 | RUN npm install @xmtp/xmtp-js ethers 7 | CMD ["node", "script.js"] 8 | -------------------------------------------------------------------------------- /example/dev/local/test/script.js: -------------------------------------------------------------------------------- 1 | const Client = require('@xmtp/xmtp-js').Client 2 | const Wallet = require('ethers').Wallet 3 | 4 | console.log('NODE VERSION', process.version) 5 | 6 | // 0xf4BF19Ed562651837bc11ff975472ABd239D35B5 7 | const keyBytes = [ 8 | 80, 7, 53, 52, 122, 163, 75, 130, 199, 86, 216, 14, 29, 2, 255, 71, 121, 51, 9 | 165, 3, 208, 178, 193, 207, 223, 217, 75, 247, 84, 78, 204, 3, 10 | ] 11 | 12 | async function checkAll() { 13 | const wallet = new Wallet(keyBytes) 14 | const client = await Client.create(wallet, { 15 | apiUrl: 'http://wakunode:5555', 16 | }) 17 | 18 | console.log('Listening…') 19 | 20 | try { 21 | for await (const message of await client.conversations.streamAllMessages()) { 22 | if (message.senderInboxId === wallet.address) { 23 | continue 24 | } 25 | 26 | await message.conversation.send({ 27 | text: 'HI ' + message.senderInboxId, 28 | }) 29 | console.log(`Replied to ${message.senderInboxId}`) 30 | } 31 | } catch (e) { 32 | console.info(`Error:`, e) 33 | await checkAll() 34 | } 35 | } 36 | 37 | checkAll().then(() => console.log('Done')) 38 | -------------------------------------------------------------------------------- /example/dev/local/upload-service/.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | -------------------------------------------------------------------------------- /example/dev/local/upload-service/Caddyfile: -------------------------------------------------------------------------------- 1 | localhost:8443 { 2 | tls internal 3 | reverse_proxy upload-service:4567 4 | } 5 | -------------------------------------------------------------------------------- /example/dev/local/upload-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.2 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | build-essential \ 5 | && rm -rf /var/lib/apt/lists/* 6 | 7 | RUN gem install sinatra webrick puma rackup --no-document 8 | 9 | WORKDIR /usr/src/app 10 | ADD app.rb /usr/src/app/ 11 | 12 | ENTRYPOINT ["ruby", "app.rb", "-o", "0.0.0.0"] -------------------------------------------------------------------------------- /example/dev/local/upload-service/app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'puma' 3 | require 'sinatra' 4 | 5 | UPLOADS = {} 6 | 7 | post '/:path' do 8 | logger.info "POST #{params[:path]}" 9 | UPLOADS[params[:path]] = request.body.read 10 | "OK" 11 | end 12 | 13 | get '/:path' do 14 | logger.info "GET #{params[:path]}" 15 | if data = UPLOADS[params[:path]] 16 | content_type 'application/octet-stream' 17 | data 18 | else 19 | raise Sinatra::NotFound 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { registerRootComponent } from 'expo' 2 | 3 | import App from './App' 4 | 5 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 6 | // It also ensures that whether you load the app in Expo Go or in a native build, 7 | // the environment is set up appropriately 8 | registerRootComponent(App) 9 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | .xcode.env.local 25 | 26 | # Bundle artifacts 27 | *.jsbundle 28 | 29 | # CocoaPods 30 | /Pods/ 31 | -------------------------------------------------------------------------------- /example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") 2 | require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") 3 | 4 | require 'json' 5 | podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} 6 | 7 | ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0' 8 | ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR'] 9 | 10 | platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1' 11 | install! 'cocoapods', 12 | :deterministic_uuids => false 13 | 14 | prepare_react_native_project! 15 | 16 | target 'xmtpreactnativesdkexample' do 17 | use_expo_modules! 18 | 19 | if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1' 20 | config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"]; 21 | else 22 | config_command = [ 23 | 'node', 24 | '--no-warnings', 25 | '--eval', 26 | 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))', 27 | 'react-native-config', 28 | '--json', 29 | '--platform', 30 | 'ios' 31 | ] 32 | end 33 | 34 | config = use_native_modules!(config_command) 35 | 36 | use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] 37 | use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] 38 | 39 | 40 | # See https://github.com/margelo/react-native-quick-crypto/issues/189#issuecomment-1711561970 41 | pod "OpenSSL-Universal", "1.1.2200" 42 | 43 | # Override XMTP pod to use specific commit 44 | # pod 'XMTP', :git => 'https://github.com/xmtp/xmtp-ios.git', :commit => 'commit here' 45 | 46 | 47 | use_react_native!( 48 | :path => config[:reactNativePath], 49 | :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', 50 | # An absolute path to your application root. 51 | :app_path => "#{Pod::Config.instance.installation_root}/..", 52 | :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', 53 | ) 54 | 55 | post_install do |installer| 56 | react_native_post_install( 57 | installer, 58 | config[:reactNativePath], 59 | :mac_catalyst_enabled => false, 60 | :ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true', 61 | ) 62 | 63 | # This is necessary for Xcode 14, because it signs resource bundles by default 64 | # when building for devices. 65 | installer.target_installation_results.pod_target_installation_results 66 | .each do |pod_name, target_installation_result| 67 | target_installation_result.resource_bundle_targets.each do |resource_bundle_target| 68 | resource_bundle_target.build_configurations.each do |config| 69 | config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' 70 | end 71 | end 72 | end 73 | 74 | installer.generated_projects.each do |project| 75 | project.targets.each do |target| 76 | target.build_configurations.each do |config| 77 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' 78 | end 79 | end 80 | project.build_configurations.each do |config| 81 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' 82 | end 83 | end 84 | end 85 | 86 | end 87 | -------------------------------------------------------------------------------- /example/ios/Podfile.properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo.jsEngine": "hermes", 3 | "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true" 4 | } -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample.xcodeproj/xcshareddata/xcschemes/xmtpreactnativesdkexample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 59 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "keychainaccess", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/kishikawakatsumi/KeychainAccess", 7 | "state" : { 8 | "branch" : "master", 9 | "revision" : "ecb18d8ce4d88277cc4fb103973352d91e18c535" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface AppDelegate : EXAppDelegateWrapper 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | #import 5 | 6 | @implementation AppDelegate 7 | 8 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 9 | { 10 | self.moduleName = @"main"; 11 | 12 | // You can add your custom initial props in the dictionary below. 13 | // They will be passed down to the ViewController used by React Native. 14 | self.initialProps = @{}; 15 | 16 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 17 | } 18 | 19 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 20 | { 21 | return [self bundleURL]; 22 | } 23 | 24 | - (NSURL *)bundleURL 25 | { 26 | #if DEBUG 27 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"]; 28 | #else 29 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 30 | #endif 31 | } 32 | 33 | // Linking API 34 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { 35 | return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options]; 36 | } 37 | 38 | // Universal Links 39 | - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { 40 | BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 41 | return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result; 42 | } 43 | 44 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries 45 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken 46 | { 47 | return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; 48 | } 49 | 50 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries 51 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error 52 | { 53 | return [super application:application didFailToRegisterForRemoteNotificationsWithError:error]; 54 | } 55 | 56 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries 57 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 58 | { 59 | return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "version" : 1, 11 | "author" : "expo" 12 | } 13 | } -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/SplashScreen.imageset/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/SplashScreen.imageset/image.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Images.xcassets/SplashScreenBackground.imageset/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmtp/xmtp-react-native/5f3d437ced1f03c88c1e3585223be80e5fb030f3/example/ios/xmtpreactnativesdkexample/Images.xcassets/SplashScreenBackground.imageset/image.png -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | xmtp-react-native-sdk-example 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 21 | CFBundleShortVersionString 22 | 1.0.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleURLTypes 26 | 27 | 28 | CFBundleURLSchemes 29 | 30 | expo.modules.xmtpreactnativesdk.example 31 | 32 | 33 | 34 | CFBundleVersion 35 | 1 36 | LSMinimumSystemVersion 37 | 12.0 38 | LSRequiresIPhoneOS 39 | 40 | NSAppTransportSecurity 41 | 42 | NSAllowsArbitraryLoads 43 | 44 | NSAllowsLocalNetworking 45 | 46 | 47 | NSCameraUsageDescription 48 | The app accesses your camera to let you attach photos as messages. 49 | NSMicrophoneUsageDescription 50 | Allow $(PRODUCT_NAME) to access your microphone 51 | NSPhotoLibraryUsageDescription 52 | The app accesses your photos to let you attach them as messages. 53 | UILaunchStoryboardName 54 | SplashScreen 55 | UIRequiredDeviceCapabilities 56 | 57 | armv64 58 | 59 | UIRequiresFullScreen 60 | 61 | UIStatusBarStyle 62 | UIStatusBarStyleDefault 63 | UISupportedInterfaceOrientations 64 | 65 | UIInterfaceOrientationPortrait 66 | UIInterfaceOrientationPortraitUpsideDown 67 | 68 | UISupportedInterfaceOrientations~ipad 69 | 70 | UIInterfaceOrientationPortrait 71 | UIInterfaceOrientationPortraitUpsideDown 72 | UIInterfaceOrientationLandscapeLeft 73 | UIInterfaceOrientationLandscapeRight 74 | 75 | UIUserInterfaceStyle 76 | Light 77 | UIViewControllerBasedStatusBarAppearance 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | CA92.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryFileTimestamp 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | 0A2A.1 21 | 3B52.1 22 | C617.1 23 | 24 | 25 | 26 | NSPrivacyAccessedAPIType 27 | NSPrivacyAccessedAPICategoryDiskSpace 28 | NSPrivacyAccessedAPITypeReasons 29 | 30 | E174.1 31 | 85F4.1 32 | 33 | 34 | 35 | NSPrivacyAccessedAPIType 36 | NSPrivacyAccessedAPICategorySystemBootTime 37 | NSPrivacyAccessedAPITypeReasons 38 | 39 | 35F9.1 40 | 41 | 42 | 43 | NSPrivacyCollectedDataTypes 44 | 45 | NSPrivacyTracking 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/SplashScreen.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 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/Supporting/Expo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EXUpdatesCheckOnLaunch 6 | ALWAYS 7 | EXUpdatesEnabled 8 | 9 | EXUpdatesLaunchWaitMs 10 | 0 11 | 12 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/noop-file.swift: -------------------------------------------------------------------------------- 1 | // 2 | // @generated 3 | // A blank Swift file must be created for native modules with Swift files to work correctly. 4 | // 5 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexample/xmtpreactnativesdkexample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | -------------------------------------------------------------------------------- /example/ios/xmtpreactnativesdkexampleUITests/xmtpreactnativesdkexampleUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // xmtpreactnativesdkexampleUITests.swift 3 | // xmtpreactnativesdkexampleUITests 4 | // 5 | // Created by Pat Nakajima on 5/1/23. 6 | // 7 | 8 | import XCTest 9 | 10 | final class xmtpreactnativesdkexampleUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | func testRunTests() throws { 22 | // UI tests must launch the application that they test. 23 | let app = XCUIApplication() 24 | app.launch() 25 | 26 | // Go to unit tests page 27 | let button = app.buttons["Unit tests"] 28 | XCTAssert(button.waitForExistence(timeout: 3)) 29 | button.tap() 30 | 31 | // Make sure we're there 32 | let view = app.staticTexts["Test View"] 33 | XCTAssert(view.waitForExistence(timeout: 3)) 34 | 35 | // Wait for tests to complete 36 | let complete = app.staticTexts["tests-complete"] 37 | XCTAssert(complete.waitForExistence(timeout: 5)) 38 | 39 | // See if we have any failures 40 | if app.staticTexts["FAIL"].waitForExistence(timeout: 3) { 41 | // Take a screenshot so we can see what failed in the UI 42 | let screenshot = app.windows.firstMatch.screenshot() 43 | let attachment = XCTAttachment(screenshot: screenshot) 44 | attachment.lifetime = .keepAlways 45 | add(attachment) 46 | 47 | XCTFail("Tests failed.") 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | // Learn more https://docs.expo.io/guides/customizing-metro 2 | const { getDefaultConfig } = require('expo/metro-config') 3 | const path = require('path') 4 | 5 | /** @type {import('expo/metro-config').MetroConfig} */ 6 | const config = getDefaultConfig(__dirname); 7 | 8 | const extraNodeModules = require('node-libs-browser') 9 | config.resolver.extraNodeModules = extraNodeModules 10 | // npm v7+ will install ../node_modules/react-native because of peerDependencies. 11 | // To prevent the incompatible react-native bewtween ./node_modules/react-native and ../node_modules/react-native, 12 | // excludes the one from the parent folder when bundling. 13 | config.resolver.blockList = [ 14 | ...Array.from(config.resolver.blockList ?? []), 15 | new RegExp(path.resolve('..', 'node_modules', 'react-native')), 16 | ] 17 | 18 | config.resolver.nodeModulesPaths = [ 19 | path.resolve(__dirname, './node_modules'), 20 | path.resolve(__dirname, '../node_modules'), 21 | ] 22 | 23 | config.watchFolders = [path.resolve(__dirname, '..')] 24 | 25 | config.transformer.getTransformOptions = async () => ({ 26 | transform: { 27 | experimentalImportSupport: false, 28 | inlineRequires: true, 29 | }, 30 | }) 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmtp-react-native-sdk-example", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "expo start --dev-client", 6 | "android": "expo run:android", 7 | "ios": "expo run:ios", 8 | "ios:clean": "expo run:ios --no-build-cache", 9 | "web": "expo start --web", 10 | "upload:up": "docker-compose -p xmtp -f dev/local/docker-compose.yml up -d", 11 | "upload:down": "docker-compose -p xmtp -f dev/local/docker-compose.yml down", 12 | "postinstall": "patch-package" 13 | }, 14 | "dependencies": { 15 | "@ethersproject/shims": "^5.8.0", 16 | "@react-native-async-storage/async-storage": "1.17.11", 17 | "@react-native-community/netinfo": "9.3.7", 18 | "@react-navigation/native": "^6.1.6", 19 | "@react-navigation/native-stack": "^6.9.12", 20 | "@xmtp/proto": "3.54.0", 21 | "buffer": "^6.0.3", 22 | "ethers": "^5.8.0", 23 | "expo": "~52.0.42", 24 | "expo-clipboard": "~7.0.1", 25 | "expo-crypto": "^14.0.2", 26 | "expo-document-picker": "~13.0.3", 27 | "expo-file-system": "~18.0.12", 28 | "expo-image-picker": "~16.0.6", 29 | "expo-splash-screen": "~0.29.22", 30 | "expo-status-bar": "~2.0.1", 31 | "moment": "^2.29.4", 32 | "node-libs-browser": "^2.2.1", 33 | "react": "18.3.1", 34 | "react-native": "0.76.9", 35 | "react-native-blob-util": "^0.19.0", 36 | "react-native-config": "^1.5.1", 37 | "react-native-crypto": "^2.2.0", 38 | "react-native-encrypted-storage": "^4.0.3", 39 | "react-native-fs": "^2.20.0", 40 | "react-native-get-random-values": "^1.11.0", 41 | "react-native-mmkv": "^2.8.0", 42 | "react-native-modal-selector": "^2.1.2", 43 | "react-native-quick-base64": "^2.0.8", 44 | "react-native-quick-crypto": "^0.7.12", 45 | "react-native-randombytes": "^3.6.1", 46 | "react-native-safe-area-context": "5.3.0", 47 | "react-native-screens": "~4.10.0", 48 | "react-native-sqlite-storage": "^6.0.1", 49 | "react-native-svg": "15.11.2", 50 | "react-native-url-polyfill": "^2.0.0", 51 | "react-native-webview": "11.26.0", 52 | "react-query": "^3.39.3", 53 | "stream-browserify": "^3.0.0", 54 | "text-encoding": "^0.7.0", 55 | "viem": "^2.7.22" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.20.0", 59 | "@types/node": "^20.17.6", 60 | "@types/text-encoding": "^0.0.39", 61 | "@typescript-eslint/eslint-plugin": "^6.13.1", 62 | "@typescript-eslint/parser": "^6.13.1", 63 | "eslint": "^8.54.0", 64 | "eslint-plugin-prettier": "^5.0.1", 65 | "eslint-plugin-react": "^7.33.2", 66 | "patch-package": "^8.0.0", 67 | "postinstall-postinstall": "^2.1.0", 68 | "prettier": "^3.1.0", 69 | "typescript": "^4.9.4" 70 | }, 71 | "private": true, 72 | "expo": { 73 | "autolinking": { 74 | "nativeModulesDir": ".." 75 | } 76 | }, 77 | "engines": { 78 | "node": ">=20" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/src/ConversationCreateScreen.tsx: -------------------------------------------------------------------------------- 1 | import { NativeStackScreenProps } from '@react-navigation/native-stack' 2 | import React, { useState } from 'react' 3 | import { Button, ScrollView, Switch, Text, TextInput, View } from 'react-native' 4 | import { PublicIdentity, useXmtp } from 'xmtp-react-native-sdk' 5 | 6 | import { NavigationParamList } from './Navigation' 7 | 8 | export default function ConversationCreateScreen({ 9 | route, 10 | navigation, 11 | }: NativeStackScreenProps) { 12 | const [toAddress, setToAddress] = useState('') 13 | const [alert, setAlert] = useState('') 14 | const [isCreating, setCreating] = useState(false) 15 | const { client } = useXmtp() 16 | const [groupsEnabled, setGroupsEnabled] = useState(false) 17 | 18 | const startNewConversation = async (toAddress: string) => { 19 | if (!client) { 20 | setAlert('Client not initialized') 21 | return 22 | } 23 | if (groupsEnabled) { 24 | const toAddresses = toAddress.split(',') 25 | const toIdentities = toAddresses.map( 26 | (address: string) => new PublicIdentity(address, 'ETHEREUM') 27 | ) 28 | const canMessage = await client.canMessage(toIdentities) 29 | if (!canMessage) { 30 | setAlert(`${toAddress} cannot be added to a group conversation yet`) 31 | return 32 | } 33 | const group = 34 | await client.conversations.newGroupWithIdentities(toIdentities) 35 | navigation.navigate('group', { id: group.id }) 36 | } else { 37 | const canMessage = await client.canMessage([ 38 | new PublicIdentity(toAddress, 'ETHEREUM'), 39 | ]) 40 | if (!canMessage) { 41 | setAlert(`${toAddress} is not on the XMTP network yet`) 42 | return 43 | } 44 | const convo = await client.conversations.findOrCreateDmWithIdentity( 45 | new PublicIdentity(toAddress, 'ETHEREUM') 46 | ) 47 | navigation.navigate('conversation', { topic: convo.topic }) 48 | } 49 | } 50 | 51 | return ( 52 | <> 53 | 54 | { 58 | setToAddress(toAddress) 59 | setAlert('') // clear any previous alert 60 | }} 61 | editable={!isCreating} 62 | style={{ 63 | height: 40, 64 | margin: 12, 65 | marginRight: 0, 66 | borderWidth: 1, 67 | padding: 10, 68 | backgroundColor: 'white', 69 | flexGrow: 1, 70 | opacity: isCreating ? 0.5 : 1, 71 | }} 72 | /> 73 | 74 | 77 | setGroupsEnabled((previousState) => !previousState) 78 | } 79 | /> 80 | Create Group: {groupsEnabled ? 'ON' : 'OFF'} 81 | 82 |