@tryterra/react-native 0.0.0-stage → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +111 -2
- package/TerraReactNative.podspec +48 -0
- package/android/build.gradle +87 -0
- package/android/src/bridgeCore/kotlin/README.md +1 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeBootstrap.kt +133 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeHost.kt +32 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Conversions.kt +468 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/LauncherSlots.kt +41 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Rejections.kt +150 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/StreamRegistry.kt +77 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridge.kt +86 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +258 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +52 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TokenBroker.kt +198 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeErrors.kt +217 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +493 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +897 -0
- package/android/src/healthConnectOnly/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +11 -0
- package/android/src/main/AndroidManifest.xml +24 -0
- package/android/src/main/java/co/tryterra/sdk/reactnative/TerraBridgeModule.kt +96 -0
- package/android/src/main/java/co/tryterra/sdk/reactnative/TerraReactNativePackage.kt +26 -0
- package/android/src/main/java/co/tryterra/sdk/reactnative/TerraStartup.kt +46 -0
- package/android/src/samsung/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +13 -0
- package/docs/availability.md +246 -0
- package/frameworks/TerraSDK.xcframework/Info.plist +48 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Info.plist +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.abi.json +81513 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +2150 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +2150 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftinterface +2127 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6473 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Info.plist +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +81513 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +2150 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +2150 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +2127 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +81513 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +2150 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +2150 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +2127 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/_CodeSignature/CodeResources +223 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6474 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6392 -0
- package/ios/TerraBridgeCore/BridgeBootstrap.swift +89 -0
- package/ios/TerraBridgeCore/BridgeHost.swift +13 -0
- package/ios/TerraBridgeCore/Conversions.swift +467 -0
- package/ios/TerraBridgeCore/Generated/BridgeErrors.swift +179 -0
- package/ios/TerraBridgeCore/Generated/BridgeOps.swift +795 -0
- package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +1317 -0
- package/ios/TerraBridgeCore/Locked.swift +15 -0
- package/ios/TerraBridgeCore/README.md +1 -0
- package/ios/TerraBridgeCore/Rejections.swift +130 -0
- package/ios/TerraBridgeCore/RuntimeAttachment.swift +60 -0
- package/ios/TerraBridgeCore/StreamRegistry.swift +59 -0
- package/ios/TerraBridgeCore/TerraBridge.swift +122 -0
- package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +254 -0
- package/ios/TerraBridgeCore/TerraBridgeStreamsImpl.swift +60 -0
- package/ios/TerraBridgeCore/TokenBroker.swift +147 -0
- package/ios/TerraBridgeModule.mm +79 -0
- package/ios/TerraBridgeModuleImpl.swift +49 -0
- package/ios/TerraReactNative.swift +19 -0
- package/lib/EndUser.d.ts +12 -0
- package/lib/EndUser.js +13 -0
- package/lib/NativeTerraBridge.d.ts +23 -0
- package/lib/NativeTerraBridge.js +13 -0
- package/lib/Terra.d.ts +154 -0
- package/lib/Terra.js +280 -0
- package/lib/TerraError.d.ts +42 -0
- package/lib/TerraError.js +95 -0
- package/lib/createTerra.d.ts +8 -0
- package/lib/createTerra.js +19 -0
- package/lib/generated/client.d.ts +92 -0
- package/lib/generated/client.js +196 -0
- package/lib/generated/codecs.d.ts +135 -0
- package/lib/generated/codecs.js +1358 -0
- package/lib/generated/errors.d.ts +13 -0
- package/lib/generated/errors.js +68 -0
- package/lib/generated/ops.d.ts +13 -0
- package/lib/generated/ops.js +137 -0
- package/lib/generated/streams.d.ts +23 -0
- package/lib/generated/streams.js +58 -0
- package/lib/generated/types.d.ts +481 -0
- package/lib/generated/types.js +92 -0
- package/lib/index.d.ts +29 -0
- package/lib/index.js +55 -0
- package/package.json +71 -4
- package/src/EndUser.ts +15 -0
- package/src/NativeTerraBridge.ts +27 -0
- package/src/Terra.ts +462 -0
- package/src/TerraError.ts +116 -0
- package/src/createTerra.ts +18 -0
- package/src/generated/client.ts +243 -0
- package/src/generated/codecs.ts +1287 -0
- package/src/generated/errors.ts +113 -0
- package/src/generated/ops.ts +197 -0
- package/src/generated/streams.ts +74 -0
- package/src/generated/types.ts +667 -0
- package/src/index.ts +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Terra
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,112 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tryterra/react-native
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Terra's health SDK for React Native: Apple Health on iOS, Health Connect (and optionally Samsung
|
|
4
|
+
Health) on Android. A TurboModule over the native SDKs in this repository. React Native 0.81 or
|
|
5
|
+
newer, New Architecture only.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createTerra, EndUser } from "@tryterra/react-native";
|
|
9
|
+
|
|
10
|
+
const terra = createTerra({ tokenProvider: () => api.terraToken() });
|
|
11
|
+
|
|
12
|
+
await terra.connect("health", EndUser.reference(user.id));
|
|
13
|
+
await terra.permissions.request({ read: ["body"] });
|
|
14
|
+
const hr = await terra.body.heartRate({ kind: "lastHours", hours: 1 });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
There is no sync call: once connected, the SDK uploads on its own, in the background too. Every
|
|
18
|
+
rejection is a `TerraError` with a stable `code` (see `docs/availability.md` for the full list and
|
|
19
|
+
what exists on which platform).
|
|
20
|
+
|
|
21
|
+
## Native setup
|
|
22
|
+
|
|
23
|
+
The client is built natively at launch, before any JavaScript runs, because background delivery
|
|
24
|
+
needs it then. Both platforms read the dev id from native configuration.
|
|
25
|
+
|
|
26
|
+
### iOS
|
|
27
|
+
|
|
28
|
+
1. `Info.plist`: `TerraDevID`, `NSHealthShareUsageDescription`, `NSHealthUpdateUsageDescription`,
|
|
29
|
+
`UIBackgroundModes` with `fetch`, and `BGTaskSchedulerPermittedIdentifiers` with
|
|
30
|
+
`co.tryterra.sdk.refresh`.
|
|
31
|
+
2. Entitlements: `com.apple.developer.healthkit`, `com.apple.developer.healthkit.access` (an empty
|
|
32
|
+
array) and `com.apple.developer.healthkit.background-delivery`.
|
|
33
|
+
3. One line, first thing in `application(_:didFinishLaunchingWithOptions:)`:
|
|
34
|
+
|
|
35
|
+
```swift
|
|
36
|
+
import TerraReactNative
|
|
37
|
+
TerraReactNative.bootstrap()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Without it every call rejects with `bridge.not_bootstrapped`, and `details.line` names the line.
|
|
41
|
+
|
|
42
|
+
### Android
|
|
43
|
+
|
|
44
|
+
1. `minSdkVersion` 28, `compileSdkVersion` 36.
|
|
45
|
+
2. In `<application>`: `<meta-data android:name="co.tryterra.sdk.DEV_ID" android:value="..." />`.
|
|
46
|
+
Nothing else in code: an `androidx.startup` initializer bootstraps before `Application.onCreate`.
|
|
47
|
+
An app that removed WorkManager's default initializer, to configure WorkManager itself, adds
|
|
48
|
+
`TerraReactNative.bootstrap(this)` at the end of `Application.onCreate`.
|
|
49
|
+
3. One `<uses-permission android:name="android.permission.health.*">` per Health Connect type your
|
|
50
|
+
`permissions.request` reads, plus the permissions-rationale activity filters. `example/` has a
|
|
51
|
+
complete manifest.
|
|
52
|
+
|
|
53
|
+
### Samsung Health (Android, optional)
|
|
54
|
+
|
|
55
|
+
1. Get Samsung's partner approval and download `samsung-health-data-api.aar` (Health Data SDK
|
|
56
|
+
1.1.0) from Samsung. Samsung's licence forbids anyone else redistributing it.
|
|
57
|
+
2. Add it to your app module: `implementation(files("libs/samsung-health-data-api.aar"))`.
|
|
58
|
+
3. Set `terraSamsungHealth=true` in `android/gradle.properties`.
|
|
59
|
+
|
|
60
|
+
Then `terra.connect("samsungHealth", ...)`, `terra.permissions.request({...}, "samsungHealth")` and
|
|
61
|
+
`terra.diagnostics.state("samsungHealth")` reach Samsung Health directly. Reads, writes and change
|
|
62
|
+
streams are Health Connect's. Without step 3, `samsungHealth` rejects with
|
|
63
|
+
`health_data_unavailable`.
|
|
64
|
+
|
|
65
|
+
## Developing in this repository
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
npm ci && npx tsc --noEmit && npx jest # the JS lane (surface.yml)
|
|
69
|
+
npm run build # lib/, the types the example compiles against
|
|
70
|
+
cd example && npm ci && npx tsc --noEmit # the example's typecheck (react-native.yml)
|
|
71
|
+
npm run pods # then: npm run ios / npm run android
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`npm run pods` is a plain `pod install` (CocoaPods from Homebrew or `gem install`). The Podfile
|
|
75
|
+
takes React Native's core prebuilt, as CI does; built from source it does not compile under Xcode 26
|
|
76
|
+
and rewrites `Podfile.lock`.
|
|
77
|
+
|
|
78
|
+
`example/` builds against this checkout's SDKs, not the published ones: its Podfile sets
|
|
79
|
+
`TERRA_IOS_SDK_FROM_SOURCE=1` and points `TerraiOS` at `ios/TerraiOS.podspec`, and
|
|
80
|
+
`npm run android` first publishes `android/` into `android/build/published`, the only repository
|
|
81
|
+
the example takes `co.tryterra:terra-android` from.
|
|
82
|
+
`xcrun simctl launch booted co.tryterra.reactnative.example -terraSmoke` (or
|
|
83
|
+
`adb shell am start -a android.intent.action.VIEW -d terraexample://smoke`) runs its smoke test.
|
|
84
|
+
|
|
85
|
+
How the native halves ship, and why, is `docs/distribution.md`.
|
|
86
|
+
|
|
87
|
+
## Releasing
|
|
88
|
+
|
|
89
|
+
The package pins one version of each native SDK in `package.json`'s `terraNative`, and
|
|
90
|
+
`packaging.test.ts` holds those pins equal to `sdkVersion` in `ios/Sources/TerraSDK/Version.swift`
|
|
91
|
+
and `VERSION_NAME` in `android/gradle.properties`. The tarball carries the released
|
|
92
|
+
`TerraSDK.xcframework` (`prepack` downloads it from the `tryterra/TerraiOS` release and checks it
|
|
93
|
+
against the checksum that tag's `Package.swift` pins), and a customer's Gradle sync resolves the
|
|
94
|
+
Android SDK from Maven Central, so the wrapper is released last:
|
|
95
|
+
|
|
96
|
+
1. Release iOS (`v<version>`, then the `tryterra/TerraiOS` steps in the root README) and Android
|
|
97
|
+
(`android-v<version>`, then release the staged bundle from the Central Portal).
|
|
98
|
+
2. Push `react-native-v<version>`. `.github/workflows/react-native-release.yml` checks the tag
|
|
99
|
+
against `version`, refuses while either native pin is missing from the `tryterra/TerraiOS`
|
|
100
|
+
release or Central, packs, and publishes with `npm publish --access public` (a pre-release under
|
|
101
|
+
the `next` dist-tag).
|
|
102
|
+
|
|
103
|
+
The workflow publishes through npm trusted publishing, with no npm secret in this repository. It
|
|
104
|
+
works once the package's settings on npmjs.com list `tryterra/terra-mobile` and
|
|
105
|
+
`react-native-release.yml` as a trusted publisher; npm allows that only after the package exists, so
|
|
106
|
+
the first version is published by hand with `npm publish --access public` from `react-native/`. A
|
|
107
|
+
version already on npm is skipped.
|
|
108
|
+
|
|
109
|
+
`npm pack --dry-run` shows the tarball. `prepack` builds `lib/`, copies hybrid-common into
|
|
110
|
+
`ios/TerraBridgeCore` and `android/src/bridgeCore`, and fetches `frameworks/TerraSDK.xcframework`
|
|
111
|
+
(`npm run fetch-sdk`), none of which a checkout carries. The xcframework is most of the tarball's
|
|
112
|
+
12.6 MB.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
# hybrid-common's Swift, copied in by scripts/vendor-bridge-core.js: from `prepack` for the npm
|
|
6
|
+
# tarball, and from the example app's Podfile in this repository. A podspec cannot reach a source
|
|
7
|
+
# file outside its own directory, so without the copy this pod would compile a module with no
|
|
8
|
+
# bridge in it and fail at link time far from the cause.
|
|
9
|
+
bridge_core = File.join(__dir__, "ios", "TerraBridgeCore")
|
|
10
|
+
unless File.directory?(bridge_core)
|
|
11
|
+
raise "TerraReactNative: #{bridge_core} is missing. In a terra-mobile checkout run " \
|
|
12
|
+
"`npm run vendor` in react-native/; an installed package always carries it."
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Pod::Spec.new do |s|
|
|
16
|
+
s.name = "TerraReactNative"
|
|
17
|
+
s.version = package["version"]
|
|
18
|
+
s.summary = package["description"]
|
|
19
|
+
s.homepage = "https://github.com/tryterra/terra-mobile"
|
|
20
|
+
s.license = package["license"]
|
|
21
|
+
s.authors = "Terra"
|
|
22
|
+
# RN 0.81's own floor; TerraSDK's is 15.0.
|
|
23
|
+
s.platforms = { :ios => "15.1" }
|
|
24
|
+
s.source = { :git => "https://github.com/tryterra/terra-mobile.git", :tag => "v#{s.version}" }
|
|
25
|
+
|
|
26
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
27
|
+
# hybrid-common is written in the Swift 6 language mode, and its concurrency checking is part of
|
|
28
|
+
# what it promises.
|
|
29
|
+
s.swift_version = "6.0"
|
|
30
|
+
|
|
31
|
+
# CocoaPods trunk takes no new releases; React Native's `spm_dependency` links dynamic
|
|
32
|
+
# binary targets without embedding them in the app.
|
|
33
|
+
if ENV["TERRA_IOS_SDK_FROM_SOURCE"] == "1"
|
|
34
|
+
s.dependency "TerraiOS", package["terraNative"]["ios"]
|
|
35
|
+
else
|
|
36
|
+
sdk = File.join(__dir__, "frameworks", "TerraSDK.xcframework")
|
|
37
|
+
unless File.directory?(sdk)
|
|
38
|
+
raise "TerraReactNative: #{sdk} is missing. In a terra-mobile checkout run " \
|
|
39
|
+
"`npm run fetch-sdk` in react-native/, or set TERRA_IOS_SDK_FROM_SOURCE=1; an installed " \
|
|
40
|
+
"package always carries it."
|
|
41
|
+
end
|
|
42
|
+
s.vendored_frameworks = "frameworks/TerraSDK.xcframework"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES" }
|
|
46
|
+
|
|
47
|
+
install_modules_dependencies(s)
|
|
48
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import groovy.json.JsonSlurper
|
|
2
|
+
|
|
3
|
+
buildscript {
|
|
4
|
+
// The host's Kotlin, so the serialization plugin matches the compiler that runs it.
|
|
5
|
+
ext.terraKotlinVersion = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : "2.1.20"
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
dependencies {
|
|
11
|
+
// hybrid-common's generated DTOs are @Serializable. A host's root build has the Kotlin plugin
|
|
12
|
+
// already; the serialization plugin it has no reason to carry.
|
|
13
|
+
classpath "org.jetbrains.kotlin:kotlin-serialization:${terraKotlinVersion}"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
apply plugin: "com.android.library"
|
|
18
|
+
apply plugin: "kotlin-android"
|
|
19
|
+
apply plugin: "org.jetbrains.kotlin.plugin.serialization"
|
|
20
|
+
apply plugin: "com.facebook.react"
|
|
21
|
+
|
|
22
|
+
def packageJson = new JsonSlurper().parse(file("../package.json"))
|
|
23
|
+
// The native SDK, exactly the version this package was released against. In this repository the
|
|
24
|
+
// example's settings.gradle includes ../android as a build, which substitutes the project for it.
|
|
25
|
+
def terraAndroidVersion = packageJson.terraNative.android
|
|
26
|
+
|
|
27
|
+
// hybrid-common compiled in place inside a terra-mobile checkout, so this build can never compile a
|
|
28
|
+
// stale copy; inside an npm tarball, the copy `prepack` made (scripts/vendor-bridge-core.js).
|
|
29
|
+
// Canonical, because a `file:..` install reaches this project through a node_modules symlink, and
|
|
30
|
+
// the checkout is only one level up from where the link points.
|
|
31
|
+
def bridgeCoreInRepo = new File(projectDir.canonicalFile, "../../hybrid-common/android/src/main/kotlin").canonicalFile
|
|
32
|
+
def bridgeCore = bridgeCoreInRepo.exists() ? bridgeCoreInRepo : file("src/bridgeCore/kotlin")
|
|
33
|
+
if (!bridgeCore.exists()) {
|
|
34
|
+
throw new GradleException("@tryterra/react-native: hybrid-common sources are missing at ${bridgeCore}")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Samsung Health is opt-in, because the host has to supply Samsung's SDK itself: Samsung's licence
|
|
38
|
+
// forbids redistributing it, so no artifact of ours can carry it (see
|
|
39
|
+
// android/terra-android-samsung/libs/README.md). A host sets `terraSamsungHealth=true` in its
|
|
40
|
+
// gradle.properties and adds `samsung-health-data-api.aar` to its app; this module then depends on
|
|
41
|
+
// the add-on and registers its store at launch.
|
|
42
|
+
def samsungHealth = (rootProject.findProperty("terraSamsungHealth") ?: "false").toString().toBoolean()
|
|
43
|
+
|
|
44
|
+
def safeExtGet(prop, fallback) {
|
|
45
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
android {
|
|
49
|
+
namespace "co.tryterra.sdk.reactnative"
|
|
50
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 36)
|
|
51
|
+
|
|
52
|
+
defaultConfig {
|
|
53
|
+
// The SDK's floor, above React Native's own 24.
|
|
54
|
+
minSdkVersion Math.max(28, safeExtGet("minSdkVersion", 28) as int)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
compileOptions {
|
|
58
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
59
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
kotlinOptions {
|
|
63
|
+
jvmTarget = "17"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
sourceSets {
|
|
67
|
+
main {
|
|
68
|
+
java.srcDirs += [bridgeCore.path, samsungHealth ? "src/samsung/kotlin" : "src/healthConnectOnly/kotlin"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
repositories {
|
|
74
|
+
google()
|
|
75
|
+
mavenCentral()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
dependencies {
|
|
79
|
+
implementation "com.facebook.react:react-android"
|
|
80
|
+
api "co.tryterra:terra-android:${terraAndroidVersion}"
|
|
81
|
+
if (samsungHealth) {
|
|
82
|
+
implementation "co.tryterra:terra-android-samsung:${terraAndroidVersion}"
|
|
83
|
+
}
|
|
84
|
+
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0"
|
|
85
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1"
|
|
86
|
+
implementation "androidx.startup:startup-runtime:1.2.0"
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Copied from hybrid-common by scripts/vendor-bridge-core.js. Do not edit; change hybrid-common.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
package co.tryterra.sdk.bridge
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Application
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.content.pm.PackageManager
|
|
7
|
+
import android.os.Bundle
|
|
8
|
+
import android.util.Log
|
|
9
|
+
import androidx.activity.result.ActivityResultCaller
|
|
10
|
+
import co.tryterra.sdk.SDK_VERSION
|
|
11
|
+
import co.tryterra.sdk.Source
|
|
12
|
+
import co.tryterra.sdk.StoreRegistration
|
|
13
|
+
import co.tryterra.sdk.Terra
|
|
14
|
+
import co.tryterra.sdk.TerraError
|
|
15
|
+
import co.tryterra.sdk.background.BackgroundSync
|
|
16
|
+
import co.tryterra.sdk.bridge.generated.BridgeError
|
|
17
|
+
import co.tryterra.sdk.permissions.PermissionLauncher
|
|
18
|
+
import java.util.concurrent.atomic.AtomicReference
|
|
19
|
+
import kotlinx.coroutines.CoroutineScope
|
|
20
|
+
|
|
21
|
+
public object BridgeBootstrap {
|
|
22
|
+
|
|
23
|
+
/** The manifest meta-data carrying the developer id. */
|
|
24
|
+
public const val DEV_ID_KEY: String = "co.tryterra.sdk.DEV_ID"
|
|
25
|
+
|
|
26
|
+
private const val TAG = "TerraBridge"
|
|
27
|
+
|
|
28
|
+
/** What bootstrap parked: the client, and the broker the client was built with. */
|
|
29
|
+
public class Parked internal constructor(
|
|
30
|
+
public val terra: Terra,
|
|
31
|
+
internal val tokens: TokenBroker,
|
|
32
|
+
internal val host: BridgeHost,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
private val parked = AtomicReference<Parked?>(null)
|
|
36
|
+
|
|
37
|
+
/** What bootstrap parked, or null when it has not run. */
|
|
38
|
+
public val current: Parked? get() = parked.get()
|
|
39
|
+
|
|
40
|
+
@Synchronized
|
|
41
|
+
public fun bootstrap(context: Context, stores: StoreRegistration.() -> Unit = {}): Parked? {
|
|
42
|
+
parked.get()?.let { return it }
|
|
43
|
+
val app = context.applicationContext as Application
|
|
44
|
+
val meta = metaData(app)
|
|
45
|
+
val devId = meta?.getString(DEV_ID_KEY)?.trim()?.takeIf { it.isNotEmpty() }
|
|
46
|
+
if (devId == null) {
|
|
47
|
+
Log.e(TAG, "no <meta-data android:name=\"$DEV_ID_KEY\"> in the app manifest, so Terra is not set up")
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
val tokens = TokenBroker()
|
|
52
|
+
val terra = Terra(app, devId, tokenProvider = tokens::provide, stores = stores)
|
|
53
|
+
val launchers = Launchers(terra, servedSources(terra))
|
|
54
|
+
app.registerActivityLifecycleCallbacks(launchers)
|
|
55
|
+
val registered = BackgroundSync.register(app, terra)
|
|
56
|
+
val result = Parked(terra, tokens, LaunchHost(app, terra, launchers, devId, registered))
|
|
57
|
+
parked.set(result)
|
|
58
|
+
return result
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public fun attach(scope: CoroutineScope, missingLine: String, emit: (String, String) -> Unit): TerraBridge {
|
|
62
|
+
val current = parked.get()
|
|
63
|
+
?: throw TerraBridge.BridgeRejection(Rejections.of(BridgeError.NotBootstrapped(missingLine)))
|
|
64
|
+
return TerraBridge(current.terra, current.tokens, current.host, scope, emit = emit)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private fun metaData(app: Application): Bundle? = try {
|
|
68
|
+
app.packageManager.getApplicationInfo(app.packageName, PackageManager.GET_META_DATA).metaData
|
|
69
|
+
} catch (missing: PackageManager.NameNotFoundException) {
|
|
70
|
+
null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The sources [terra] has a store for, asked of the client itself rather than tracked beside it. */
|
|
74
|
+
internal fun servedSources(terra: Terra): Set<Source> = Source.entries.filterTo(LinkedHashSet()) { source ->
|
|
75
|
+
try {
|
|
76
|
+
terra.on(source)
|
|
77
|
+
true
|
|
78
|
+
} catch (missing: TerraError.HealthDataUnavailable) {
|
|
79
|
+
false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
internal class Launchers(
|
|
84
|
+
private val terra: Terra,
|
|
85
|
+
private val sources: Set<Source>,
|
|
86
|
+
) : Application.ActivityLifecycleCallbacks {
|
|
87
|
+
private val slots = LauncherSlots<Activity, PermissionLauncher>()
|
|
88
|
+
|
|
89
|
+
fun current(source: Source): PermissionLauncher? = slots.current(source)
|
|
90
|
+
|
|
91
|
+
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
|
92
|
+
if (activity !is ActivityResultCaller) return
|
|
93
|
+
val made = sources.mapNotNull { source ->
|
|
94
|
+
try {
|
|
95
|
+
source to terra.on(source).permissions.launcher(activity)
|
|
96
|
+
} catch (refused: RuntimeException) {
|
|
97
|
+
Log.w(TAG, "no permission sheet for $source in ${activity.javaClass.simpleName}", refused)
|
|
98
|
+
null
|
|
99
|
+
}
|
|
100
|
+
}.toMap()
|
|
101
|
+
slots.created(activity, made)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
override fun onActivityDestroyed(activity: Activity) {
|
|
105
|
+
slots.destroyed(activity)?.values?.forEach { it.unregister() }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
override fun onActivityStarted(activity: Activity) = slots.started(activity)
|
|
109
|
+
override fun onActivityStopped(activity: Activity) = slots.stopped(activity)
|
|
110
|
+
override fun onActivityResumed(activity: Activity) {}
|
|
111
|
+
override fun onActivityPaused(activity: Activity) {}
|
|
112
|
+
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private class LaunchHost(
|
|
116
|
+
private val app: Application,
|
|
117
|
+
private val terra: Terra,
|
|
118
|
+
private val launchers: Launchers,
|
|
119
|
+
override val devId: String,
|
|
120
|
+
private val registered: Boolean,
|
|
121
|
+
) : BridgeHost {
|
|
122
|
+
override fun permissionLauncher(source: Source): PermissionLauncher? = launchers.current(source)
|
|
123
|
+
|
|
124
|
+
override suspend fun backgroundRegistered(): Boolean =
|
|
125
|
+
registered && BackgroundSync.isScheduled(app, Terra.DEFAULT_STORAGE_DOMAIN) != false
|
|
126
|
+
|
|
127
|
+
override suspend fun cancelBackgroundSync(): Boolean = BackgroundSync.cancel(app, terra)
|
|
128
|
+
|
|
129
|
+
override val platform: String = "android"
|
|
130
|
+
|
|
131
|
+
override val sdkVersion: String = SDK_VERSION
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package co.tryterra.sdk.bridge
|
|
2
|
+
|
|
3
|
+
import co.tryterra.sdk.Source
|
|
4
|
+
import co.tryterra.sdk.permissions.PermissionLauncher
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The parts of an op that are not the SDK: the permission sheet's launcher, and the scheduler.
|
|
8
|
+
*
|
|
9
|
+
* A seam rather than a `Context` parameter, for the reason `HealthStore` is one in the SDK itself:
|
|
10
|
+
* everything behind it needs a device, and everything in front of it can then be decided on a plain
|
|
11
|
+
* JVM. `TerraBridgeOpsImpl` holds no `Context`, so the whole dispatch is testable on `ubuntu-latest`
|
|
12
|
+
* with no emulator - which is the lane that catches the disease this wrapper exists to cure.
|
|
13
|
+
*/
|
|
14
|
+
public interface BridgeHost {
|
|
15
|
+
|
|
16
|
+
/** The launcher the current activity registered for [source]'s permission sheet, or null when none has. */
|
|
17
|
+
public fun permissionLauncher(source: Source): PermissionLauncher?
|
|
18
|
+
|
|
19
|
+
/** Whether the scheduler holds this client's periodic work. False means bootstrap did not run. */
|
|
20
|
+
public suspend fun backgroundRegistered(): Boolean
|
|
21
|
+
|
|
22
|
+
/** Drops the schedule. Android only; there is no iOS counterpart. */
|
|
23
|
+
public suspend fun cancelBackgroundSync(): Boolean
|
|
24
|
+
|
|
25
|
+
/** What `client.attach` reports, so a host can tell a misconfiguration from a bug. */
|
|
26
|
+
public val platform: String
|
|
27
|
+
|
|
28
|
+
public val sdkVersion: String
|
|
29
|
+
|
|
30
|
+
/** The dev id the native bootstrap was configured with, or null when it did not run. */
|
|
31
|
+
public val devId: String?
|
|
32
|
+
}
|