@symbiote-native/screen-capture 0.0.0-stage → 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 +21 -0
- package/README.md +137 -2
- package/build/angular/index.d.ts +4 -0
- package/build/angular/index.js +4 -0
- package/build/angular/services/permissions.service/index.d.ts +5 -0
- package/build/angular/services/permissions.service/index.js +59 -0
- package/build/angular/services/prevent-screen-capture.service/index.d.ts +4 -0
- package/build/angular/services/prevent-screen-capture.service/index.js +65 -0
- package/build/angular/services/screenshot-listener.service/index.d.ts +4 -0
- package/build/angular/services/screenshot-listener.service/index.js +65 -0
- package/build/core/index.d.ts +2 -0
- package/build/core/index.js +2 -0
- package/build/core/native-module.d.ts +13 -0
- package/build/core/native-module.js +4 -0
- package/build/core/permissions-runtime.d.ts +5 -0
- package/build/core/permissions-runtime.js +16 -0
- package/build/core/screen-capture.d.ts +16 -0
- package/build/core/screen-capture.js +68 -0
- package/build/react/hooks/use-permissions/index.d.ts +7 -0
- package/build/react/hooks/use-permissions/index.js +37 -0
- package/build/react/hooks/use-prevent-screen-capture/index.d.ts +1 -0
- package/build/react/hooks/use-prevent-screen-capture/index.js +12 -0
- package/build/react/hooks/use-screenshot-listener/index.d.ts +1 -0
- package/build/react/hooks/use-screenshot-listener/index.js +12 -0
- package/build/react/index.d.ts +4 -0
- package/build/react/index.js +4 -0
- package/build/solid/index.d.ts +4 -0
- package/build/solid/index.js +4 -0
- package/build/solid/primitives/create-permissions.d.ts +8 -0
- package/build/solid/primitives/create-permissions.js +25 -0
- package/build/solid/primitives/create-prevent-screen-capture.d.ts +1 -0
- package/build/solid/primitives/create-prevent-screen-capture.js +11 -0
- package/build/solid/primitives/create-screenshot-listener.d.ts +1 -0
- package/build/solid/primitives/create-screenshot-listener.js +10 -0
- package/build/svelte/index.d.ts +4 -0
- package/build/svelte/index.js +4 -0
- package/build/svelte/use-permissions.svelte.d.ts +7 -0
- package/build/svelte/use-permissions.svelte.js +27 -0
- package/build/svelte/use-prevent-screen-capture.svelte.d.ts +1 -0
- package/build/svelte/use-prevent-screen-capture.svelte.js +10 -0
- package/build/svelte/use-screenshot-listener.svelte.d.ts +1 -0
- package/build/svelte/use-screenshot-listener.svelte.js +11 -0
- package/build/vue/composables/use-permissions/index.d.ts +8 -0
- package/build/vue/composables/use-permissions/index.js +19 -0
- package/build/vue/composables/use-prevent-screen-capture/index.d.ts +1 -0
- package/build/vue/composables/use-prevent-screen-capture/index.js +12 -0
- package/build/vue/composables/use-screenshot-listener/index.d.ts +1 -0
- package/build/vue/composables/use-screenshot-listener/index.js +13 -0
- package/build/vue/index.d.ts +4 -0
- package/build/vue/index.js +4 -0
- package/build-ngc/angular/index.d.ts +4 -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 +8 -0
- package/build-ngc/angular/services/permissions.service/index.js +16 -0
- package/build-ngc/angular/services/permissions.service/index.js.map +1 -0
- package/build-ngc/angular/services/prevent-screen-capture.service/index.d.ts +7 -0
- package/build-ngc/angular/services/prevent-screen-capture.service/index.js +23 -0
- package/build-ngc/angular/services/prevent-screen-capture.service/index.js.map +1 -0
- package/build-ngc/angular/services/screenshot-listener.service/index.d.ts +7 -0
- package/build-ngc/angular/services/screenshot-listener.service/index.js +23 -0
- package/build-ngc/angular/services/screenshot-listener.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 +13 -0
- package/build-ngc/core/native-module.js +5 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/permissions-runtime.d.ts +5 -0
- package/build-ngc/core/permissions-runtime.js +17 -0
- package/build-ngc/core/permissions-runtime.js.map +1 -0
- package/build-ngc/core/screen-capture.d.ts +16 -0
- package/build-ngc/core/screen-capture.js +69 -0
- package/build-ngc/core/screen-capture.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +148 -3
- package/src/angular/index.ts +4 -0
- package/src/angular/services/permissions.service/index.ts +11 -0
- package/src/angular/services/prevent-screen-capture.service/index.ts +25 -0
- package/src/angular/services/screenshot-listener.service/index.ts +22 -0
- package/src/core/index.ts +17 -0
- package/src/core/native-module.ts +27 -0
- package/src/core/permissions-runtime.ts +26 -0
- package/src/core/screen-capture.ts +102 -0
- package/src/react/hooks/use-permissions/index.ts +49 -0
- package/src/react/hooks/use-prevent-screen-capture/index.ts +17 -0
- package/src/react/hooks/use-screenshot-listener/index.ts +14 -0
- package/src/react/index.ts +4 -0
- package/src/solid/index.ts +4 -0
- package/src/solid/primitives/create-permissions.ts +39 -0
- package/src/solid/primitives/create-prevent-screen-capture.ts +14 -0
- package/src/solid/primitives/create-screenshot-listener.ts +13 -0
- package/src/svelte/index.ts +4 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +7 -0
- package/src/svelte/use-permissions.svelte.ts +41 -0
- package/src/svelte/use-prevent-screen-capture.svelte.ts +12 -0
- package/src/svelte/use-screenshot-listener.svelte.ts +13 -0
- package/src/vue/composables/use-permissions/index.ts +33 -0
- package/src/vue/composables/use-prevent-screen-capture/index.ts +18 -0
- package/src/vue/composables/use-screenshot-listener/index.ts +18 -0
- package/src/vue/index.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,151 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/screen-capture",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "expo-screen-capture wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Blocks or detects screenshots/screen recordings and offers an app-switcher privacy blur.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"react-native",
|
|
8
|
+
"symbiote-native",
|
|
9
|
+
"expo-screen-capture",
|
|
10
|
+
"react",
|
|
11
|
+
"vue",
|
|
12
|
+
"svelte",
|
|
13
|
+
"solid",
|
|
14
|
+
"angular",
|
|
15
|
+
"screen-capture",
|
|
16
|
+
"screenshot",
|
|
17
|
+
"privacy"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
22
|
+
"directory": "packages/screen-capture"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/screen-capture#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/OneEyed1366/symbiote-native/issues"
|
|
27
|
+
},
|
|
28
|
+
"author": "Andrey Prokopenko <psevdoproger@gmail.com>",
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "./build/core/index.js",
|
|
31
|
+
"module": "./build/core/index.js",
|
|
32
|
+
"types": "./build/core/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./build/core/index.d.ts",
|
|
36
|
+
"default": "./build/core/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./vue": {
|
|
39
|
+
"types": "./build/vue/index.d.ts",
|
|
40
|
+
"default": "./build/vue/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./react": {
|
|
43
|
+
"types": "./build/react/index.d.ts",
|
|
44
|
+
"default": "./build/react/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./svelte": {
|
|
47
|
+
"types": "./build/svelte/index.d.ts",
|
|
48
|
+
"default": "./build/svelte/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./solid": {
|
|
51
|
+
"types": "./build/solid/index.d.ts",
|
|
52
|
+
"default": "./build/solid/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./angular": {
|
|
55
|
+
"types": "./build-ngc/angular/index.d.ts",
|
|
56
|
+
"react-native": "./build-ngc/angular/index.js",
|
|
57
|
+
"default": "./src/angular/index.ts"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"files": [
|
|
61
|
+
"src",
|
|
62
|
+
"build",
|
|
63
|
+
"build-ngc",
|
|
64
|
+
"native-link.json",
|
|
65
|
+
"!src/**/*.test.*",
|
|
66
|
+
"!src/**/*.spec.*",
|
|
67
|
+
"!src/**/*.detox.*"
|
|
68
|
+
],
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"expo-screen-capture": "57.0.3",
|
|
74
|
+
"expo-modules-core": "57.0.20"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"@angular/core": ">=20",
|
|
78
|
+
"@vue/runtime-core": "^3.5.13",
|
|
79
|
+
"react": ">=19.0.0",
|
|
80
|
+
"react-native": ">=0.86",
|
|
81
|
+
"solid-js": ">=1.9.0",
|
|
82
|
+
"svelte": ">=5.56.0",
|
|
83
|
+
"vue": ">=3.5.0",
|
|
84
|
+
"@symbiote-native/engine": "^1.4.0",
|
|
85
|
+
"@symbiote-native/react": "^3.1.0",
|
|
86
|
+
"@symbiote-native/solid": "^3.0.5",
|
|
87
|
+
"@symbiote-native/angular": "^3.1.3",
|
|
88
|
+
"@symbiote-native/svelte": "^3.0.5",
|
|
89
|
+
"@symbiote-native/vue": "^3.1.0"
|
|
90
|
+
},
|
|
91
|
+
"peerDependenciesMeta": {
|
|
92
|
+
"@symbiote-native/angular": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"@symbiote-native/react": {
|
|
96
|
+
"optional": true
|
|
97
|
+
},
|
|
98
|
+
"@symbiote-native/solid": {
|
|
99
|
+
"optional": true
|
|
100
|
+
},
|
|
101
|
+
"@symbiote-native/svelte": {
|
|
102
|
+
"optional": true
|
|
103
|
+
},
|
|
104
|
+
"@symbiote-native/vue": {
|
|
105
|
+
"optional": true
|
|
106
|
+
},
|
|
107
|
+
"@angular/core": {
|
|
108
|
+
"optional": true
|
|
109
|
+
},
|
|
110
|
+
"@vue/runtime-core": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
113
|
+
"react": {
|
|
114
|
+
"optional": true
|
|
115
|
+
},
|
|
116
|
+
"solid-js": {
|
|
117
|
+
"optional": true
|
|
118
|
+
},
|
|
119
|
+
"svelte": {
|
|
120
|
+
"optional": true
|
|
121
|
+
},
|
|
122
|
+
"vue": {
|
|
123
|
+
"optional": true
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"devDependencies": {
|
|
127
|
+
"@angular/compiler": "~22.0.8",
|
|
128
|
+
"@angular/compiler-cli": "~22.0.8",
|
|
129
|
+
"@angular/core": "~22.0.8",
|
|
130
|
+
"@types/node": "^26.0.0",
|
|
131
|
+
"@types/react": "^19.2.0",
|
|
132
|
+
"@vue/runtime-core": "^3.5.13",
|
|
133
|
+
"react": "19.2.3",
|
|
134
|
+
"solid-js": "^1.9.14",
|
|
135
|
+
"svelte": "^5.56.0",
|
|
136
|
+
"typescript": "~6.0.0",
|
|
137
|
+
"@symbiote-native/engine": "1.4.0",
|
|
138
|
+
"@symbiote-native/angular": "3.1.3",
|
|
139
|
+
"@symbiote-native/react": "3.1.0",
|
|
140
|
+
"@symbiote-native/solid": "3.0.5",
|
|
141
|
+
"@symbiote-native/vue": "3.1.0",
|
|
142
|
+
"@symbiote-native/test-utils": "0.4.5",
|
|
143
|
+
"@symbiote-native/svelte": "3.0.5"
|
|
144
|
+
},
|
|
145
|
+
"scripts": {
|
|
146
|
+
"typecheck": "tsc --build",
|
|
147
|
+
"clean": "rm -rf build-ngc",
|
|
148
|
+
"ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
|
|
149
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
150
|
+
}
|
|
6
151
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { PermissionsServiceBase } from '@symbiote-native/angular';
|
|
3
|
+
import { getPermissionsAsync, requestPermissionsAsync } from '../../../core';
|
|
4
|
+
import type { PermissionResponse } from '../../../core';
|
|
5
|
+
|
|
6
|
+
@Injectable({ providedIn: 'root' })
|
|
7
|
+
export class PermissionsService extends PermissionsServiceBase<PermissionResponse> {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(getPermissionsAsync, requestPermissionsAsync);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Angular twin of `../../react`'s `usePreventScreenCapture`, `connect()` shape matching
|
|
2
|
+
// `@symbiote-native/keep-awake`'s own `KeepAwakeService`
|
|
3
|
+
|
|
4
|
+
import { effect, inject, Injectable, Injector } from '@angular/core';
|
|
5
|
+
import {
|
|
6
|
+
allowScreenCaptureAsync,
|
|
7
|
+
preventScreenCaptureAsync,
|
|
8
|
+
} from '../../../core';
|
|
9
|
+
|
|
10
|
+
@Injectable({ providedIn: 'root' })
|
|
11
|
+
export class PreventScreenCaptureService {
|
|
12
|
+
private readonly injector = inject(Injector);
|
|
13
|
+
|
|
14
|
+
connect(key: string = 'default'): void {
|
|
15
|
+
effect(
|
|
16
|
+
onCleanup => {
|
|
17
|
+
preventScreenCaptureAsync(key);
|
|
18
|
+
onCleanup(() => {
|
|
19
|
+
allowScreenCaptureAsync(key);
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
{ injector: this.injector },
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Angular twin of `../../react`'s `useScreenshotListener`, `connect()` shape matching
|
|
2
|
+
// `@symbiote-native/keep-awake`'s own `KeepAwakeService`
|
|
3
|
+
|
|
4
|
+
import { effect, inject, Injectable, Injector } from '@angular/core';
|
|
5
|
+
import { addScreenshotListener } from '../../../core';
|
|
6
|
+
|
|
7
|
+
@Injectable({ providedIn: 'root' })
|
|
8
|
+
export class ScreenshotListenerService {
|
|
9
|
+
private readonly injector = inject(Injector);
|
|
10
|
+
|
|
11
|
+
connect(listener: () => void): void {
|
|
12
|
+
effect(
|
|
13
|
+
onCleanup => {
|
|
14
|
+
const subscription = addScreenshotListener(listener);
|
|
15
|
+
onCleanup(() => {
|
|
16
|
+
subscription.remove();
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
{ injector: this.injector },
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
addScreenshotListener,
|
|
3
|
+
allowScreenCaptureAsync,
|
|
4
|
+
disableAppSwitcherProtectionAsync,
|
|
5
|
+
enableAppSwitcherProtectionAsync,
|
|
6
|
+
getPermissionsAsync,
|
|
7
|
+
isAvailableAsync,
|
|
8
|
+
preventScreenCaptureAsync,
|
|
9
|
+
removeScreenshotListener,
|
|
10
|
+
requestPermissionsAsync,
|
|
11
|
+
} from './screen-capture';
|
|
12
|
+
export {
|
|
13
|
+
PermissionStatus,
|
|
14
|
+
type EventSubscription,
|
|
15
|
+
type PermissionExpiration,
|
|
16
|
+
type PermissionResponse,
|
|
17
|
+
} from 'expo-modules-core';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
import type { EventSubscription, PermissionResponse } from 'expo-modules-core';
|
|
3
|
+
|
|
4
|
+
const EXPO_SCREEN_CAPTURE_MODULE_NAME = 'ExpoScreenCapture';
|
|
5
|
+
|
|
6
|
+
export const ON_SCREENSHOT_EVENT_NAME = 'onScreenshot';
|
|
7
|
+
|
|
8
|
+
// App-switcher blur is iOS-only (absent from ScreenCaptureModule.kt); permissions are
|
|
9
|
+
// Android-only in practice - each optional method is checked at its own call site.
|
|
10
|
+
export type INativeScreenCaptureModule = {
|
|
11
|
+
addListener(
|
|
12
|
+
eventName: typeof ON_SCREENSHOT_EVENT_NAME,
|
|
13
|
+
listener: () => void,
|
|
14
|
+
): EventSubscription;
|
|
15
|
+
removeAllListeners(eventName: typeof ON_SCREENSHOT_EVENT_NAME): void;
|
|
16
|
+
preventScreenCapture?(): Promise<void>;
|
|
17
|
+
allowScreenCapture?(): Promise<void>;
|
|
18
|
+
enableAppSwitcherProtection?(blurIntensity: number): Promise<void>;
|
|
19
|
+
disableAppSwitcherProtection?(): Promise<void>;
|
|
20
|
+
getPermissionsAsync?(): Promise<PermissionResponse>;
|
|
21
|
+
requestPermissionsAsync?(): Promise<PermissionResponse>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const expoScreenCapture =
|
|
25
|
+
requireNativeModule<INativeScreenCaptureModule>(
|
|
26
|
+
EXPO_SCREEN_CAPTURE_MODULE_NAME,
|
|
27
|
+
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// The get/request wrapper every adapter's `usePermissions` needs, extracted once so
|
|
2
|
+
// `src/vue`/`src/solid`/`src/svelte`/`src/react` each supply only their own state primitive
|
|
3
|
+
|
|
4
|
+
export type IPermissionsApi<TPermission> = {
|
|
5
|
+
get: () => Promise<TPermission>;
|
|
6
|
+
request: () => Promise<TPermission>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function createPermissionsApi<TPermission>(
|
|
10
|
+
getMethod: () => Promise<TPermission>,
|
|
11
|
+
requestMethod: () => Promise<TPermission>,
|
|
12
|
+
record: (response: TPermission) => void,
|
|
13
|
+
): IPermissionsApi<TPermission> {
|
|
14
|
+
return {
|
|
15
|
+
get: async () => {
|
|
16
|
+
const response = await getMethod();
|
|
17
|
+
record(response);
|
|
18
|
+
return response;
|
|
19
|
+
},
|
|
20
|
+
request: async () => {
|
|
21
|
+
const response = await requestMethod();
|
|
22
|
+
record(response);
|
|
23
|
+
return response;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { PermissionStatus, UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import type { EventSubscription, PermissionResponse } from 'expo-modules-core';
|
|
3
|
+
import { expoScreenCapture, ON_SCREENSHOT_EVENT_NAME } from './native-module';
|
|
4
|
+
|
|
5
|
+
const NATIVE_MODULE_NAME = 'ScreenCapture';
|
|
6
|
+
const activeKeys = new Set<string>();
|
|
7
|
+
|
|
8
|
+
export async function isAvailableAsync(): Promise<boolean> {
|
|
9
|
+
return (
|
|
10
|
+
!!expoScreenCapture.preventScreenCapture &&
|
|
11
|
+
!!expoScreenCapture.allowScreenCapture
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Repeated calls with the same `key` are no-ops until a matching `allowScreenCaptureAsync`. */
|
|
16
|
+
export async function preventScreenCaptureAsync(
|
|
17
|
+
key: string = 'default',
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
if (!expoScreenCapture.preventScreenCapture) {
|
|
20
|
+
throw new UnavailabilityError(
|
|
21
|
+
NATIVE_MODULE_NAME,
|
|
22
|
+
'preventScreenCaptureAsync',
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (!activeKeys.has(key)) {
|
|
26
|
+
activeKeys.add(key);
|
|
27
|
+
await expoScreenCapture.preventScreenCapture();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function allowScreenCaptureAsync(
|
|
32
|
+
key: string = 'default',
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
if (!expoScreenCapture.allowScreenCapture) {
|
|
35
|
+
throw new UnavailabilityError(
|
|
36
|
+
NATIVE_MODULE_NAME,
|
|
37
|
+
'allowScreenCaptureAsync',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
activeKeys.delete(key);
|
|
41
|
+
if (activeKeys.size === 0) {
|
|
42
|
+
await expoScreenCapture.allowScreenCapture();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** iOS only; throws `UnavailabilityError` on Android. */
|
|
47
|
+
export async function enableAppSwitcherProtectionAsync(
|
|
48
|
+
blurIntensity: number = 0.5,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
if (!expoScreenCapture.enableAppSwitcherProtection) {
|
|
51
|
+
throw new UnavailabilityError(
|
|
52
|
+
NATIVE_MODULE_NAME,
|
|
53
|
+
'enableAppSwitcherProtectionAsync',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
await expoScreenCapture.enableAppSwitcherProtection(blurIntensity);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** iOS only; throws `UnavailabilityError` on Android. */
|
|
60
|
+
export async function disableAppSwitcherProtectionAsync(): Promise<void> {
|
|
61
|
+
if (!expoScreenCapture.disableAppSwitcherProtection) {
|
|
62
|
+
throw new UnavailabilityError(
|
|
63
|
+
NATIVE_MODULE_NAME,
|
|
64
|
+
'disableAppSwitcherProtectionAsync',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
await expoScreenCapture.disableAppSwitcherProtection();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function addScreenshotListener(listener: () => void): EventSubscription {
|
|
71
|
+
return expoScreenCapture.addListener(ON_SCREENSHOT_EVENT_NAME, listener);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @deprecated use `subscription.remove()` instead. */
|
|
75
|
+
export function removeScreenshotListener(
|
|
76
|
+
subscription: EventSubscription,
|
|
77
|
+
): void {
|
|
78
|
+
subscription.remove();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const defaultPermissionsResponse: PermissionResponse = {
|
|
82
|
+
granted: true,
|
|
83
|
+
expires: 'never',
|
|
84
|
+
canAskAgain: true,
|
|
85
|
+
status: PermissionStatus.GRANTED,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Only Android requires a permission to detect screenshots; iOS always resolves granted. */
|
|
89
|
+
export async function getPermissionsAsync(): Promise<PermissionResponse> {
|
|
90
|
+
if (expoScreenCapture.getPermissionsAsync) {
|
|
91
|
+
return expoScreenCapture.getPermissionsAsync();
|
|
92
|
+
}
|
|
93
|
+
return defaultPermissionsResponse;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Only Android requires a permission to detect screenshots; iOS always resolves granted. */
|
|
97
|
+
export async function requestPermissionsAsync(): Promise<PermissionResponse> {
|
|
98
|
+
if (expoScreenCapture.requestPermissionsAsync) {
|
|
99
|
+
return expoScreenCapture.requestPermissionsAsync();
|
|
100
|
+
}
|
|
101
|
+
return defaultPermissionsResponse;
|
|
102
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Same shape as `@symbiote-native/brightness`'s own `usePermissions` by design - each package
|
|
2
|
+
// owns its copy. The mount fetch has nobody to reject to, so its failure lands in the 4th
|
|
3
|
+
// tuple slot (`error`)
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
6
|
+
import { getPermissionsAsync, requestPermissionsAsync } from '../../../core';
|
|
7
|
+
import type { PermissionResponse } from '../../../core';
|
|
8
|
+
|
|
9
|
+
export function usePermissions(): [
|
|
10
|
+
PermissionResponse | null,
|
|
11
|
+
() => Promise<PermissionResponse>,
|
|
12
|
+
() => Promise<PermissionResponse>,
|
|
13
|
+
Error | null,
|
|
14
|
+
] {
|
|
15
|
+
const isMounted = useRef(true);
|
|
16
|
+
const [status, setStatus] = useState<PermissionResponse | null>(null);
|
|
17
|
+
const [error, setError] = useState<Error | null>(null);
|
|
18
|
+
|
|
19
|
+
const getPermission = useCallback(async () => {
|
|
20
|
+
const response = await getPermissionsAsync();
|
|
21
|
+
if (isMounted.current) {
|
|
22
|
+
setStatus(response);
|
|
23
|
+
setError(null);
|
|
24
|
+
}
|
|
25
|
+
return response;
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const requestPermission = useCallback(async () => {
|
|
29
|
+
const response = await requestPermissionsAsync();
|
|
30
|
+
if (isMounted.current) {
|
|
31
|
+
setStatus(response);
|
|
32
|
+
setError(null);
|
|
33
|
+
}
|
|
34
|
+
return response;
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
isMounted.current = true;
|
|
39
|
+
getPermission().catch((cause: unknown) => {
|
|
40
|
+
if (isMounted.current)
|
|
41
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
42
|
+
});
|
|
43
|
+
return () => {
|
|
44
|
+
isMounted.current = false;
|
|
45
|
+
};
|
|
46
|
+
}, [getPermission]);
|
|
47
|
+
|
|
48
|
+
return [status, requestPermission, getPermission, error];
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// React lifecycle wiring over the framework-agnostic core (core/screen-capture.ts): prevents on
|
|
2
|
+
// mount, allows on unmount, mirroring upstream's `usePreventScreenCapture`
|
|
3
|
+
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
import {
|
|
6
|
+
allowScreenCaptureAsync,
|
|
7
|
+
preventScreenCaptureAsync,
|
|
8
|
+
} from '../../../core';
|
|
9
|
+
|
|
10
|
+
export function usePreventScreenCapture(key: string = 'default'): void {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
preventScreenCaptureAsync(key);
|
|
13
|
+
return () => {
|
|
14
|
+
allowScreenCaptureAsync(key);
|
|
15
|
+
};
|
|
16
|
+
}, [key]);
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// React lifecycle wiring over the framework-agnostic core (core/screen-capture.ts): subscribes
|
|
2
|
+
// on mount, unsubscribes on unmount, mirroring upstream's `useScreenshotListener`
|
|
3
|
+
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
import { addScreenshotListener } from '../../../core';
|
|
6
|
+
|
|
7
|
+
export function useScreenshotListener(listener: () => void): void {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const subscription = addScreenshotListener(listener);
|
|
10
|
+
return () => {
|
|
11
|
+
subscription.remove();
|
|
12
|
+
};
|
|
13
|
+
}, [listener]);
|
|
14
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Solid lifecycle wiring over core/screen-capture.ts. The synchronous fetch has nobody to reject
|
|
2
|
+
// to, so its failure lands in `error` instead of escaping as an unhandled rejection
|
|
3
|
+
|
|
4
|
+
import { createSignal, onCleanup, type Accessor } from 'solid-js';
|
|
5
|
+
import { getPermissionsAsync, requestPermissionsAsync } from '../../core';
|
|
6
|
+
import type { PermissionResponse } from '../../core';
|
|
7
|
+
import { createPermissionsApi } from '../../core/permissions-runtime';
|
|
8
|
+
|
|
9
|
+
export function createPermissions(): {
|
|
10
|
+
status: Accessor<PermissionResponse | null>;
|
|
11
|
+
error: Accessor<Error | null>;
|
|
12
|
+
request: () => Promise<PermissionResponse>;
|
|
13
|
+
get: () => Promise<PermissionResponse>;
|
|
14
|
+
} {
|
|
15
|
+
const [status, setStatus] = createSignal<PermissionResponse | null>(null);
|
|
16
|
+
const [error, setError] = createSignal<Error | null>(null);
|
|
17
|
+
let isDisposed = false;
|
|
18
|
+
|
|
19
|
+
const api = createPermissionsApi(
|
|
20
|
+
getPermissionsAsync,
|
|
21
|
+
requestPermissionsAsync,
|
|
22
|
+
response => {
|
|
23
|
+
if (isDisposed) return;
|
|
24
|
+
setStatus(response);
|
|
25
|
+
setError(null);
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
api.get().catch((cause: unknown) => {
|
|
30
|
+
if (isDisposed) return;
|
|
31
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
onCleanup(() => {
|
|
35
|
+
isDisposed = true;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return { status, error, request: api.request, get: api.get };
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Solid twin of `../../react`'s `usePreventScreenCapture`: prevents synchronously in the
|
|
2
|
+
// primitive body, allows on `onCleanup` - same "nothing can interleave" reasoning as
|
|
3
|
+
// `create-keep-awake.ts` (`@symbiote-native/keep-awake`)
|
|
4
|
+
|
|
5
|
+
import { onCleanup } from 'solid-js';
|
|
6
|
+
import { allowScreenCaptureAsync, preventScreenCaptureAsync } from '../../core';
|
|
7
|
+
|
|
8
|
+
export function createPreventScreenCapture(key: string = 'default'): void {
|
|
9
|
+
preventScreenCaptureAsync(key);
|
|
10
|
+
|
|
11
|
+
onCleanup(() => {
|
|
12
|
+
allowScreenCaptureAsync(key);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Solid twin of `../../react`'s `useScreenshotListener`: subscribes synchronously in the
|
|
2
|
+
// primitive body, unsubscribes on `onCleanup`
|
|
3
|
+
|
|
4
|
+
import { onCleanup } from 'solid-js';
|
|
5
|
+
import { addScreenshotListener } from '../../core';
|
|
6
|
+
|
|
7
|
+
export function createScreenshotListener(listener: () => void): void {
|
|
8
|
+
const subscription = addScreenshotListener(listener);
|
|
9
|
+
|
|
10
|
+
onCleanup(() => {
|
|
11
|
+
subscription.remove();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Re-exports the existing harness instead of a third copy (packages/sqlite and
|
|
2
|
+
// packages/navigation already carry one, not part of either package's public exports)
|
|
3
|
+
export {
|
|
4
|
+
createSvelteHarness,
|
|
5
|
+
loadComponent,
|
|
6
|
+
type ISvelteHarness,
|
|
7
|
+
} from '../../../sqlite/src/svelte/svelte-compile.test-helper';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Boxed-getter shape matches `@symbiote-native/brightness`'s own rune by design - each package
|
|
2
|
+
// owns its copy rather than depend on a sibling install-optional package
|
|
3
|
+
|
|
4
|
+
import { getPermissionsAsync, requestPermissionsAsync } from '../core';
|
|
5
|
+
import type { PermissionResponse } from '../core';
|
|
6
|
+
import { createPermissionsApi } from '../core/permissions-runtime';
|
|
7
|
+
|
|
8
|
+
export function usePermissions(): {
|
|
9
|
+
readonly status: PermissionResponse | null;
|
|
10
|
+
readonly error: Error | null;
|
|
11
|
+
request: () => Promise<PermissionResponse>;
|
|
12
|
+
get: () => Promise<PermissionResponse>;
|
|
13
|
+
} {
|
|
14
|
+
let status = $state<PermissionResponse | null>(null);
|
|
15
|
+
let error = $state<Error | null>(null);
|
|
16
|
+
const api = createPermissionsApi(
|
|
17
|
+
getPermissionsAsync,
|
|
18
|
+
requestPermissionsAsync,
|
|
19
|
+
response => {
|
|
20
|
+
status = response;
|
|
21
|
+
error = null;
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
$effect(() => {
|
|
26
|
+
api.get().catch((cause: unknown) => {
|
|
27
|
+
error = cause instanceof Error ? cause : new Error(String(cause));
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
get status(): PermissionResponse | null {
|
|
33
|
+
return status;
|
|
34
|
+
},
|
|
35
|
+
get error(): Error | null {
|
|
36
|
+
return error;
|
|
37
|
+
},
|
|
38
|
+
request: api.request,
|
|
39
|
+
get: api.get,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Svelte twin of `../react`'s `usePreventScreenCapture`: prevents on mount, allows on unmount
|
|
2
|
+
|
|
3
|
+
import { allowScreenCaptureAsync, preventScreenCaptureAsync } from '../core';
|
|
4
|
+
|
|
5
|
+
export function usePreventScreenCapture(key: string = 'default'): void {
|
|
6
|
+
$effect(() => {
|
|
7
|
+
preventScreenCaptureAsync(key);
|
|
8
|
+
return () => {
|
|
9
|
+
allowScreenCaptureAsync(key);
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Svelte twin of `../react`'s `useScreenshotListener`: subscribes on mount, unsubscribes on
|
|
2
|
+
// unmount
|
|
3
|
+
|
|
4
|
+
import { addScreenshotListener } from '../core';
|
|
5
|
+
|
|
6
|
+
export function useScreenshotListener(listener: () => void): void {
|
|
7
|
+
$effect(() => {
|
|
8
|
+
const subscription = addScreenshotListener(listener);
|
|
9
|
+
return () => {
|
|
10
|
+
subscription.remove();
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
}
|