@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.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/build/angular/index.d.ts +2 -0
- package/build/angular/index.js +4 -0
- package/build/angular/services/permissions.service/index.d.ts +8 -0
- package/build/angular/services/permissions.service/index.js +78 -0
- package/build/core/index.d.ts +2 -0
- package/build/core/index.js +2 -0
- package/build/core/native-module.d.ts +7 -0
- package/build/core/native-module.js +3 -0
- package/build/core/tracking-transparency.d.ts +19 -0
- package/build/core/tracking-transparency.js +58 -0
- package/build/react/hooks/use-permissions/index.d.ts +6 -0
- package/build/react/hooks/use-permissions/index.js +30 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.js +6 -0
- package/build/vue/composables/use-permissions/index.d.ts +7 -0
- package/build/vue/composables/use-permissions/index.js +23 -0
- package/build/vue/index.d.ts +2 -0
- package/build/vue/index.js +6 -0
- package/build-ngc/angular/index.d.ts +2 -0
- package/build-ngc/angular/index.js +5 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/services/permissions.service/index.d.ts +11 -0
- package/build-ngc/angular/services/permissions.service/index.js +37 -0
- package/build-ngc/angular/services/permissions.service/index.js.map +1 -0
- package/build-ngc/core/index.d.ts +2 -0
- package/build-ngc/core/index.js +3 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +7 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/tracking-transparency.d.ts +19 -0
- package/build-ngc/core/tracking-transparency.js +59 -0
- package/build-ngc/core/tracking-transparency.js.map +1 -0
- package/native-link.json +17 -0
- package/package.json +107 -0
- package/src/angular/index.ts +4 -0
- package/src/angular/services/permissions.service/index.ts +37 -0
- package/src/angular/services/permissions.service/permissions.service.test.ts +107 -0
- package/src/core/index.ts +12 -0
- package/src/core/native-module.ts +21 -0
- package/src/core/tracking-transparency.test.ts +137 -0
- package/src/core/tracking-transparency.ts +66 -0
- package/src/react/hooks/use-permissions/index.ts +41 -0
- package/src/react/hooks/use-permissions/use-permissions.test.tsx +100 -0
- package/src/react/index.ts +7 -0
- package/src/vue/composables/use-permissions/index.ts +36 -0
- package/src/vue/composables/use-permissions/use-permissions.test.ts +99 -0
- package/src/vue/index.ts +7 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Co-located React-driven test (ADR 0025) for usePermissions. See battery's
|
|
2
|
+
// use-battery-state.test.tsx for the shared rationale (mocks `core`, not expo-modules-core
|
|
3
|
+
// internals).
|
|
4
|
+
|
|
5
|
+
import { createElement, type ReactElement } from 'react';
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { mount, unmount, View } from '@symbiote-native/react';
|
|
8
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
9
|
+
import { usePermissions } from './index';
|
|
10
|
+
import { PermissionStatus, type PermissionResponse } from '../../../core';
|
|
11
|
+
|
|
12
|
+
const { getTrackingPermissionsAsync, requestTrackingPermissionsAsync } = vi.hoisted(() => ({
|
|
13
|
+
getTrackingPermissionsAsync: vi.fn(),
|
|
14
|
+
requestTrackingPermissionsAsync: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Same enum-shaped-object mock trick packages/battery/src/react/hooks/use-battery-state's test
|
|
18
|
+
// uses for BatteryState — the mock factory's runtime object stands in for the real enum
|
|
19
|
+
// (imported here purely for its type), so `PermissionStatus.GRANTED` still type-checks.
|
|
20
|
+
vi.mock('../../../core', () => ({
|
|
21
|
+
getTrackingPermissionsAsync,
|
|
22
|
+
requestTrackingPermissionsAsync,
|
|
23
|
+
PermissionStatus: { GRANTED: 'granted', DENIED: 'denied', UNDETERMINED: 'undetermined' },
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const ROOT_TAG = 954;
|
|
27
|
+
|
|
28
|
+
const GRANTED: PermissionResponse = {
|
|
29
|
+
status: PermissionStatus.GRANTED,
|
|
30
|
+
granted: true,
|
|
31
|
+
canAskAgain: true,
|
|
32
|
+
expires: 'never',
|
|
33
|
+
};
|
|
34
|
+
const DENIED: PermissionResponse = {
|
|
35
|
+
status: PermissionStatus.DENIED,
|
|
36
|
+
granted: false,
|
|
37
|
+
canAskAgain: true,
|
|
38
|
+
expires: 'never',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const results: (PermissionResponse | null)[] = [];
|
|
42
|
+
let latestRequest: (() => Promise<PermissionResponse>) | undefined;
|
|
43
|
+
let latestGet: (() => Promise<PermissionResponse>) | undefined;
|
|
44
|
+
|
|
45
|
+
function Probe(): ReactElement {
|
|
46
|
+
const [status, request, get] = usePermissions();
|
|
47
|
+
results.push(status);
|
|
48
|
+
latestRequest = request;
|
|
49
|
+
latestGet = get;
|
|
50
|
+
return createElement(View);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const fabric = installFabric();
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
fabric.reset();
|
|
57
|
+
results.length = 0;
|
|
58
|
+
latestRequest = undefined;
|
|
59
|
+
latestGet = undefined;
|
|
60
|
+
vi.clearAllMocks();
|
|
61
|
+
getTrackingPermissionsAsync.mockResolvedValue(GRANTED);
|
|
62
|
+
requestTrackingPermissionsAsync.mockResolvedValue(GRANTED);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
66
|
+
|
|
67
|
+
describe('usePermissions', () => {
|
|
68
|
+
it('starts at null before the initial fetch resolves', () => {
|
|
69
|
+
mount(ROOT_TAG, createElement(Probe));
|
|
70
|
+
|
|
71
|
+
expect(results[results.length - 1]).toBe(null);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('fetches the permission status once on mount', async () => {
|
|
75
|
+
mount(ROOT_TAG, createElement(Probe));
|
|
76
|
+
|
|
77
|
+
await vi.waitFor(() => expect(results[results.length - 1]).toEqual(GRANTED));
|
|
78
|
+
expect(getTrackingPermissionsAsync).toHaveBeenCalledTimes(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('request() delegates to requestTrackingPermissionsAsync and updates the status', async () => {
|
|
82
|
+
mount(ROOT_TAG, createElement(Probe));
|
|
83
|
+
await vi.waitFor(() => expect(results[results.length - 1]).toEqual(GRANTED));
|
|
84
|
+
|
|
85
|
+
requestTrackingPermissionsAsync.mockResolvedValueOnce(DENIED);
|
|
86
|
+
await latestRequest?.();
|
|
87
|
+
|
|
88
|
+
await vi.waitFor(() => expect(results[results.length - 1]).toEqual(DENIED));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('get() re-fetches and updates the status', async () => {
|
|
92
|
+
mount(ROOT_TAG, createElement(Probe));
|
|
93
|
+
await vi.waitFor(() => expect(results[results.length - 1]).toEqual(GRANTED));
|
|
94
|
+
|
|
95
|
+
getTrackingPermissionsAsync.mockResolvedValueOnce(DENIED);
|
|
96
|
+
await latestGet?.();
|
|
97
|
+
|
|
98
|
+
await vi.waitFor(() => expect(results[results.length - 1]).toEqual(DENIED));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
|
|
6
|
+
export { usePermissions } from './hooks/use-permissions';
|
|
7
|
+
export * from '../core';
|
|
@@ -0,0 +1,36 @@
|
|
|
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, type Ref } from '@vue/runtime-core';
|
|
6
|
+
import {
|
|
7
|
+
getTrackingPermissionsAsync,
|
|
8
|
+
requestTrackingPermissionsAsync,
|
|
9
|
+
type PermissionResponse,
|
|
10
|
+
} from '../../../core';
|
|
11
|
+
|
|
12
|
+
export function usePermissions(): {
|
|
13
|
+
status: Ref<PermissionResponse | null>;
|
|
14
|
+
request: () => Promise<PermissionResponse>;
|
|
15
|
+
get: () => Promise<PermissionResponse>;
|
|
16
|
+
} {
|
|
17
|
+
const status = ref<PermissionResponse | null>(null);
|
|
18
|
+
|
|
19
|
+
const get = async () => {
|
|
20
|
+
const response = await getTrackingPermissionsAsync();
|
|
21
|
+
status.value = response;
|
|
22
|
+
return response;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const request = async () => {
|
|
26
|
+
const response = await requestTrackingPermissionsAsync();
|
|
27
|
+
status.value = response;
|
|
28
|
+
return response;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
onMounted(() => {
|
|
32
|
+
void get();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return { status, request, get };
|
|
36
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Co-located Vue-driven test (ADR 0025) for usePermissions. See battery's
|
|
2
|
+
// use-battery-state.test.ts for the shared rationale.
|
|
3
|
+
|
|
4
|
+
import { defineComponent, h } from '@vue/runtime-core';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { mount, unmount } from '@symbiote-native/vue';
|
|
7
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
8
|
+
import { usePermissions } from './index';
|
|
9
|
+
import { PermissionStatus, type PermissionResponse } from '../../../core';
|
|
10
|
+
|
|
11
|
+
const { getTrackingPermissionsAsync, requestTrackingPermissionsAsync } = vi.hoisted(() => ({
|
|
12
|
+
getTrackingPermissionsAsync: vi.fn(),
|
|
13
|
+
requestTrackingPermissionsAsync: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Same enum-shaped-object mock trick packages/battery/src/vue/composables/use-battery-state's
|
|
17
|
+
// test uses for BatteryState.
|
|
18
|
+
vi.mock('../../../core', () => ({
|
|
19
|
+
getTrackingPermissionsAsync,
|
|
20
|
+
requestTrackingPermissionsAsync,
|
|
21
|
+
PermissionStatus: { GRANTED: 'granted', DENIED: 'denied', UNDETERMINED: 'undetermined' },
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const GRANTED: PermissionResponse = {
|
|
25
|
+
status: PermissionStatus.GRANTED,
|
|
26
|
+
granted: true,
|
|
27
|
+
canAskAgain: true,
|
|
28
|
+
expires: 'never',
|
|
29
|
+
};
|
|
30
|
+
const DENIED: PermissionResponse = {
|
|
31
|
+
status: PermissionStatus.DENIED,
|
|
32
|
+
granted: false,
|
|
33
|
+
canAskAgain: true,
|
|
34
|
+
expires: 'never',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const ROOT_TAG = 9954;
|
|
38
|
+
const fabric = installFabric();
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
fabric.reset();
|
|
42
|
+
vi.clearAllMocks();
|
|
43
|
+
getTrackingPermissionsAsync.mockResolvedValue(GRANTED);
|
|
44
|
+
requestTrackingPermissionsAsync.mockResolvedValue(GRANTED);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
48
|
+
|
|
49
|
+
function mountPermissions(): ReturnType<typeof usePermissions> {
|
|
50
|
+
let result: ReturnType<typeof usePermissions> | undefined;
|
|
51
|
+
mount(
|
|
52
|
+
ROOT_TAG,
|
|
53
|
+
defineComponent({
|
|
54
|
+
setup: () => {
|
|
55
|
+
result = usePermissions();
|
|
56
|
+
return () => h('symbiote-text', {}, 'tracking-transparency');
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
if (result === undefined) {
|
|
61
|
+
throw new Error('setup() did not run');
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
describe('usePermissions (Vue)', () => {
|
|
67
|
+
it('starts at null before the initial fetch resolves', () => {
|
|
68
|
+
const { status } = mountPermissions();
|
|
69
|
+
|
|
70
|
+
expect(status.value).toBe(null);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('fetches the permission status once on mount', async () => {
|
|
74
|
+
const { status } = mountPermissions();
|
|
75
|
+
|
|
76
|
+
await vi.waitFor(() => expect(status.value).toEqual(GRANTED));
|
|
77
|
+
expect(getTrackingPermissionsAsync).toHaveBeenCalledTimes(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('request() delegates to requestTrackingPermissionsAsync and updates the status', async () => {
|
|
81
|
+
const { status, request } = mountPermissions();
|
|
82
|
+
await vi.waitFor(() => expect(status.value).toEqual(GRANTED));
|
|
83
|
+
|
|
84
|
+
requestTrackingPermissionsAsync.mockResolvedValueOnce(DENIED);
|
|
85
|
+
await request();
|
|
86
|
+
|
|
87
|
+
expect(status.value).toEqual(DENIED);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('get() re-fetches and updates the status', async () => {
|
|
91
|
+
const { status, get } = mountPermissions();
|
|
92
|
+
await vi.waitFor(() => expect(status.value).toEqual(GRANTED));
|
|
93
|
+
|
|
94
|
+
getTrackingPermissionsAsync.mockResolvedValueOnce(DENIED);
|
|
95
|
+
await get();
|
|
96
|
+
|
|
97
|
+
expect(status.value).toEqual(DENIED);
|
|
98
|
+
});
|
|
99
|
+
});
|
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
|
|
6
|
+
export { usePermissions } from './composables/use-permissions';
|
|
7
|
+
export * from '../core';
|