@symbiote-native/tracking-transparency 0.0.1

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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/build/angular/index.d.ts +2 -0
  4. package/build/angular/index.js +4 -0
  5. package/build/angular/services/permissions.service/index.d.ts +8 -0
  6. package/build/angular/services/permissions.service/index.js +78 -0
  7. package/build/core/index.d.ts +2 -0
  8. package/build/core/index.js +2 -0
  9. package/build/core/native-module.d.ts +7 -0
  10. package/build/core/native-module.js +3 -0
  11. package/build/core/tracking-transparency.d.ts +19 -0
  12. package/build/core/tracking-transparency.js +58 -0
  13. package/build/react/hooks/use-permissions/index.d.ts +6 -0
  14. package/build/react/hooks/use-permissions/index.js +30 -0
  15. package/build/react/index.d.ts +2 -0
  16. package/build/react/index.js +6 -0
  17. package/build/vue/composables/use-permissions/index.d.ts +7 -0
  18. package/build/vue/composables/use-permissions/index.js +23 -0
  19. package/build/vue/index.d.ts +2 -0
  20. package/build/vue/index.js +6 -0
  21. package/build-ngc/angular/index.d.ts +2 -0
  22. package/build-ngc/angular/index.js +5 -0
  23. package/build-ngc/angular/index.js.map +1 -0
  24. package/build-ngc/angular/services/permissions.service/index.d.ts +11 -0
  25. package/build-ngc/angular/services/permissions.service/index.js +37 -0
  26. package/build-ngc/angular/services/permissions.service/index.js.map +1 -0
  27. package/build-ngc/core/index.d.ts +2 -0
  28. package/build-ngc/core/index.js +3 -0
  29. package/build-ngc/core/index.js.map +1 -0
  30. package/build-ngc/core/native-module.d.ts +7 -0
  31. package/build-ngc/core/native-module.js +4 -0
  32. package/build-ngc/core/native-module.js.map +1 -0
  33. package/build-ngc/core/tracking-transparency.d.ts +19 -0
  34. package/build-ngc/core/tracking-transparency.js +59 -0
  35. package/build-ngc/core/tracking-transparency.js.map +1 -0
  36. package/native-link.json +17 -0
  37. package/package.json +107 -0
  38. package/src/angular/index.ts +4 -0
  39. package/src/angular/services/permissions.service/index.ts +37 -0
  40. package/src/angular/services/permissions.service/permissions.service.test.ts +107 -0
  41. package/src/core/index.ts +12 -0
  42. package/src/core/native-module.ts +21 -0
  43. package/src/core/tracking-transparency.test.ts +137 -0
  44. package/src/core/tracking-transparency.ts +66 -0
  45. package/src/react/hooks/use-permissions/index.ts +41 -0
  46. package/src/react/hooks/use-permissions/use-permissions.test.tsx +100 -0
  47. package/src/react/index.ts +7 -0
  48. package/src/vue/composables/use-permissions/index.ts +36 -0
  49. package/src/vue/composables/use-permissions/use-permissions.test.ts +99 -0
  50. package/src/vue/index.ts +7 -0
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,210 @@
1
+ # @symbiote-native/tracking-transparency
2
+
3
+ A wrapper package for [SymbioteNative](../../README.md) that makes
4
+ [`expo-tracking-transparency`](https://github.com/expo/expo/tree/main/packages/expo-tracking-transparency)
5
+ — the iOS App Tracking Transparency prompt, permission get/request, and the advertising-ID getter
6
+ — usable from **every** adapter, React, Vue, and Angular, not just React. Like
7
+ [`@symbiote-native/brightness`](../brightness), this package's surface is mostly stateless free
8
+ async functions; only the permission surface gets its own lifecycle hook/composable/service,
9
+ mirroring upstream's own `usePermissions`. Built the same way as
10
+ [`@symbiote-native/local-auth`](../local-auth), [`@symbiote-native/battery`](../battery), and
11
+ [`@symbiote-native/brightness`](../brightness) — an `expo-modules-core`-based wrapper (see the
12
+ `symbiote-expo-native-module` project skill for the full mechanism: why `expo-modules-core` is
13
+ depended on directly and never the `expo` meta-package, why the upstream JS is hand-ported into
14
+ `core/` rather than imported, and how autolinking picks up the native module).
15
+
16
+ This is a **permission-hook package**: on iOS it drives the real ATT prompt; on Android and web
17
+ there is no such concept, so every permission call always resolves granted, matching upstream
18
+ exactly.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install @symbiote-native/tracking-transparency
24
+ ```
25
+
26
+ `expo-tracking-transparency` and `expo-modules-core` come along as regular, pinned dependencies —
27
+ never install either yourself, and never add the `expo` meta-package to this project (it bundles
28
+ its own Metro/Babel pipeline that conflicts with this project's own).
29
+
30
+ ### Required one-time step: native autolinking wiring
31
+
32
+ Unlike a plain RN native module, `expo-tracking-transparency`'s native code is discovered by
33
+ `expo-modules-autolinking`, not the `react-native.config.cjs`/podspec mechanism other wrappers in
34
+ this repo use — this needs wiring into the native host app **once**, covering this package and
35
+ every other `expo-modules-core` package with zero further changes:
36
+
37
+ | Platform | Touches |
38
+ |---|---|
39
+ | iOS | `ios/Podfile` — add `use_expo_modules!` |
40
+ | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
41
+ | iOS | `Info.plist` — `NSUserTrackingUsageDescription`, required to show the ATT prompt |
42
+ | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
43
+ | 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) |
44
+
45
+ Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
46
+ peer-dependency exclusion list — live in the `symbiote-expo-native-module` skill. The Android
47
+ native module also pulls in `com.google.android.gms:play-services-ads-identifier:18.0.1`
48
+ transitively via `expo-tracking-transparency`'s own `android/build.gradle` — automatic once the
49
+ module project is included, no extra wiring needed on our side.
50
+
51
+ ## Shape
52
+
53
+ ```
54
+ src/core/ getAdvertisingId, get/requestTrackingPermissionsAsync, isAvailable;
55
+ native-module.ts resolves ExpoTrackingTransparency through
56
+ expo-modules-core's requireNativeModule.
57
+ src/react/hooks/ @symbiote-native/tracking-transparency/react — usePermissions
58
+ src/vue/composables/ @symbiote-native/tracking-transparency/vue — usePermissions (same name)
59
+ src/angular/services/ @symbiote-native/tracking-transparency/angular — PermissionsService
60
+ ```
61
+
62
+ Each adapter's hook/composable/service wraps the same one-shot `getTrackingPermissionsAsync`/
63
+ `requestTrackingPermissionsAsync` pair in its own lifecycle idiom (auto-fetch on mount, exposed as
64
+ a tuple/object/signal). `getAdvertisingId`/`isAvailable` are stateless free-function re-exports,
65
+ written once in `core/` and shared verbatim by all three adapters.
66
+
67
+ Upstream's own `createPermissionHook`/`useTrackingPermissions` is **not** ported — that helper is
68
+ React-only (built on `useState`/`useEffect`), and this repo's convention is for each adapter to
69
+ hand-roll its own permission hook instead, exactly like `brightness`/`cellular` already do.
70
+
71
+ ## Use it
72
+
73
+ ```tsx
74
+ // React
75
+ import { usePermissions } from '@symbiote-native/tracking-transparency/react';
76
+ import { getAdvertisingId } from '@symbiote-native/tracking-transparency';
77
+
78
+ function TrackingScreen() {
79
+ const [permissionStatus, requestPermission] = usePermissions();
80
+
81
+ return (
82
+ <View>
83
+ <Text>{permissionStatus?.status ?? 'checking…'}</Text>
84
+ <Pressable onPress={() => requestPermission()}>
85
+ <Text>Request tracking permission</Text>
86
+ </Pressable>
87
+ <Pressable onPress={() => console.log(getAdvertisingId())}>
88
+ <Text>Log advertising ID</Text>
89
+ </Pressable>
90
+ </View>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ```vue
96
+ <!-- Vue -->
97
+ <script setup lang="ts">
98
+ import { usePermissions } from '@symbiote-native/tracking-transparency/vue';
99
+ import { getAdvertisingId } from '@symbiote-native/tracking-transparency';
100
+
101
+ const { status: permissionStatus, request: requestPermission } = usePermissions();
102
+ </script>
103
+
104
+ <template>
105
+ <View>
106
+ <Text>{{ permissionStatus?.status ?? 'checking…' }}</Text>
107
+ <Pressable @press="requestPermission()">
108
+ <Text>Request tracking permission</Text>
109
+ </Pressable>
110
+ <Pressable @press="console.log(getAdvertisingId())">
111
+ <Text>Log advertising ID</Text>
112
+ </Pressable>
113
+ </View>
114
+ </template>
115
+ ```
116
+
117
+ ```ts
118
+ // Angular
119
+ import { Component, inject } from '@angular/core';
120
+ import { PermissionsService, getAdvertisingId } from '@symbiote-native/tracking-transparency/angular';
121
+
122
+ @Component({
123
+ selector: 'TrackingScreen',
124
+ standalone: true,
125
+ template: `
126
+ <View>
127
+ <Text>{{ permissionStatus()?.status ?? 'checking…' }}</Text>
128
+ <Pressable (press)="permissionsService.request()">
129
+ <Text>Request tracking permission</Text>
130
+ </Pressable>
131
+ <Pressable (press)="logAdvertisingId()"><Text>Log advertising ID</Text></Pressable>
132
+ </View>
133
+ `,
134
+ })
135
+ export class TrackingScreen {
136
+ protected readonly permissionsService = inject(PermissionsService);
137
+ readonly permissionStatus = this.permissionsService.connect();
138
+
139
+ logAdvertisingId(): void {
140
+ console.log(getAdvertisingId());
141
+ }
142
+ }
143
+ ```
144
+
145
+ ## API
146
+
147
+ ```ts
148
+ getAdvertisingId(): string | null
149
+ // The advertising ID (Android AAID / iOS IDFA). Returns null on the iOS simulator, when tracking
150
+ // hasn't been authorized via requestTrackingPermissionsAsync, or when the user declined.
151
+
152
+ getTrackingPermissionsAsync(): Promise<PermissionResponse>
153
+ requestTrackingPermissionsAsync(): Promise<PermissionResponse>
154
+ // On Android and web these always resolve granted — there is no tracking-consent concept there.
155
+ // On iOS these drive the real ATT prompt / read its current status.
156
+
157
+ isAvailable(): boolean
158
+ // Whether the native module resolved at all.
159
+ ```
160
+
161
+ Plus `PermissionStatus`/`PermissionResponse`/`PermissionExpiration`/`PermissionHookOptions` —
162
+ re-exported verbatim from `expo-modules-core`, never the `expo` meta-package.
163
+
164
+ ```ts
165
+ import { getAdvertisingId, getTrackingPermissionsAsync } from '@symbiote-native/tracking-transparency';
166
+
167
+ // framework-scoped entry points re-export the same free functions, plus a usePermissions
168
+ // hook/composable/service:
169
+ import { usePermissions } from '@symbiote-native/tracking-transparency/react';
170
+ import { usePermissions } from '@symbiote-native/tracking-transparency/vue';
171
+ import { PermissionsService } from '@symbiote-native/tracking-transparency/angular';
172
+ ```
173
+
174
+ `usePermissions` (React/Vue) auto-fetches the current permission status on mount and returns a
175
+ `[status, request, get]` tuple (React) / `{ status, request, get }` object (Vue); Angular's
176
+ `PermissionsService.connect()` does the same, returning a readonly `Signal`:
177
+
178
+ ```ts
179
+ // React
180
+ const [status, request] = usePermissions();
181
+
182
+ // Vue
183
+ const { status, request } = usePermissions();
184
+
185
+ // Angular
186
+ readonly status = inject(PermissionsService).connect();
187
+ ```
188
+
189
+ ## Platform notes
190
+
191
+ - **Android and web always report granted.** There is no tracking-consent concept on either
192
+ platform — `getTrackingPermissionsAsync`/`requestTrackingPermissionsAsync` short-circuit to a
193
+ fixed granted response without ever calling the native module, matching upstream exactly.
194
+ - **`getAdvertisingId` returns `null` on the iOS Simulator, regardless of any settings** — there is
195
+ no real IDFA to read there. This is expected Apple Simulator behavior, not a bug in this wrapper.
196
+
197
+ ## Test it
198
+
199
+ No Fabric/Descriptor angle at all — tracking-transparency is a pure async-function + permission
200
+ surface, never a view. Tests inject a fake native-module object in place of the real
201
+ `requireNativeModule` resolution (`src/core/*.test.ts`, `src/{react,vue,angular}/**/*.test.{ts,tsx}`,
202
+ `vitest`), the same pattern `expo-tracking-transparency` itself uses upstream — no
203
+ `installFabric()` for the core layer, no ViewConfig. Native rendering itself is verified on-device
204
+ (see the parent [README](../../README.md) for the project's testing model).
205
+
206
+ ## Known gaps
207
+
208
+ - Not yet wired into any `examples/expo-*` canary.
209
+ - Tests exercise the JS layer only (fake native module, `vitest`) — no on-device/simulator
210
+ automated smoke test yet, only manual verification.
@@ -0,0 +1,2 @@
1
+ export { PermissionsService } from './services/permissions.service';
2
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // PermissionsService is the Angular-only lifecycle half; getAdvertisingId/isAvailable are
2
+ // stateless free functions, shared with React/Vue.
3
+ export { PermissionsService } from './services/permissions.service';
4
+ export * from '../core';
@@ -0,0 +1,8 @@
1
+ import { type Signal } from '@angular/core';
2
+ import { type PermissionResponse } from '../../../core';
3
+ export declare class PermissionsService {
4
+ private readonly status;
5
+ connect(): Signal<PermissionResponse | null>;
6
+ get(): Promise<PermissionResponse>;
7
+ request(): Promise<PermissionResponse>;
8
+ }
@@ -0,0 +1,78 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ // Angular twin of React's usePermissions hook and Vue's usePermissions composable.
36
+ // `connect()` auto-fetches once (no ongoing subscription to clean up, so no Injector/effect
37
+ // needed, unlike BatteryStateService) and returns a readonly signal; get()/request() are
38
+ // imperative one-shot methods.
39
+ //
40
+ // readonly status = inject(PermissionsService).connect();
41
+ // // template: {{ status()?.granted }}
42
+ import { Injectable, signal } from '@angular/core';
43
+ import { getTrackingPermissionsAsync, requestTrackingPermissionsAsync, } from '../../../core';
44
+ let PermissionsService = (() => {
45
+ let _classDecorators = [Injectable({ providedIn: 'root' })];
46
+ let _classDescriptor;
47
+ let _classExtraInitializers = [];
48
+ let _classThis;
49
+ var PermissionsService = class {
50
+ static { _classThis = this; }
51
+ static {
52
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
53
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
54
+ PermissionsService = _classThis = _classDescriptor.value;
55
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
56
+ __runInitializers(_classThis, _classExtraInitializers);
57
+ }
58
+ status = signal(null);
59
+ connect() {
60
+ if (this.status() === null) {
61
+ void this.get();
62
+ }
63
+ return this.status.asReadonly();
64
+ }
65
+ async get() {
66
+ const response = await getTrackingPermissionsAsync();
67
+ this.status.set(response);
68
+ return response;
69
+ }
70
+ async request() {
71
+ const response = await requestTrackingPermissionsAsync();
72
+ this.status.set(response);
73
+ return response;
74
+ }
75
+ };
76
+ return PermissionsService = _classThis;
77
+ })();
78
+ export { PermissionsService };
@@ -0,0 +1,2 @@
1
+ export { getAdvertisingId, requestTrackingPermissionsAsync, getTrackingPermissionsAsync, isAvailable, } from './tracking-transparency';
2
+ export { PermissionStatus, type PermissionResponse, type PermissionExpiration, type PermissionHookOptions, } from 'expo-modules-core';
@@ -0,0 +1,2 @@
1
+ export { getAdvertisingId, requestTrackingPermissionsAsync, getTrackingPermissionsAsync, isAvailable, } from './tracking-transparency';
2
+ export { PermissionStatus, } from 'expo-modules-core';
@@ -0,0 +1,7 @@
1
+ import type { PermissionResponse } from 'expo-modules-core';
2
+ export type INativeTrackingTransparencyModule = {
3
+ getAdvertisingId(): string | null;
4
+ requestPermissionsAsync?(): Promise<PermissionResponse>;
5
+ getPermissionsAsync?(): Promise<PermissionResponse>;
6
+ };
7
+ export declare const expoTrackingTransparency: INativeTrackingTransparencyModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_TRACKING_TRANSPARENCY_MODULE_NAME = 'ExpoTrackingTransparency';
3
+ export const expoTrackingTransparency = requireNativeModule(EXPO_TRACKING_TRANSPARENCY_MODULE_NAME);
@@ -0,0 +1,19 @@
1
+ import type { PermissionResponse } from 'expo-modules-core';
2
+ /**
3
+ * Gets the advertising ID, a UUID string intended only for advertising. Returns `null` on the
4
+ * iOS simulator, when tracking hasn't been authorized via `requestTrackingPermissionsAsync`, or
5
+ * when the user declined.
6
+ */
7
+ export declare function getAdvertisingId(): string | null;
8
+ /**
9
+ * Requests the user to authorize or deny access to app-related data that can be used for
10
+ * tracking. On Android and web this always resolves granted.
11
+ */
12
+ export declare function requestTrackingPermissionsAsync(): Promise<PermissionResponse>;
13
+ /**
14
+ * Checks whether the user has authorized the app to access tracking-related data. On Android and
15
+ * web this always resolves granted.
16
+ */
17
+ export declare function getTrackingPermissionsAsync(): Promise<PermissionResponse>;
18
+ /** Whether the tracking-transparency native module resolved at all. */
19
+ export declare function isAvailable(): boolean;
@@ -0,0 +1,58 @@
1
+ // Hand-ported from .vendors/expo/packages/expo-tracking-transparency/src/TrackingTransparency.ts
2
+ // (sdk-57, verified via `git -C .vendors/expo show origin/sdk-57:...` — the vendored working
3
+ // tree is on `main` and imports createPermissionHook/PermissionResponse from the `expo`
4
+ // meta-package instead of expo-modules-core). createPermissionHook/useTrackingPermissions are
5
+ // NOT ported here — that helper is React-only (built on useState/useEffect), and this repo's
6
+ // convention is for each adapter to hand-roll its own permission hook instead (react/hooks,
7
+ // vue/composables, angular/services), same as brightness/cellular already do.
8
+ import { Platform, PermissionStatus, UnavailabilityError } from 'expo-modules-core';
9
+ import { expoTrackingTransparency } from './native-module';
10
+ const NATIVE_MODULE_NAME = 'TrackingTransparency';
11
+ /**
12
+ * Gets the advertising ID, a UUID string intended only for advertising. Returns `null` on the
13
+ * iOS simulator, when tracking hasn't been authorized via `requestTrackingPermissionsAsync`, or
14
+ * when the user declined.
15
+ */
16
+ export function getAdvertisingId() {
17
+ const advertisingId = expoTrackingTransparency.getAdvertisingId();
18
+ if (advertisingId === '00000000-0000-0000-0000-000000000000') {
19
+ return null;
20
+ }
21
+ return advertisingId;
22
+ }
23
+ const androidAndWebPermissionsResponse = {
24
+ granted: true,
25
+ expires: 'never',
26
+ canAskAgain: true,
27
+ status: PermissionStatus.GRANTED,
28
+ };
29
+ /**
30
+ * Requests the user to authorize or deny access to app-related data that can be used for
31
+ * tracking. On Android and web this always resolves granted.
32
+ */
33
+ export async function requestTrackingPermissionsAsync() {
34
+ if (Platform.OS !== 'ios') {
35
+ return Promise.resolve(androidAndWebPermissionsResponse);
36
+ }
37
+ if (!expoTrackingTransparency.requestPermissionsAsync) {
38
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'requestPermissionsAsync');
39
+ }
40
+ return await expoTrackingTransparency.requestPermissionsAsync();
41
+ }
42
+ /**
43
+ * Checks whether the user has authorized the app to access tracking-related data. On Android and
44
+ * web this always resolves granted.
45
+ */
46
+ export async function getTrackingPermissionsAsync() {
47
+ if (Platform.OS !== 'ios') {
48
+ return Promise.resolve(androidAndWebPermissionsResponse);
49
+ }
50
+ if (!expoTrackingTransparency.getPermissionsAsync) {
51
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getPermissionsAsync');
52
+ }
53
+ return await expoTrackingTransparency.getPermissionsAsync();
54
+ }
55
+ /** Whether the tracking-transparency native module resolved at all. */
56
+ export function isAvailable() {
57
+ return Boolean(expoTrackingTransparency);
58
+ }
@@ -0,0 +1,6 @@
1
+ import { type PermissionResponse } from '../../../core';
2
+ export declare function usePermissions(): [
3
+ PermissionResponse | null,
4
+ () => Promise<PermissionResponse>,
5
+ () => Promise<PermissionResponse>
6
+ ];
@@ -0,0 +1,30 @@
1
+ // React lifecycle wiring over the framework-agnostic core (core/tracking-transparency.ts) —
2
+ // auto-fetches the current permission status on mount, then exposes get/request as imperative
3
+ // callbacks. Mirrors the shape @symbiote-native/cellular's own usePermissions ships in parallel,
4
+ // so both stay byte-for-byte consistent.
5
+ import { useCallback, useEffect, useRef, useState } from 'react';
6
+ import { getTrackingPermissionsAsync, requestTrackingPermissionsAsync, } from '../../../core';
7
+ export function usePermissions() {
8
+ const isMounted = useRef(true);
9
+ const [status, setStatus] = useState(null);
10
+ const getPermission = useCallback(async () => {
11
+ const response = await getTrackingPermissionsAsync();
12
+ if (isMounted.current)
13
+ setStatus(response);
14
+ return response;
15
+ }, []);
16
+ const requestPermission = useCallback(async () => {
17
+ const response = await requestTrackingPermissionsAsync();
18
+ if (isMounted.current)
19
+ setStatus(response);
20
+ return response;
21
+ }, []);
22
+ useEffect(() => {
23
+ isMounted.current = true;
24
+ void getPermission();
25
+ return () => {
26
+ isMounted.current = false;
27
+ };
28
+ }, [getPermission]);
29
+ return [status, requestPermission, getPermission];
30
+ }
@@ -0,0 +1,2 @@
1
+ export { usePermissions } from './hooks/use-permissions';
2
+ export * from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/tracking-transparency/react: the React entry over the framework-agnostic
2
+ // core. Only the permission surface gets a lifecycle hook (there is per-instance state to seed
3
+ // and refresh); getAdvertisingId/isAvailable are stateless, re-exported verbatim — mirrors the
4
+ // lifecycle-bucket naming convention of adapters/react/src/hooks.
5
+ export { usePermissions } from './hooks/use-permissions';
6
+ export * from '../core';
@@ -0,0 +1,7 @@
1
+ import { type Ref } from '@vue/runtime-core';
2
+ import { type PermissionResponse } from '../../../core';
3
+ export declare function usePermissions(): {
4
+ status: Ref<PermissionResponse | null>;
5
+ request: () => Promise<PermissionResponse>;
6
+ get: () => Promise<PermissionResponse>;
7
+ };
@@ -0,0 +1,23 @@
1
+ // Vue lifecycle wiring over the framework-agnostic core (core/tracking-transparency.ts) —
2
+ // auto-fetches the current permission status on mount (mirrors use-battery-level's
3
+ // onMounted/onUnmounted style, but there is no ongoing subscription to tear down here, just a
4
+ // one-shot get), then exposes get/request as imperative callbacks.
5
+ import { onMounted, ref } from '@vue/runtime-core';
6
+ import { getTrackingPermissionsAsync, requestTrackingPermissionsAsync, } from '../../../core';
7
+ export function usePermissions() {
8
+ const status = ref(null);
9
+ const get = async () => {
10
+ const response = await getTrackingPermissionsAsync();
11
+ status.value = response;
12
+ return response;
13
+ };
14
+ const request = async () => {
15
+ const response = await requestTrackingPermissionsAsync();
16
+ status.value = response;
17
+ return response;
18
+ };
19
+ onMounted(() => {
20
+ void get();
21
+ });
22
+ return { status, request, get };
23
+ }
@@ -0,0 +1,2 @@
1
+ export { usePermissions } from './composables/use-permissions';
2
+ export * from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/tracking-transparency/vue: the Vue entry over the framework-agnostic core.
2
+ // Same reasoning as the React entry — only the permission surface gets a lifecycle composable,
3
+ // every other export is a plain re-export — mirrors the lifecycle-bucket naming convention of
4
+ // adapters/vue/src/composables (never `hooks/`, that's React's term).
5
+ export { usePermissions } from './composables/use-permissions';
6
+ export * from '../core';
@@ -0,0 +1,2 @@
1
+ export { PermissionsService } from './services/permissions.service';
2
+ export * from '../core';
@@ -0,0 +1,5 @@
1
+ // PermissionsService is the Angular-only lifecycle half; getAdvertisingId/isAvailable are
2
+ // stateless free functions, shared with React/Vue.
3
+ export { PermissionsService } from './services/permissions.service';
4
+ export * from '../core';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,mDAAmD;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,cAAc,SAAS,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type Signal } from '@angular/core';
2
+ import { type PermissionResponse } from '../../../core';
3
+ import * as i0 from "@angular/core";
4
+ export declare class PermissionsService {
5
+ private readonly status;
6
+ connect(): Signal<PermissionResponse | null>;
7
+ get(): Promise<PermissionResponse>;
8
+ request(): Promise<PermissionResponse>;
9
+ static ɵfac: i0.ɵɵFactoryDeclaration<PermissionsService, never>;
10
+ static ɵprov: i0.ɵɵInjectableDeclaration<PermissionsService>;
11
+ }
@@ -0,0 +1,37 @@
1
+ // Angular twin of React's usePermissions hook and Vue's usePermissions composable.
2
+ // `connect()` auto-fetches once (no ongoing subscription to clean up, so no Injector/effect
3
+ // needed, unlike BatteryStateService) and returns a readonly signal; get()/request() are
4
+ // imperative one-shot methods.
5
+ //
6
+ // readonly status = inject(PermissionsService).connect();
7
+ // // template: {{ status()?.granted }}
8
+ import { Injectable, signal } from '@angular/core';
9
+ import { getTrackingPermissionsAsync, requestTrackingPermissionsAsync, } from '../../../core';
10
+ import * as i0 from "@angular/core";
11
+ export class PermissionsService {
12
+ status = signal(null, /* @ts-ignore */
13
+ ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
14
+ connect() {
15
+ if (this.status() === null) {
16
+ void this.get();
17
+ }
18
+ return this.status.asReadonly();
19
+ }
20
+ async get() {
21
+ const response = await getTrackingPermissionsAsync();
22
+ this.status.set(response);
23
+ return response;
24
+ }
25
+ async request() {
26
+ const response = await requestTrackingPermissionsAsync();
27
+ this.status.set(response);
28
+ return response;
29
+ }
30
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: PermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
31
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: PermissionsService, providedIn: 'root' });
32
+ }
33
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: PermissionsService, decorators: [{
34
+ type: Injectable,
35
+ args: [{ providedIn: 'root' }]
36
+ }] });
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/permissions.service/index.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,4FAA4F;AAC5F,yFAAyF;AACzF,+BAA+B;AAC/B,EAAE;AACF,4DAA4D;AAC5D,yCAAyC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,2BAA2B,EAC3B,+BAA+B,GAEhC,MAAM,eAAe,CAAC;;AAGvB,MAAM,OAAO,kBAAkB;IACZ,MAAM,GAAG,MAAM,CAA4B,IAAI;+EAAC,CAAC;IAElE,OAAO;QACL,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;YAC3B,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,GAAG;QACP,MAAM,QAAQ,GAAG,MAAM,2BAA2B,EAAE,CAAC;QACrD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,QAAQ,GAAG,MAAM,+BAA+B,EAAE,CAAC;QACzD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;uGApBU,kBAAkB;2GAAlB,kBAAkB,cADL,MAAM;;2FACnB,kBAAkB;kBAD9B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
@@ -0,0 +1,2 @@
1
+ export { getAdvertisingId, requestTrackingPermissionsAsync, getTrackingPermissionsAsync, isAvailable, } from './tracking-transparency';
2
+ export { PermissionStatus, type PermissionResponse, type PermissionExpiration, type PermissionHookOptions, } from 'expo-modules-core';
@@ -0,0 +1,3 @@
1
+ export { getAdvertisingId, requestTrackingPermissionsAsync, getTrackingPermissionsAsync, isAvailable, } from './tracking-transparency';
2
+ export { PermissionStatus, } from 'expo-modules-core';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,+BAA+B,EAC/B,2BAA2B,EAC3B,WAAW,GACZ,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,gBAAgB,GAIjB,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { PermissionResponse } from 'expo-modules-core';
2
+ export type INativeTrackingTransparencyModule = {
3
+ getAdvertisingId(): string | null;
4
+ requestPermissionsAsync?(): Promise<PermissionResponse>;
5
+ getPermissionsAsync?(): Promise<PermissionResponse>;
6
+ };
7
+ export declare const expoTrackingTransparency: INativeTrackingTransparencyModule;
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_TRACKING_TRANSPARENCY_MODULE_NAME = 'ExpoTrackingTransparency';
3
+ export const expoTrackingTransparency = requireNativeModule(EXPO_TRACKING_TRANSPARENCY_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;AAGxD,MAAM,sCAAsC,GAAG,0BAA0B,CAAC;AAe1E,MAAM,CAAC,MAAM,wBAAwB,GAAG,mBAAmB,CACzD,sCAAsC,CACvC,CAAC"}