@symbiote-native/app-integrity 0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
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 ADDED
@@ -0,0 +1,149 @@
1
+ # @symbiote-native/app-integrity
2
+
3
+ Let your backend tell your real app on a real device apart from a modified app, a script or an
4
+ emulator. One API for every [SymbioteNative](../../README.md) adapter (React, Vue, Svelte, Solid
5
+ and Angular).
6
+
7
+ It wraps [`@expo/app-integrity`](https://github.com/expo/expo/tree/main/packages/expo-app-integrity)
8
+ (Apple's App Attest, Google Play Integrity, and Android hardware-attested key generation). Like
9
+ [`@symbiote-native/application`](../application), everything here is a one-shot async call (or a
10
+ plain sync constant) with no per-instance state, so there is no hook, composable or service to
11
+ wrap: every adapter entry point is a plain re-export of the same `core`.
12
+
13
+ ## Install
14
+
15
+ **New app:**
16
+
17
+ ```bash
18
+ npx @symbiote-native/cli new my-app --app-integrity
19
+ ```
20
+
21
+ **Existing SymbioteNative app:**
22
+
23
+ ```bash
24
+ npx @symbiote-native/cli add --app-integrity
25
+ ```
26
+
27
+ Either way: installs `@symbiote-native/app-integrity` and wires the native autolinking
28
+ automatically, see [`@symbiote-native/cli`](../cli).
29
+
30
+ <details>
31
+ <summary>Manual install (no CLI, wiring native autolinking by hand)</summary>
32
+
33
+ ```bash
34
+ npm install @symbiote-native/app-integrity
35
+ ```
36
+
37
+ `@expo/app-integrity` and `expo-modules-core` come along as regular dependencies, pinned to exact
38
+ versions. Never install either yourself, and never add the `expo` meta-package to your project
39
+ (it bundles its own Metro/Babel pipeline, which conflicts with this project's own).
40
+
41
+ ## Required one-time step: native autolinking wiring
42
+
43
+ Unlike a plain RN native module, `@expo/app-integrity`'s native code is discovered by
44
+ `expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism. This needs wiring
45
+ into the native host app **once**, covering this package and every other `expo-modules-core`
46
+ package with zero further changes:
47
+
48
+ | Platform | Touches |
49
+ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
50
+ | iOS | `ios/Podfile`, add `use_expo_modules!` |
51
+ | iOS | `AppDelegate.swift`, Expo's runtime-bootstrap hook |
52
+ | Android | `settings.gradle` / `app/build.gradle`, resolve and include the Expo Gradle projects |
53
+ | Android | `MainApplication.kt`, Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
54
+
55
+ Full mechanics live in the `symbiote-expo-native-module` project skill. Reference
56
+ implementation: `examples/expo-react/ios/Podfile` and
57
+ `examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt`.
58
+
59
+ No app-level permission strings are needed. App Attest, Play Integrity, and hardware attestation
60
+ are all system services with no user-facing permission prompt.
61
+
62
+ Each platform needs one setup step of its own: on iOS, add the **App Attest** capability in Xcode
63
+ (Signing & Capabilities; the app needs a registered App ID); on Android, enable the Play Integrity
64
+ API for your app and note your Google Cloud project number.
65
+
66
+ </details>
67
+
68
+ ## Shape
69
+
70
+ ```
71
+ src/core/ isSupported constant, generateKeyAsync / attestKeyAsync / generateAssertionAsync
72
+ (App Attest, ios), prepareIntegrityTokenProviderAsync / requestIntegrityCheckAsync
73
+ (Play Integrity, android), isHardwareAttestationSupportedAsync /
74
+ generateHardwareAttestedKeyAsync / getAttestationCertificateChainAsync (hardware
75
+ attestation, android). native-module.ts resolves the native module via
76
+ expo-modules-core's requireNativeModule.
77
+ src/angular/ @symbiote-native/app-integrity/angular, export * from '../core'
78
+ ```
79
+
80
+ `./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto `src/core/`,
81
+ no physical per-framework file, since there's nothing to subscribe to or clean up. `./angular`
82
+ stays a physical file/subpath since Angular ships through a separate `ngc`/AOT build (`build-ngc/`).
83
+
84
+ ## API
85
+
86
+ ```ts
87
+ isSupported: boolean // ios: real App Attest support; off ios: always true
88
+
89
+ generateKeyAsync(): Promise<string> // ios only
90
+ attestKeyAsync(keyId: string, challenge: string): Promise<string> // ios only
91
+ generateAssertionAsync(keyId: string, challenge: string): Promise<string> // ios only
92
+
93
+ prepareIntegrityTokenProviderAsync(cloudProjectNumber: string): Promise<void> // android only
94
+ requestIntegrityCheckAsync(requestHash: string): Promise<string> // android only
95
+
96
+ isHardwareAttestationSupportedAsync(): Promise<boolean> // android only, false elsewhere
97
+ generateHardwareAttestedKeyAsync(keyAlias: string, challenge: string): Promise<void> // android only
98
+ getAttestationCertificateChainAsync(keyAlias: string): Promise<string[]> // android only
99
+ ```
100
+
101
+ ```ts
102
+ import {
103
+ generateKeyAsync,
104
+ isSupported,
105
+ } from '@symbiote-native/app-integrity';
106
+ // or the framework-scoped entry points, identical surface, re-exported verbatim:
107
+ import { generateKeyAsync } from '@symbiote-native/app-integrity/react';
108
+ import { generateKeyAsync } from '@symbiote-native/app-integrity/vue';
109
+ import { generateKeyAsync } from '@symbiote-native/app-integrity/angular';
110
+ ```
111
+
112
+ ## Notes
113
+
114
+ - **Every function throws `UnavailabilityError` when called on the wrong platform** (an
115
+ iOS-only App Attest function on Android, or an Android-only function on iOS), except
116
+ `isHardwareAttestationSupportedAsync`, which resolves `false` instead, matching upstream.
117
+ - `isSupported` is a plain constant read once at import time from the native `Constant`, `true`
118
+ on any non-iOS platform since App Attest has no Android/web equivalent to report on.
119
+ - The App Attest trio (`generateKeyAsync`/`attestKeyAsync`/`generateAssertionAsync`) is a
120
+ three-step flow: generate a key, have Apple attest it once, then generate per-request
121
+ assertions against the attested key. Send the attestation and each assertion to your own
122
+ server for verification, this package only produces the client-side artifacts.
123
+ - `generateHardwareAttestedKeyAsync`/`getAttestationCertificateChainAsync` target the Android
124
+ Keystore directly (works on GrapheneOS and other secure Android distributions), independent of
125
+ Play Integrity's `prepareIntegrityTokenProviderAsync`/`requestIntegrityCheckAsync` pair.
126
+ - If the Android token provider is reused for too long, the next token request fails with
127
+ `ERR_APP_INTEGRITY_PROVIDER_INVALID`. Call `prepareIntegrityTokenProviderAsync` again and retry.
128
+ - Verify on your server, never on the device: a check that runs only in the app can be bypassed by
129
+ the clients you are guarding against. The canary `AppIntegrityScreen` in the `examples/expo-*`
130
+ apps exercises every call.
131
+
132
+ ## Common questions
133
+
134
+ - **`ERR_APP_INTEGRITY_PROVIDER_INVALID`.** The token provider expired; call
135
+ `prepareIntegrityTokenProviderAsync` again.
136
+ - **Per-platform flow?** Android: Play Integrity Standard request (prepare once, token per action).
137
+ iOS: App Attest key per user per device, attested once.
138
+ - **Enough alone?** No, verify the token on your server.
139
+
140
+ Sources: [Expo docs: AppIntegrity](https://docs.expo.dev/versions/latest/sdk/app-integrity/),
141
+ [Expo blog: Introducing Expo App Integrity](https://expo.dev/blog/expo-app-integrity).
142
+
143
+ ## Test it
144
+
145
+ No Fabric/Descriptor angle at all, every export here is a one-shot async function or a plain
146
+ constant, never a view or per-instance state. Tests inject a fake native-module object in place
147
+ of the real `requireNativeModule` resolution (`src/core/app-integrity.test.ts`, `vitest`), no
148
+ `installFabric()`, no ViewConfig. Native behavior itself is verified on-device (see the parent
149
+ [README](../../README.md) for the project's testing model).
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,2 @@
1
+ // No per-instance state or event stream to wrap in an injectable service, plain re-export
2
+ export * from '../core/index.js';
@@ -0,0 +1,18 @@
1
+ /** @platform ios, true off ios since App Attest support has no Android/web equivalent */
2
+ export declare const isSupported: boolean;
3
+ /** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
4
+ export declare function generateKeyAsync(): Promise<string>;
5
+ /** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
6
+ export declare function attestKeyAsync(keyId: string, challenge: string): Promise<string>;
7
+ /** @platform ios */
8
+ export declare function generateAssertionAsync(keyId: string, challenge: string): Promise<string>;
9
+ /** @platform android */
10
+ export declare function prepareIntegrityTokenProviderAsync(cloudProjectNumber: string): Promise<void>;
11
+ /** @platform android */
12
+ export declare function requestIntegrityCheckAsync(requestHash: string): Promise<string>;
13
+ /** @platform android, `false` off android rather than throwing */
14
+ export declare function isHardwareAttestationSupportedAsync(): Promise<boolean>;
15
+ /** Usable on GrapheneOS and other secure Android distributions. @platform android */
16
+ export declare function generateHardwareAttestedKeyAsync(keyAlias: string, challenge: string): Promise<void>;
17
+ /** Base64-encoded X.509 certificates, verify server-side. @platform android */
18
+ export declare function getAttestationCertificateChainAsync(keyAlias: string): Promise<string[]>;
@@ -0,0 +1,66 @@
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
+ import { expoAppIntegrity } from './native-module.js';
3
+ const NATIVE_MODULE_NAME = 'expo-app-integrity';
4
+ /** @platform ios, true off ios since App Attest support has no Android/web equivalent */
5
+ export const isSupported = Platform.OS === 'ios' ? (expoAppIntegrity.isSupported ?? false) : true;
6
+ /** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
7
+ export async function generateKeyAsync() {
8
+ if (Platform.OS !== 'ios' || !expoAppIntegrity.generateKeyAsync) {
9
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateKeyAsync');
10
+ }
11
+ return expoAppIntegrity.generateKeyAsync();
12
+ }
13
+ /** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
14
+ export async function attestKeyAsync(keyId, challenge) {
15
+ if (Platform.OS !== 'ios' || !expoAppIntegrity.attestKeyAsync) {
16
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'attestKeyAsync');
17
+ }
18
+ return expoAppIntegrity.attestKeyAsync(keyId, challenge);
19
+ }
20
+ /** @platform ios */
21
+ export async function generateAssertionAsync(keyId, challenge) {
22
+ if (Platform.OS !== 'ios' || !expoAppIntegrity.generateAssertionAsync) {
23
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateAssertionAsync');
24
+ }
25
+ return expoAppIntegrity.generateAssertionAsync(keyId, challenge);
26
+ }
27
+ /** @platform android */
28
+ export async function prepareIntegrityTokenProviderAsync(cloudProjectNumber) {
29
+ if (Platform.OS !== 'android' ||
30
+ !expoAppIntegrity.prepareIntegrityTokenProviderAsync) {
31
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'prepareIntegrityTokenProviderAsync');
32
+ }
33
+ return expoAppIntegrity.prepareIntegrityTokenProviderAsync(cloudProjectNumber);
34
+ }
35
+ /** @platform android */
36
+ export async function requestIntegrityCheckAsync(requestHash) {
37
+ if (Platform.OS !== 'android' ||
38
+ !expoAppIntegrity.requestIntegrityCheckAsync) {
39
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'requestIntegrityCheckAsync');
40
+ }
41
+ return expoAppIntegrity.requestIntegrityCheckAsync(requestHash);
42
+ }
43
+ /** @platform android, `false` off android rather than throwing */
44
+ export async function isHardwareAttestationSupportedAsync() {
45
+ if (Platform.OS !== 'android' ||
46
+ !expoAppIntegrity.isHardwareAttestationSupportedAsync) {
47
+ return false;
48
+ }
49
+ return expoAppIntegrity.isHardwareAttestationSupportedAsync();
50
+ }
51
+ /** Usable on GrapheneOS and other secure Android distributions. @platform android */
52
+ export async function generateHardwareAttestedKeyAsync(keyAlias, challenge) {
53
+ if (Platform.OS !== 'android' ||
54
+ !expoAppIntegrity.generateHardwareAttestedKeyAsync) {
55
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateHardwareAttestedKeyAsync');
56
+ }
57
+ return expoAppIntegrity.generateHardwareAttestedKeyAsync(keyAlias, challenge);
58
+ }
59
+ /** Base64-encoded X.509 certificates, verify server-side. @platform android */
60
+ export async function getAttestationCertificateChainAsync(keyAlias) {
61
+ if (Platform.OS !== 'android' ||
62
+ !expoAppIntegrity.getAttestationCertificateChainAsync) {
63
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getAttestationCertificateChainAsync');
64
+ }
65
+ return expoAppIntegrity.getAttestationCertificateChainAsync(keyAlias);
66
+ }
@@ -0,0 +1 @@
1
+ export { attestKeyAsync, generateAssertionAsync, generateHardwareAttestedKeyAsync, generateKeyAsync, getAttestationCertificateChainAsync, isHardwareAttestationSupportedAsync, isSupported, prepareIntegrityTokenProviderAsync, requestIntegrityCheckAsync, } from './app-integrity';
@@ -0,0 +1 @@
1
+ export { attestKeyAsync, generateAssertionAsync, generateHardwareAttestedKeyAsync, generateKeyAsync, getAttestationCertificateChainAsync, isHardwareAttestationSupportedAsync, isSupported, prepareIntegrityTokenProviderAsync, requestIntegrityCheckAsync, } from './app-integrity.js';
@@ -0,0 +1,12 @@
1
+ export type INativeAppIntegrityModule = {
2
+ isSupported?: boolean;
3
+ generateKeyAsync?(): Promise<string>;
4
+ attestKeyAsync?(keyId: string, challenge: string): Promise<string>;
5
+ generateAssertionAsync?(keyId: string, challenge: string): Promise<string>;
6
+ prepareIntegrityTokenProviderAsync?(cloudProjectNumber: string): Promise<void>;
7
+ requestIntegrityCheckAsync?(requestHash: string): Promise<string>;
8
+ isHardwareAttestationSupportedAsync?(): Promise<boolean>;
9
+ generateHardwareAttestedKeyAsync?(keyAlias: string, challenge: string): Promise<void>;
10
+ getAttestationCertificateChainAsync?(keyAlias: string): Promise<string[]>;
11
+ };
12
+ export declare const expoAppIntegrity: INativeAppIntegrityModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_APP_INTEGRITY_MODULE_NAME = 'ExpoAppIntegrity';
3
+ export const expoAppIntegrity = requireNativeModule(EXPO_APP_INTEGRITY_MODULE_NAME);
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,3 @@
1
+ // No per-instance state or event stream to wrap in an injectable service, plain re-export
2
+ export * from '../core';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,cAAc,SAAS,CAAC"}
@@ -0,0 +1,18 @@
1
+ /** @platform ios, true off ios since App Attest support has no Android/web equivalent */
2
+ export declare const isSupported: boolean;
3
+ /** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
4
+ export declare function generateKeyAsync(): Promise<string>;
5
+ /** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
6
+ export declare function attestKeyAsync(keyId: string, challenge: string): Promise<string>;
7
+ /** @platform ios */
8
+ export declare function generateAssertionAsync(keyId: string, challenge: string): Promise<string>;
9
+ /** @platform android */
10
+ export declare function prepareIntegrityTokenProviderAsync(cloudProjectNumber: string): Promise<void>;
11
+ /** @platform android */
12
+ export declare function requestIntegrityCheckAsync(requestHash: string): Promise<string>;
13
+ /** @platform android, `false` off android rather than throwing */
14
+ export declare function isHardwareAttestationSupportedAsync(): Promise<boolean>;
15
+ /** Usable on GrapheneOS and other secure Android distributions. @platform android */
16
+ export declare function generateHardwareAttestedKeyAsync(keyAlias: string, challenge: string): Promise<void>;
17
+ /** Base64-encoded X.509 certificates, verify server-side. @platform android */
18
+ export declare function getAttestationCertificateChainAsync(keyAlias: string): Promise<string[]>;
@@ -0,0 +1,67 @@
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
+ import { expoAppIntegrity } from './native-module';
3
+ const NATIVE_MODULE_NAME = 'expo-app-integrity';
4
+ /** @platform ios, true off ios since App Attest support has no Android/web equivalent */
5
+ export const isSupported = Platform.OS === 'ios' ? (expoAppIntegrity.isSupported ?? false) : true;
6
+ /** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
7
+ export async function generateKeyAsync() {
8
+ if (Platform.OS !== 'ios' || !expoAppIntegrity.generateKeyAsync) {
9
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateKeyAsync');
10
+ }
11
+ return expoAppIntegrity.generateKeyAsync();
12
+ }
13
+ /** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
14
+ export async function attestKeyAsync(keyId, challenge) {
15
+ if (Platform.OS !== 'ios' || !expoAppIntegrity.attestKeyAsync) {
16
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'attestKeyAsync');
17
+ }
18
+ return expoAppIntegrity.attestKeyAsync(keyId, challenge);
19
+ }
20
+ /** @platform ios */
21
+ export async function generateAssertionAsync(keyId, challenge) {
22
+ if (Platform.OS !== 'ios' || !expoAppIntegrity.generateAssertionAsync) {
23
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateAssertionAsync');
24
+ }
25
+ return expoAppIntegrity.generateAssertionAsync(keyId, challenge);
26
+ }
27
+ /** @platform android */
28
+ export async function prepareIntegrityTokenProviderAsync(cloudProjectNumber) {
29
+ if (Platform.OS !== 'android' ||
30
+ !expoAppIntegrity.prepareIntegrityTokenProviderAsync) {
31
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'prepareIntegrityTokenProviderAsync');
32
+ }
33
+ return expoAppIntegrity.prepareIntegrityTokenProviderAsync(cloudProjectNumber);
34
+ }
35
+ /** @platform android */
36
+ export async function requestIntegrityCheckAsync(requestHash) {
37
+ if (Platform.OS !== 'android' ||
38
+ !expoAppIntegrity.requestIntegrityCheckAsync) {
39
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'requestIntegrityCheckAsync');
40
+ }
41
+ return expoAppIntegrity.requestIntegrityCheckAsync(requestHash);
42
+ }
43
+ /** @platform android, `false` off android rather than throwing */
44
+ export async function isHardwareAttestationSupportedAsync() {
45
+ if (Platform.OS !== 'android' ||
46
+ !expoAppIntegrity.isHardwareAttestationSupportedAsync) {
47
+ return false;
48
+ }
49
+ return expoAppIntegrity.isHardwareAttestationSupportedAsync();
50
+ }
51
+ /** Usable on GrapheneOS and other secure Android distributions. @platform android */
52
+ export async function generateHardwareAttestedKeyAsync(keyAlias, challenge) {
53
+ if (Platform.OS !== 'android' ||
54
+ !expoAppIntegrity.generateHardwareAttestedKeyAsync) {
55
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateHardwareAttestedKeyAsync');
56
+ }
57
+ return expoAppIntegrity.generateHardwareAttestedKeyAsync(keyAlias, challenge);
58
+ }
59
+ /** Base64-encoded X.509 certificates, verify server-side. @platform android */
60
+ export async function getAttestationCertificateChainAsync(keyAlias) {
61
+ if (Platform.OS !== 'android' ||
62
+ !expoAppIntegrity.getAttestationCertificateChainAsync) {
63
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getAttestationCertificateChainAsync');
64
+ }
65
+ return expoAppIntegrity.getAttestationCertificateChainAsync(keyAlias);
66
+ }
67
+ //# sourceMappingURL=app-integrity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-integrity.js","sourceRoot":"","sources":["../../src/core/app-integrity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { attestKeyAsync, generateAssertionAsync, generateHardwareAttestedKeyAsync, generateKeyAsync, getAttestationCertificateChainAsync, isHardwareAttestationSupportedAsync, isSupported, prepareIntegrityTokenProviderAsync, requestIntegrityCheckAsync, } from './app-integrity';
@@ -0,0 +1,2 @@
1
+ export { attestKeyAsync, generateAssertionAsync, generateHardwareAttestedKeyAsync, generateKeyAsync, getAttestationCertificateChainAsync, isHardwareAttestationSupportedAsync, isSupported, prepareIntegrityTokenProviderAsync, requestIntegrityCheckAsync, } from './app-integrity';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,gCAAgC,EAChC,gBAAgB,EAChB,mCAAmC,EACnC,mCAAmC,EACnC,WAAW,EACX,kCAAkC,EAClC,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,12 @@
1
+ export type INativeAppIntegrityModule = {
2
+ isSupported?: boolean;
3
+ generateKeyAsync?(): Promise<string>;
4
+ attestKeyAsync?(keyId: string, challenge: string): Promise<string>;
5
+ generateAssertionAsync?(keyId: string, challenge: string): Promise<string>;
6
+ prepareIntegrityTokenProviderAsync?(cloudProjectNumber: string): Promise<void>;
7
+ requestIntegrityCheckAsync?(requestHash: string): Promise<string>;
8
+ isHardwareAttestationSupportedAsync?(): Promise<boolean>;
9
+ generateHardwareAttestedKeyAsync?(keyAlias: string, challenge: string): Promise<void>;
10
+ getAttestationCertificateChainAsync?(keyAlias: string): Promise<string[]>;
11
+ };
12
+ export declare const expoAppIntegrity: INativeAppIntegrityModule;
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_APP_INTEGRITY_MODULE_NAME = 'ExpoAppIntegrity';
3
+ export const expoAppIntegrity = requireNativeModule(EXPO_APP_INTEGRITY_MODULE_NAME);
4
+ //# sourceMappingURL=native-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,8BAA8B,GAAG,kBAAkB,CAAC;AAsB1D,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,CACjD,8BAA8B,CAC/B,CAAC"}
@@ -0,0 +1,12 @@
1
+ {
2
+ "android": {
3
+ "gradleProjectName": "expo-app-integrity",
4
+ "modules": [
5
+ {
6
+ "importPath": "expo.modules.integrity.IntegrityModule",
7
+ "className": "IntegrityModule",
8
+ "nativeName": "ExpoAppIntegrity"
9
+ }
10
+ ]
11
+ }
12
+ }
package/package.json ADDED
@@ -0,0 +1,150 @@
1
+ {
2
+ "name": "@symbiote-native/app-integrity",
3
+ "version": "0.1.0",
4
+ "description": "@expo/app-integrity wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Apple's App Attest, Android's Play Integrity, and Android hardware-attested key generation.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "react-native",
8
+ "symbiote-native",
9
+ "app-integrity",
10
+ "react",
11
+ "vue",
12
+ "svelte",
13
+ "solid",
14
+ "angular",
15
+ "app-attest",
16
+ "play-integrity"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
21
+ "directory": "packages/app-integrity"
22
+ },
23
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/app-integrity#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
26
+ },
27
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
28
+ "type": "module",
29
+ "main": "./build/core/index.js",
30
+ "module": "./build/core/index.js",
31
+ "types": "./build/core/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./build/core/index.d.ts",
35
+ "default": "./build/core/index.js"
36
+ },
37
+ "./vue": {
38
+ "types": "./build/core/index.d.ts",
39
+ "default": "./build/core/index.js"
40
+ },
41
+ "./react": {
42
+ "types": "./build/core/index.d.ts",
43
+ "default": "./build/core/index.js"
44
+ },
45
+ "./svelte": {
46
+ "types": "./build/core/index.d.ts",
47
+ "default": "./build/core/index.js"
48
+ },
49
+ "./solid": {
50
+ "types": "./build/core/index.d.ts",
51
+ "default": "./build/core/index.js"
52
+ },
53
+ "./angular": {
54
+ "types": "./build-ngc/angular/index.d.ts",
55
+ "react-native": "./build-ngc/angular/index.js",
56
+ "default": "./src/angular/index.ts"
57
+ }
58
+ },
59
+ "files": [
60
+ "src",
61
+ "build",
62
+ "build-ngc",
63
+ "native-link.json",
64
+ "!src/**/*.test.*",
65
+ "!src/**/*.spec.*",
66
+ "!src/**/*.detox.*"
67
+ ],
68
+ "publishConfig": {
69
+ "access": "public"
70
+ },
71
+ "dependencies": {
72
+ "@expo/app-integrity": "57.0.2",
73
+ "expo-modules-core": "57.0.20"
74
+ },
75
+ "peerDependencies": {
76
+ "@angular/core": ">=20",
77
+ "@vue/runtime-core": "^3.5.13",
78
+ "react": ">=19.0.0",
79
+ "react-native": ">=0.86",
80
+ "solid-js": ">=1.9.0",
81
+ "svelte": ">=5.56.0",
82
+ "vue": ">=3.5.0",
83
+ "@symbiote-native/angular": "^3.1.3",
84
+ "@symbiote-native/engine": "^1.4.0",
85
+ "@symbiote-native/solid": "^3.0.5",
86
+ "@symbiote-native/react": "^3.1.0",
87
+ "@symbiote-native/svelte": "^3.0.5",
88
+ "@symbiote-native/vue": "^3.1.0"
89
+ },
90
+ "peerDependenciesMeta": {
91
+ "@symbiote-native/angular": {
92
+ "optional": true
93
+ },
94
+ "@symbiote-native/react": {
95
+ "optional": true
96
+ },
97
+ "@symbiote-native/solid": {
98
+ "optional": true
99
+ },
100
+ "@symbiote-native/svelte": {
101
+ "optional": true
102
+ },
103
+ "@symbiote-native/vue": {
104
+ "optional": true
105
+ },
106
+ "@angular/core": {
107
+ "optional": true
108
+ },
109
+ "@vue/runtime-core": {
110
+ "optional": true
111
+ },
112
+ "react": {
113
+ "optional": true
114
+ },
115
+ "solid-js": {
116
+ "optional": true
117
+ },
118
+ "svelte": {
119
+ "optional": true
120
+ },
121
+ "vue": {
122
+ "optional": true
123
+ }
124
+ },
125
+ "devDependencies": {
126
+ "@angular/compiler": "~22.0.8",
127
+ "@angular/compiler-cli": "~22.0.8",
128
+ "@angular/core": "~22.0.8",
129
+ "@types/node": "^26.0.0",
130
+ "@types/react": "^19.2.0",
131
+ "@vue/runtime-core": "^3.5.13",
132
+ "react": "19.2.3",
133
+ "solid-js": "^1.9.14",
134
+ "svelte": "^5.56.0",
135
+ "typescript": "~6.0.0",
136
+ "@symbiote-native/engine": "1.4.0",
137
+ "@symbiote-native/svelte": "3.0.5",
138
+ "@symbiote-native/react": "3.1.0",
139
+ "@symbiote-native/angular": "3.1.3",
140
+ "@symbiote-native/solid": "3.0.5",
141
+ "@symbiote-native/vue": "3.1.0",
142
+ "@symbiote-native/test-utils": "0.4.5"
143
+ },
144
+ "scripts": {
145
+ "typecheck": "tsc --build",
146
+ "clean": "rm -rf build-ngc",
147
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
148
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
149
+ }
150
+ }
@@ -0,0 +1,2 @@
1
+ // No per-instance state or event stream to wrap in an injectable service, plain re-export
2
+ export * from '../core';
@@ -0,0 +1,116 @@
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
+ import { expoAppIntegrity } from './native-module';
3
+
4
+ const NATIVE_MODULE_NAME = 'expo-app-integrity';
5
+
6
+ /** @platform ios, true off ios since App Attest support has no Android/web equivalent */
7
+ export const isSupported: boolean =
8
+ Platform.OS === 'ios' ? (expoAppIntegrity.isSupported ?? false) : true;
9
+
10
+ /** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
11
+ export async function generateKeyAsync(): Promise<string> {
12
+ if (Platform.OS !== 'ios' || !expoAppIntegrity.generateKeyAsync) {
13
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateKeyAsync');
14
+ }
15
+ return expoAppIntegrity.generateKeyAsync();
16
+ }
17
+
18
+ /** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
19
+ export async function attestKeyAsync(
20
+ keyId: string,
21
+ challenge: string,
22
+ ): Promise<string> {
23
+ if (Platform.OS !== 'ios' || !expoAppIntegrity.attestKeyAsync) {
24
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'attestKeyAsync');
25
+ }
26
+ return expoAppIntegrity.attestKeyAsync(keyId, challenge);
27
+ }
28
+
29
+ /** @platform ios */
30
+ export async function generateAssertionAsync(
31
+ keyId: string,
32
+ challenge: string,
33
+ ): Promise<string> {
34
+ if (Platform.OS !== 'ios' || !expoAppIntegrity.generateAssertionAsync) {
35
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateAssertionAsync');
36
+ }
37
+ return expoAppIntegrity.generateAssertionAsync(keyId, challenge);
38
+ }
39
+
40
+ /** @platform android */
41
+ export async function prepareIntegrityTokenProviderAsync(
42
+ cloudProjectNumber: string,
43
+ ): Promise<void> {
44
+ if (
45
+ Platform.OS !== 'android' ||
46
+ !expoAppIntegrity.prepareIntegrityTokenProviderAsync
47
+ ) {
48
+ throw new UnavailabilityError(
49
+ NATIVE_MODULE_NAME,
50
+ 'prepareIntegrityTokenProviderAsync',
51
+ );
52
+ }
53
+ return expoAppIntegrity.prepareIntegrityTokenProviderAsync(
54
+ cloudProjectNumber,
55
+ );
56
+ }
57
+
58
+ /** @platform android */
59
+ export async function requestIntegrityCheckAsync(
60
+ requestHash: string,
61
+ ): Promise<string> {
62
+ if (
63
+ Platform.OS !== 'android' ||
64
+ !expoAppIntegrity.requestIntegrityCheckAsync
65
+ ) {
66
+ throw new UnavailabilityError(
67
+ NATIVE_MODULE_NAME,
68
+ 'requestIntegrityCheckAsync',
69
+ );
70
+ }
71
+ return expoAppIntegrity.requestIntegrityCheckAsync(requestHash);
72
+ }
73
+
74
+ /** @platform android, `false` off android rather than throwing */
75
+ export async function isHardwareAttestationSupportedAsync(): Promise<boolean> {
76
+ if (
77
+ Platform.OS !== 'android' ||
78
+ !expoAppIntegrity.isHardwareAttestationSupportedAsync
79
+ ) {
80
+ return false;
81
+ }
82
+ return expoAppIntegrity.isHardwareAttestationSupportedAsync();
83
+ }
84
+
85
+ /** Usable on GrapheneOS and other secure Android distributions. @platform android */
86
+ export async function generateHardwareAttestedKeyAsync(
87
+ keyAlias: string,
88
+ challenge: string,
89
+ ): Promise<void> {
90
+ if (
91
+ Platform.OS !== 'android' ||
92
+ !expoAppIntegrity.generateHardwareAttestedKeyAsync
93
+ ) {
94
+ throw new UnavailabilityError(
95
+ NATIVE_MODULE_NAME,
96
+ 'generateHardwareAttestedKeyAsync',
97
+ );
98
+ }
99
+ return expoAppIntegrity.generateHardwareAttestedKeyAsync(keyAlias, challenge);
100
+ }
101
+
102
+ /** Base64-encoded X.509 certificates, verify server-side. @platform android */
103
+ export async function getAttestationCertificateChainAsync(
104
+ keyAlias: string,
105
+ ): Promise<string[]> {
106
+ if (
107
+ Platform.OS !== 'android' ||
108
+ !expoAppIntegrity.getAttestationCertificateChainAsync
109
+ ) {
110
+ throw new UnavailabilityError(
111
+ NATIVE_MODULE_NAME,
112
+ 'getAttestationCertificateChainAsync',
113
+ );
114
+ }
115
+ return expoAppIntegrity.getAttestationCertificateChainAsync(keyAlias);
116
+ }
@@ -0,0 +1,11 @@
1
+ export {
2
+ attestKeyAsync,
3
+ generateAssertionAsync,
4
+ generateHardwareAttestedKeyAsync,
5
+ generateKeyAsync,
6
+ getAttestationCertificateChainAsync,
7
+ isHardwareAttestationSupportedAsync,
8
+ isSupported,
9
+ prepareIntegrityTokenProviderAsync,
10
+ requestIntegrityCheckAsync,
11
+ } from './app-integrity';
@@ -0,0 +1,27 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+
3
+ const EXPO_APP_INTEGRITY_MODULE_NAME = 'ExpoAppIntegrity';
4
+
5
+ // The App Attest trio and `isSupported` exist only on iOS, the Play Integrity/hardware-attestation
6
+ // methods only on Android, so app-integrity.ts checks Platform.OS before calling through, same
7
+ // convention as packages/application/src/core/native-module.ts
8
+ export type INativeAppIntegrityModule = {
9
+ isSupported?: boolean;
10
+ generateKeyAsync?(): Promise<string>;
11
+ attestKeyAsync?(keyId: string, challenge: string): Promise<string>;
12
+ generateAssertionAsync?(keyId: string, challenge: string): Promise<string>;
13
+ prepareIntegrityTokenProviderAsync?(
14
+ cloudProjectNumber: string,
15
+ ): Promise<void>;
16
+ requestIntegrityCheckAsync?(requestHash: string): Promise<string>;
17
+ isHardwareAttestationSupportedAsync?(): Promise<boolean>;
18
+ generateHardwareAttestedKeyAsync?(
19
+ keyAlias: string,
20
+ challenge: string,
21
+ ): Promise<void>;
22
+ getAttestationCertificateChainAsync?(keyAlias: string): Promise<string[]>;
23
+ };
24
+
25
+ export const expoAppIntegrity = requireNativeModule<INativeAppIntegrityModule>(
26
+ EXPO_APP_INTEGRITY_MODULE_NAME,
27
+ );