@symbiote-native/tracking-transparency 0.2.0 → 1.0.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/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  A wrapper package for [SymbioteNative](../../README.md) that makes
4
4
  [`expo-tracking-transparency`](https://github.com/expo/expo/tree/main/packages/expo-tracking-transparency)
5
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
6
+ — usable from **every** adapter, React, Vue, Svelte, Solid, and Angular, not just React. Like
7
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,
8
+ async functions; only the permission surface gets its own per-adapter lifecycle wrapper,
9
9
  mirroring upstream's own `usePermissions`. Built the same way as
10
10
  [`@symbiote-native/local-auth`](../local-auth), [`@symbiote-native/battery`](../battery), and
11
11
  [`@symbiote-native/brightness`](../brightness) — an `expo-modules-core`-based wrapper (see the
@@ -34,13 +34,13 @@ Unlike a plain RN native module, `expo-tracking-transparency`'s native code is d
34
34
  this repo use — this needs wiring into the native host app **once**, covering this package and
35
35
  every other `expo-modules-core` package with zero further changes:
36
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) |
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
44
 
45
45
  Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
46
46
  peer-dependency exclusion list — live in the `symbiote-expo-native-module` skill. The Android
@@ -56,13 +56,16 @@ src/core/ getAdvertisingId, get/requestTrackingPermissionsAsync,
56
56
  expo-modules-core's requireNativeModule.
57
57
  src/react/hooks/ @symbiote-native/tracking-transparency/react — usePermissions
58
58
  src/vue/composables/ @symbiote-native/tracking-transparency/vue — usePermissions (same name)
59
+ src/svelte/runes/ @symbiote-native/tracking-transparency/svelte — usePermissions (same name)
60
+ src/solid/primitives/ @symbiote-native/tracking-transparency/solid — createPermissions
61
+ (Solid reserves `use*` for consuming existing state)
59
62
  src/angular/services/ @symbiote-native/tracking-transparency/angular — PermissionsService
60
63
  ```
61
64
 
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.
65
+ Each adapter's wrapper puts the same one-shot `getTrackingPermissionsAsync`/
66
+ `requestTrackingPermissionsAsync` pair in its own lifecycle idiom (auto-fetch when the wrapper is
67
+ created, exposed as a tuple/object/signal/accessor). `getAdvertisingId`/`isAvailable` are stateless
68
+ free-function re-exports, written once in `core/` and shared verbatim by every adapter.
66
69
 
67
70
  Upstream's own `createPermissionHook`/`useTrackingPermissions` is **not** ported — that helper is
68
71
  React-only (built on `useState`/`useEffect`), and this repo's convention is for each adapter to
@@ -98,7 +101,8 @@ function TrackingScreen() {
98
101
  import { usePermissions } from '@symbiote-native/tracking-transparency/vue';
99
102
  import { getAdvertisingId } from '@symbiote-native/tracking-transparency';
100
103
 
101
- const { status: permissionStatus, request: requestPermission } = usePermissions();
104
+ const { status: permissionStatus, request: requestPermission } =
105
+ usePermissions();
102
106
  </script>
103
107
 
104
108
  <template>
@@ -117,7 +121,10 @@ const { status: permissionStatus, request: requestPermission } = usePermissions(
117
121
  ```ts
118
122
  // Angular
119
123
  import { Component, inject } from '@angular/core';
120
- import { PermissionsService, getAdvertisingId } from '@symbiote-native/tracking-transparency/angular';
124
+ import {
125
+ PermissionsService,
126
+ getAdvertisingId,
127
+ } from '@symbiote-native/tracking-transparency/angular';
121
128
 
122
129
  @Component({
123
130
  selector: 'TrackingScreen',
@@ -128,7 +135,9 @@ import { PermissionsService, getAdvertisingId } from '@symbiote-native/tracking-
128
135
  <Pressable (press)="permissionsService.request()">
129
136
  <Text>Request tracking permission</Text>
130
137
  </Pressable>
131
- <Pressable (press)="logAdvertisingId()"><Text>Log advertising ID</Text></Pressable>
138
+ <Pressable (press)="logAdvertisingId()"
139
+ ><Text>Log advertising ID</Text></Pressable
140
+ >
132
141
  </View>
133
142
  `,
134
143
  })
@@ -142,10 +151,58 @@ export class TrackingScreen {
142
151
  }
143
152
  ```
144
153
 
145
- All three examples mirror the real canary demo screens —
154
+ ```svelte
155
+ <!-- Svelte — the rune hands back a boxed object whose `status` is a getter; destructuring it
156
+ would freeze the value at its initial null, since Svelte 5 reactivity is lexically scoped. -->
157
+ <script lang="ts">
158
+ import {
159
+ getAdvertisingId,
160
+ usePermissions,
161
+ } from '@symbiote-native/tracking-transparency/svelte';
162
+
163
+ const permissions = usePermissions();
164
+ </script>
165
+
166
+ <View>
167
+ <Text>{permissions.status?.status ?? 'checking…'}</Text>
168
+ <Pressable onPress={() => permissions.request()}>
169
+ <Text>Request tracking permission</Text>
170
+ </Pressable>
171
+ <Pressable onPress={() => console.log(getAdvertisingId())}>
172
+ <Text>Log advertising ID</Text>
173
+ </Pressable>
174
+ </View>
175
+ ```
176
+
177
+ ```tsx
178
+ // Solid — the accessors are CALLED; a Solid component body runs once, so a snapshot would freeze.
179
+ import { createPermissions } from '@symbiote-native/tracking-transparency/solid';
180
+ import { getAdvertisingId } from '@symbiote-native/tracking-transparency';
181
+
182
+ function TrackingScreen() {
183
+ const { status: permissionStatus, request: requestPermission } =
184
+ createPermissions();
185
+
186
+ return (
187
+ <View>
188
+ <Text>{permissionStatus()?.status ?? 'checking…'}</Text>
189
+ <Pressable onPress={() => void requestPermission()}>
190
+ <Text>Request tracking permission</Text>
191
+ </Pressable>
192
+ <Pressable onPress={() => console.log(getAdvertisingId())}>
193
+ <Text>Log advertising ID</Text>
194
+ </Pressable>
195
+ </View>
196
+ );
197
+ }
198
+ ```
199
+
200
+ The examples above mirror the real canary demo screens —
146
201
  `examples/expo-react/screens/TrackingTransparencyScreen.tsx`,
147
202
  `examples/expo-vue-sfc/screens/TrackingTransparencyScreen.vue`,
148
203
  `examples/expo-vue-tsx/screens/TrackingTransparencyScreen.tsx`,
204
+ `examples/expo-svelte/screens/TrackingTransparencyScreen.svelte`,
205
+ `examples/expo-solid/screens/TrackingTransparencyScreen.tsx`,
149
206
  `examples/expo-angular/src/screens/TrackingTransparencyScreen.ts`.
150
207
 
151
208
  ## API
@@ -168,18 +225,27 @@ Plus `PermissionStatus`/`PermissionResponse`/`PermissionExpiration`/`PermissionH
168
225
  re-exported verbatim from `expo-modules-core`, never the `expo` meta-package.
169
226
 
170
227
  ```ts
171
- import { getAdvertisingId, getTrackingPermissionsAsync } from '@symbiote-native/tracking-transparency';
228
+ import {
229
+ getAdvertisingId,
230
+ getTrackingPermissionsAsync,
231
+ } from '@symbiote-native/tracking-transparency';
172
232
 
173
- // framework-scoped entry points re-export the same free functions, plus a usePermissions
174
- // hook/composable/service:
233
+ // framework-scoped entry points re-export the same free functions, plus a per-adapter
234
+ // permission wrapper:
175
235
  import { usePermissions } from '@symbiote-native/tracking-transparency/react';
176
236
  import { usePermissions } from '@symbiote-native/tracking-transparency/vue';
237
+ import { usePermissions } from '@symbiote-native/tracking-transparency/svelte';
238
+ import { createPermissions } from '@symbiote-native/tracking-transparency/solid';
177
239
  import { PermissionsService } from '@symbiote-native/tracking-transparency/angular';
178
240
  ```
179
241
 
180
- `usePermissions` (React/Vue) auto-fetches the current permission status on mount and returns a
181
- `[status, request, get]` tuple (React) / `{ status, request, get }` object (Vue); Angular's
182
- `PermissionsService.connect()` does the same, returning a readonly `Signal`:
242
+ `usePermissions` auto-fetches the current permission status on mount and returns a
243
+ `[status, request, get, error]` tuple (React) / `{ status, error, request, get }` object (Vue,
244
+ destructurable — `status` is a `Ref`). Solid's `createPermissions` returns the same shape with
245
+ `Accessor`s instead of `Ref`s, and Angular's `PermissionsService.connect()` returns a readonly
246
+ `Signal`. **Svelte's rune returns a boxed object whose `status` is a getter — do not destructure
247
+ it**, or the value freezes at its initial `null` (Svelte 5 reactivity is lexically scoped to the
248
+ property access):
183
249
 
184
250
  ```ts
185
251
  // React
@@ -188,6 +254,13 @@ const [status, request] = usePermissions();
188
254
  // Vue
189
255
  const { status, request } = usePermissions();
190
256
 
257
+ // Svelte — read permissions.status, never destructure it
258
+ const permissions = usePermissions();
259
+ permissions.status;
260
+
261
+ // Solid — status is an accessor, called at the read site
262
+ const { status, request } = createPermissions();
263
+
191
264
  // Angular
192
265
  readonly status = inject(PermissionsService).connect();
193
266
  ```
@@ -204,7 +277,7 @@ readonly status = inject(PermissionsService).connect();
204
277
 
205
278
  No Fabric/Descriptor angle at all — tracking-transparency is a pure async-function + permission
206
279
  surface, never a view. Tests inject a fake native-module object in place of the real
207
- `requireNativeModule` resolution (`src/core/*.test.ts`, `src/{react,vue,angular}/**/*.test.{ts,tsx}`,
280
+ `requireNativeModule` resolution (`src/core/*.test.ts`, `src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`,
208
281
  `vitest`), the same pattern `expo-tracking-transparency` itself uses upstream — no
209
282
  `installFabric()` for the core layer, no ViewConfig. Native rendering itself is verified on-device
210
283
  (see the parent [README](../../README.md) for the project's testing model).
@@ -5,7 +5,7 @@
5
5
  // NOT ported here — that helper is React-only (built on useState/useEffect), and this repo's
6
6
  // convention is for each adapter to hand-roll its own permission hook instead (react/hooks,
7
7
  // vue/composables, angular/services), same as brightness/cellular already do.
8
- import { Platform, PermissionStatus, UnavailabilityError } from 'expo-modules-core';
8
+ import { Platform, PermissionStatus, UnavailabilityError, } from 'expo-modules-core';
9
9
  import { expoTrackingTransparency } from './native-module.js';
10
10
  const NATIVE_MODULE_NAME = 'TrackingTransparency';
11
11
  /**
@@ -0,0 +1,2 @@
1
+ export { createPermissions } from './primitives/create-permissions';
2
+ export * from '../core';
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/tracking-transparency/solid: the Solid entry over the framework-agnostic core.
2
+ // Same reasoning as the React, Vue and Svelte entries — only the permission surface gets a
3
+ // lifecycle primitive, every other export is a plain re-export — mirrors the lifecycle-bucket
4
+ // naming convention of adapters/solid/src/primitives (never `hooks/`/`composables/`/`runes/`,
5
+ // those are React's, Vue's and Svelte's terms).
6
+ export { createPermissions } from './primitives/create-permissions.js';
7
+ export * from '../core/index.js';
@@ -0,0 +1,8 @@
1
+ import { type Accessor } from 'solid-js';
2
+ import { type PermissionResponse } from '../../core';
3
+ export declare function createPermissions(): {
4
+ status: Accessor<PermissionResponse | null>;
5
+ error: Accessor<Error | null>;
6
+ request: () => Promise<PermissionResponse>;
7
+ get: () => Promise<PermissionResponse>;
8
+ };
@@ -0,0 +1,41 @@
1
+ // createPermissions — the Solid twin of React's `usePermissions` hook, Vue's composable and
2
+ // Svelte's rune, over the framework-agnostic core (core/tracking-transparency.ts). Auto-fetches
3
+ // the current permission status, then exposes get/request as imperative callbacks.
4
+ //
5
+ // `primitives/` and `create*`, never `hooks/`+`use*`: Solid's ecosystem calls a composable
6
+ // reactive function a PRIMITIVE and reserves `use*` for consuming something that already exists.
7
+ // Full rationale in adapters/solid/src/primitives/create-color-scheme.ts.
8
+ //
9
+ // Returns ACCESSORS, never snapshots: a Solid component body runs ONCE, so returned values would
10
+ // pin the caller to the null pre-fetch state forever.
11
+ //
12
+ // The auto-fetch fires from the primitive body rather than a mount effect — the shape
13
+ // `createResource` uses, and the reason this primitive needs no lifecycle hook at all. Its failure
14
+ // lands in `error` instead of escaping as an unhandled rejection: a null status with a non-null
15
+ // error is how a consumer tells "the fetch failed" from "not fetched yet". get()/request() still
16
+ // reject to their direct caller — only the automatic call is made safe.
17
+ //
18
+ // No isMounted guard around the late write, unlike React's hook: once the owning root is disposed
19
+ // nothing is subscribed to these signals any more, so a resolution arriving afterwards is inert.
20
+ import { createSignal } from 'solid-js';
21
+ import { getTrackingPermissionsAsync, requestTrackingPermissionsAsync, } from '../../core/index.js';
22
+ export function createPermissions() {
23
+ const [status, setStatus] = createSignal(null);
24
+ const [error, setError] = createSignal(null);
25
+ const get = async () => {
26
+ const response = await getTrackingPermissionsAsync();
27
+ setStatus(response);
28
+ setError(null);
29
+ return response;
30
+ };
31
+ const request = async () => {
32
+ const response = await requestTrackingPermissionsAsync();
33
+ setStatus(response);
34
+ setError(null);
35
+ return response;
36
+ };
37
+ get().catch((cause) => {
38
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
39
+ });
40
+ return { status, error, request, get };
41
+ }
@@ -1 +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,EAAE;AACF,gGAAgG;AAChG,kGAAkG;AAClG,mGAAmG;AACnG,wCAAwC;AACxC,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;IACjD,UAAU,GAAG,MAAM,CAAe,IAAI;mFAAC,CAAC;IACjD,kBAAkB,GAAG,KAAK,CAAC;IAEnC,gGAAgG;IACvF,KAAK,GAAyB,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;IAEpE,OAAO;QACL,wFAAwF;QACxF,8FAA8F;QAC9F,8FAA8F;QAC9F,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAC/B,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBAClC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjF,CAAC,CAAC,CAAC;QACL,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,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,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,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;uGAjCU,kBAAkB;2GAAlB,kBAAkB,cADL,MAAM;;2FACnB,kBAAkB;kBAD9B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
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,EAAE;AACF,gGAAgG;AAChG,kGAAkG;AAClG,mGAAmG;AACnG,wCAAwC;AACxC,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;IACjD,UAAU,GAAG,MAAM,CAAe,IAAI;mFAAC,CAAC;IACjD,kBAAkB,GAAG,KAAK,CAAC;IAEnC,gGAAgG;IACvF,KAAK,GAAyB,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;IAEpE,OAAO;QACL,wFAAwF;QACxF,8FAA8F;QAC9F,8FAA8F;QAC9F,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAC/B,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBAClC,IAAI,CAAC,UAAU,CAAC,GAAG,CACjB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,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,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,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,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;uGAnCU,kBAAkB;2GAAlB,kBAAkB,cADL,MAAM;;2FACnB,kBAAkB;kBAD9B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
@@ -1 +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"}
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,GACnC,mBAAmB,CACjB,sCAAsC,CACvC,CAAC"}
@@ -5,7 +5,7 @@
5
5
  // NOT ported here — that helper is React-only (built on useState/useEffect), and this repo's
6
6
  // convention is for each adapter to hand-roll its own permission hook instead (react/hooks,
7
7
  // vue/composables, angular/services), same as brightness/cellular already do.
8
- import { Platform, PermissionStatus, UnavailabilityError } from 'expo-modules-core';
8
+ import { Platform, PermissionStatus, UnavailabilityError, } from 'expo-modules-core';
9
9
  import { expoTrackingTransparency } from './native-module';
10
10
  const NATIVE_MODULE_NAME = 'TrackingTransparency';
11
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tracking-transparency.js","sourceRoot":"","sources":["../../src/core/tracking-transparency.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,6FAA6F;AAC7F,wFAAwF;AACxF,8FAA8F;AAC9F,6FAA6F;AAC7F,4FAA4F;AAC5F,8EAA8E;AAC9E,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,MAAM,kBAAkB,GAAG,sBAAsB,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,aAAa,GAAG,wBAAwB,CAAC,gBAAgB,EAAE,CAAC;IAClE,IAAI,aAAa,KAAK,sCAAsC,EAAE,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,MAAM,gCAAgC,GAAuB;IAC3D,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,OAAO;IAChB,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,gBAAgB,CAAC,OAAO;CACjC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,+BAA+B;IACnD,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC,wBAAwB,CAAC,uBAAuB,EAAE,CAAC;QACtD,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,yBAAyB,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,MAAM,wBAAwB,CAAC,uBAAuB,EAAE,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B;IAC/C,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC,wBAAwB,CAAC,mBAAmB,EAAE,CAAC;QAClD,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,MAAM,wBAAwB,CAAC,mBAAmB,EAAE,CAAC;AAC9D,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,WAAW;IACzB,OAAO,OAAO,CAAC,wBAAwB,CAAC,CAAC;AAC3C,CAAC"}
1
+ {"version":3,"file":"tracking-transparency.js","sourceRoot":"","sources":["../../src/core/tracking-transparency.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,6FAA6F;AAC7F,wFAAwF;AACxF,8FAA8F;AAC9F,6FAA6F;AAC7F,4FAA4F;AAC5F,8EAA8E;AAC9E,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,MAAM,kBAAkB,GAAG,sBAAsB,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,aAAa,GAAG,wBAAwB,CAAC,gBAAgB,EAAE,CAAC;IAClE,IAAI,aAAa,KAAK,sCAAsC,EAAE,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,MAAM,gCAAgC,GAAuB;IAC3D,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,OAAO;IAChB,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,gBAAgB,CAAC,OAAO;CACjC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,+BAA+B;IACnD,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC,wBAAwB,CAAC,uBAAuB,EAAE,CAAC;QACtD,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,EAClB,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,wBAAwB,CAAC,uBAAuB,EAAE,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B;IAC/C,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC,wBAAwB,CAAC,mBAAmB,EAAE,CAAC;QAClD,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,MAAM,wBAAwB,CAAC,mBAAmB,EAAE,CAAC;AAC9D,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,WAAW;IACzB,OAAO,OAAO,CAAC,wBAAwB,CAAC,CAAC;AAC3C,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,18 @@
1
1
  {
2
2
  "name": "@symbiote-native/tracking-transparency",
3
- "version": "0.2.0",
4
- "description": "expo-tracking-transparency wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. iOS App Tracking Transparency prompt (permission get/request, always-granted on Android/web) plus the advertising-ID getter.",
3
+ "version": "1.0.0",
4
+ "description": "expo-tracking-transparency wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. iOS App Tracking Transparency prompt (permission get/request, always-granted on Android/web) plus the advertising-ID getter.",
5
+ "keywords": [
6
+ "react-native",
7
+ "symbiote-native",
8
+ "react",
9
+ "vue",
10
+ "svelte",
11
+ "solid",
12
+ "angular",
13
+ "app-tracking-transparency",
14
+ "idfa"
15
+ ],
5
16
  "license": "MIT",
6
17
  "repository": {
7
18
  "type": "git",
@@ -34,6 +45,10 @@
34
45
  "types": "./build/svelte/index.d.ts",
35
46
  "default": "./build/svelte/index.js"
36
47
  },
48
+ "./solid": {
49
+ "types": "./build/solid/index.d.ts",
50
+ "default": "./build/solid/index.js"
51
+ },
37
52
  "./angular": {
38
53
  "types": "./build-ngc/angular/index.d.ts",
39
54
  "react-native": "./build-ngc/angular/index.js",
@@ -57,17 +72,19 @@
57
72
  "expo-modules-core": "57.0.5"
58
73
  },
59
74
  "peerDependencies": {
60
- "@symbiote-native/angular": ">=0.6.2",
61
- "@symbiote-native/engine": ">=0.1.7",
62
- "@symbiote-native/react": ">=0.2.8",
63
- "@symbiote-native/svelte": ">=0.1.0",
64
- "@symbiote-native/vue": ">=0.3.8",
65
75
  "@angular/core": ">=20",
66
76
  "@vue/runtime-core": "^3.5.13",
67
77
  "react": ">=19.0.0",
68
78
  "react-native": ">=0.86",
79
+ "solid-js": ">=1.9.0",
69
80
  "svelte": ">=5.56.0",
70
- "vue": ">=3.5.0"
81
+ "vue": ">=3.5.0",
82
+ "@symbiote-native/angular": "^1.0.0",
83
+ "@symbiote-native/engine": "^0.4.0",
84
+ "@symbiote-native/react": "^1.0.0",
85
+ "@symbiote-native/solid": "^1.0.0",
86
+ "@symbiote-native/vue": "^1.0.0",
87
+ "@symbiote-native/svelte": "^1.0.0"
71
88
  },
72
89
  "peerDependenciesMeta": {
73
90
  "@symbiote-native/angular": {
@@ -76,6 +93,9 @@
76
93
  "@symbiote-native/react": {
77
94
  "optional": true
78
95
  },
96
+ "@symbiote-native/solid": {
97
+ "optional": true
98
+ },
79
99
  "@symbiote-native/svelte": {
80
100
  "optional": true
81
101
  },
@@ -91,6 +111,9 @@
91
111
  "react": {
92
112
  "optional": true
93
113
  },
114
+ "solid-js": {
115
+ "optional": true
116
+ },
94
117
  "svelte": {
95
118
  "optional": true
96
119
  },
@@ -106,14 +129,16 @@
106
129
  "@types/react": "^19.2.0",
107
130
  "@vue/runtime-core": "^3.5.13",
108
131
  "react": "19.2.3",
132
+ "solid-js": "^1.9.14",
109
133
  "svelte": "^5.56.0",
110
134
  "typescript": "~6.0.0",
111
- "@symbiote-native/angular": "0.7.0",
112
- "@symbiote-native/engine": "0.2.0",
113
- "@symbiote-native/react": "0.3.0",
114
- "@symbiote-native/svelte": "0.2.0",
115
- "@symbiote-native/test-utils": "0.1.6",
116
- "@symbiote-native/vue": "0.4.0"
135
+ "@symbiote-native/angular": "1.0.0",
136
+ "@symbiote-native/engine": "0.4.0",
137
+ "@symbiote-native/react": "1.0.0",
138
+ "@symbiote-native/solid": "1.0.0",
139
+ "@symbiote-native/svelte": "1.0.0",
140
+ "@symbiote-native/test-utils": "0.3.0",
141
+ "@symbiote-native/vue": "1.0.0"
117
142
  },
118
143
  "scripts": {
119
144
  "typecheck": "tsc --build",
@@ -33,7 +33,9 @@ export class PermissionsService {
33
33
  if (!this.isAutoFetchStarted) {
34
34
  this.isAutoFetchStarted = true;
35
35
  this.get().catch((cause: unknown) => {
36
- this.fetchError.set(cause instanceof Error ? cause : new Error(String(cause)));
36
+ this.fetchError.set(
37
+ cause instanceof Error ? cause : new Error(String(cause)),
38
+ );
37
39
  });
38
40
  }
39
41
  return this.status.asReadonly();
@@ -16,6 +16,7 @@ export type INativeTrackingTransparencyModule = {
16
16
  getPermissionsAsync?(): Promise<PermissionResponse>;
17
17
  };
18
18
 
19
- export const expoTrackingTransparency = requireNativeModule<INativeTrackingTransparencyModule>(
20
- EXPO_TRACKING_TRANSPARENCY_MODULE_NAME,
21
- );
19
+ export const expoTrackingTransparency =
20
+ requireNativeModule<INativeTrackingTransparencyModule>(
21
+ EXPO_TRACKING_TRANSPARENCY_MODULE_NAME,
22
+ );
@@ -5,7 +5,11 @@
5
5
  // NOT ported here — that helper is React-only (built on useState/useEffect), and this repo's
6
6
  // convention is for each adapter to hand-roll its own permission hook instead (react/hooks,
7
7
  // vue/composables, angular/services), same as brightness/cellular already do.
8
- import { Platform, PermissionStatus, UnavailabilityError } from 'expo-modules-core';
8
+ import {
9
+ Platform,
10
+ PermissionStatus,
11
+ UnavailabilityError,
12
+ } from 'expo-modules-core';
9
13
  import type { PermissionResponse } from 'expo-modules-core';
10
14
 
11
15
  import { expoTrackingTransparency } from './native-module';
@@ -41,7 +45,10 @@ export async function requestTrackingPermissionsAsync(): Promise<PermissionRespo
41
45
  return Promise.resolve(androidAndWebPermissionsResponse);
42
46
  }
43
47
  if (!expoTrackingTransparency.requestPermissionsAsync) {
44
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'requestPermissionsAsync');
48
+ throw new UnavailabilityError(
49
+ NATIVE_MODULE_NAME,
50
+ 'requestPermissionsAsync',
51
+ );
45
52
  }
46
53
  return await expoTrackingTransparency.requestPermissionsAsync();
47
54
  }
@@ -45,7 +45,8 @@ export function usePermissions(): [
45
45
  useEffect(() => {
46
46
  isMounted.current = true;
47
47
  getPermission().catch((cause: unknown) => {
48
- if (isMounted.current) setError(cause instanceof Error ? cause : new Error(String(cause)));
48
+ if (isMounted.current)
49
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
49
50
  });
50
51
  return () => {
51
52
  isMounted.current = false;
@@ -0,0 +1,8 @@
1
+ // @symbiote-native/tracking-transparency/solid: the Solid entry over the framework-agnostic core.
2
+ // Same reasoning as the React, Vue and Svelte entries — only the permission surface gets a
3
+ // lifecycle primitive, every other export is a plain re-export — mirrors the lifecycle-bucket
4
+ // naming convention of adapters/solid/src/primitives (never `hooks/`/`composables/`/`runes/`,
5
+ // those are React's, Vue's and Svelte's terms).
6
+
7
+ export { createPermissions } from './primitives/create-permissions';
8
+ export * from '../core';
@@ -0,0 +1,56 @@
1
+ // createPermissions — the Solid twin of React's `usePermissions` hook, Vue's composable and
2
+ // Svelte's rune, over the framework-agnostic core (core/tracking-transparency.ts). Auto-fetches
3
+ // the current permission status, then exposes get/request as imperative callbacks.
4
+ //
5
+ // `primitives/` and `create*`, never `hooks/`+`use*`: Solid's ecosystem calls a composable
6
+ // reactive function a PRIMITIVE and reserves `use*` for consuming something that already exists.
7
+ // Full rationale in adapters/solid/src/primitives/create-color-scheme.ts.
8
+ //
9
+ // Returns ACCESSORS, never snapshots: a Solid component body runs ONCE, so returned values would
10
+ // pin the caller to the null pre-fetch state forever.
11
+ //
12
+ // The auto-fetch fires from the primitive body rather than a mount effect — the shape
13
+ // `createResource` uses, and the reason this primitive needs no lifecycle hook at all. Its failure
14
+ // lands in `error` instead of escaping as an unhandled rejection: a null status with a non-null
15
+ // error is how a consumer tells "the fetch failed" from "not fetched yet". get()/request() still
16
+ // reject to their direct caller — only the automatic call is made safe.
17
+ //
18
+ // No isMounted guard around the late write, unlike React's hook: once the owning root is disposed
19
+ // nothing is subscribed to these signals any more, so a resolution arriving afterwards is inert.
20
+
21
+ import { createSignal, type Accessor } from 'solid-js';
22
+ import {
23
+ getTrackingPermissionsAsync,
24
+ requestTrackingPermissionsAsync,
25
+ type PermissionResponse,
26
+ } from '../../core';
27
+
28
+ export function createPermissions(): {
29
+ status: Accessor<PermissionResponse | null>;
30
+ error: Accessor<Error | null>;
31
+ request: () => Promise<PermissionResponse>;
32
+ get: () => Promise<PermissionResponse>;
33
+ } {
34
+ const [status, setStatus] = createSignal<PermissionResponse | null>(null);
35
+ const [error, setError] = createSignal<Error | null>(null);
36
+
37
+ const get = async (): Promise<PermissionResponse> => {
38
+ const response = await getTrackingPermissionsAsync();
39
+ setStatus(response);
40
+ setError(null);
41
+ return response;
42
+ };
43
+
44
+ const request = async (): Promise<PermissionResponse> => {
45
+ const response = await requestTrackingPermissionsAsync();
46
+ setStatus(response);
47
+ setError(null);
48
+ return response;
49
+ };
50
+
51
+ get().catch((cause: unknown) => {
52
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
53
+ });
54
+
55
+ return { status, error, request, get };
56
+ }