@egen-civitas/esm-platform 1.1.0 → 1.2.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/CHANGELOG.md +15 -0
- package/README.md +64 -2
- package/dist/adapters/web/index.d.ts +7 -5
- package/dist/adapters/web/index.js +7 -22
- package/dist/bridge/client.d.ts +48 -0
- package/dist/bridge/client.js +212 -0
- package/dist/bridge/client.test.js +1264 -0
- package/dist/bridge/index.d.ts +4 -0
- package/dist/bridge/index.js +2 -0
- package/dist/bridge/protocol.d.ts +72 -0
- package/dist/bridge/protocol.js +66 -0
- package/dist/bridge/protocol.test.js +215 -0
- package/dist/capabilities.d.ts +15 -7
- package/dist/capabilities.js +15 -39
- package/dist/detect.d.ts +9 -2
- package/dist/detect.js +15 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/native-platform.test.js +329 -0
- package/dist/native.d.ts +24 -0
- package/dist/native.js +46 -0
- package/dist/platform.d.ts +6 -3
- package/dist/platform.js +27 -18
- package/dist/platform.test.js +3 -6
- package/dist/registry.d.ts +52 -0
- package/dist/registry.js +101 -0
- package/dist/registry.test.js +681 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/mock-host.d.ts +49 -0
- package/dist/testing/mock-host.js +86 -0
- package/dist/types.d.ts +34 -12
- package/package.json +3 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { CapabilityName } from '@egen-civitas/esm-globals';
|
|
2
|
+
import type { HostBridge } from '../bridge/protocol.js';
|
|
3
|
+
import type { PlatformEnvironment } from '../types.js';
|
|
4
|
+
export interface MockHostOptions {
|
|
5
|
+
/** Version de protocole annoncée par le Host dans le `hello`. @default 1 */
|
|
6
|
+
protocol?: number;
|
|
7
|
+
/** Valeur du champ `v` des réponses (permet de simuler un Host qui répond dans une version inconnue). @default protocol */
|
|
8
|
+
replyVersion?: number;
|
|
9
|
+
/** Version de l'application hôte. @default '1.0.0-test' */
|
|
10
|
+
host?: string;
|
|
11
|
+
/** Plateforme annoncée. @default 'android' */
|
|
12
|
+
platform?: PlatformEnvironment;
|
|
13
|
+
/** Capacités annoncées dans le `hello`. @default {} */
|
|
14
|
+
capabilities?: Partial<Record<CapabilityName, {
|
|
15
|
+
v: number;
|
|
16
|
+
}>>;
|
|
17
|
+
/** Remplace entièrement le résultat du `hello` (cas limites : réponse invalide…). */
|
|
18
|
+
helloResult?: unknown;
|
|
19
|
+
/** Méthodes servies, clé `namespace.méthode`. Retourner une valeur, ou lever (`code` de l'erreur repris). */
|
|
20
|
+
handlers?: Record<string, (args: unknown) => unknown>;
|
|
21
|
+
/** `false` : le Host ne répond jamais (tests de timeout). @default true */
|
|
22
|
+
respond?: boolean;
|
|
23
|
+
/** Latence de réponse en ms (0 = prochaine micro-tâche). @default 0 */
|
|
24
|
+
latencyMs?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface MockHost {
|
|
27
|
+
/** Le pont à injecter : `window.egenHostBridge`. */
|
|
28
|
+
readonly bridge: HostBridge;
|
|
29
|
+
/** Requêtes décodées reçues par le Host, dans l'ordre. */
|
|
30
|
+
readonly sent: Array<Record<string, unknown>>;
|
|
31
|
+
/** Nombre d'appels à `subscribe`. */
|
|
32
|
+
readonly subscribeCount: number;
|
|
33
|
+
/** Pose `window.egenHostBridge`. */
|
|
34
|
+
install(): void;
|
|
35
|
+
/** Retire `window.egenHostBridge`. */
|
|
36
|
+
uninstall(): void;
|
|
37
|
+
/** host → web : émet un événement du protocole. */
|
|
38
|
+
emit(event: string, data?: unknown): void;
|
|
39
|
+
/** host → web : pousse une chaîne arbitraire (messages malformés, orphelins…). */
|
|
40
|
+
inject(raw: string): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Simule un Host natif (`window.egenHostBridge`) pour les tests : handshake automatique, méthodes programmables,
|
|
44
|
+
* latence, silence, version de protocole incompatible. Aucun code natif : c'est le CONTRAT du protocole v1 (annexe 1
|
|
45
|
+
* du document de développement) rejoué côté test.
|
|
46
|
+
*
|
|
47
|
+
* @category Platform
|
|
48
|
+
*/
|
|
49
|
+
export declare function createMockHost(options?: MockHostOptions): MockHost;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simule un Host natif (`window.egenHostBridge`) pour les tests : handshake automatique, méthodes programmables,
|
|
3
|
+
* latence, silence, version de protocole incompatible. Aucun code natif : c'est le CONTRAT du protocole v1 (annexe 1
|
|
4
|
+
* du document de développement) rejoué côté test.
|
|
5
|
+
*
|
|
6
|
+
* @category Platform
|
|
7
|
+
*/
|
|
8
|
+
export function createMockHost(options = {}) {
|
|
9
|
+
const { protocol = 1, replyVersion = protocol, host = '1.0.0-test', platform = 'android', capabilities = {}, handlers = {}, respond = true, latencyMs = 0, } = options;
|
|
10
|
+
let callback = null;
|
|
11
|
+
let subscribeCount = 0;
|
|
12
|
+
const sent = [];
|
|
13
|
+
const deliver = (message) => {
|
|
14
|
+
const json = JSON.stringify({ p: 'egen-host', v: replyVersion, ...message });
|
|
15
|
+
const push = () => callback?.(json);
|
|
16
|
+
if (latencyMs > 0) {
|
|
17
|
+
setTimeout(push, latencyMs);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
void Promise.resolve().then(push);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const reply = async (request) => {
|
|
24
|
+
const id = request.id;
|
|
25
|
+
try {
|
|
26
|
+
if (request.ns === 'host' && request.m === 'hello') {
|
|
27
|
+
deliver({
|
|
28
|
+
id,
|
|
29
|
+
ok: true,
|
|
30
|
+
r: 'helloResult' in options ? options.helloResult : { protocol, host, platform, capabilities },
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const handler = handlers[`${String(request.ns)}.${String(request.m)}`];
|
|
35
|
+
if (!handler) {
|
|
36
|
+
deliver({
|
|
37
|
+
id,
|
|
38
|
+
ok: false,
|
|
39
|
+
e: { code: 'UNAVAILABLE', message: `Pas de handler pour ${String(request.ns)}.${String(request.m)}` },
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
deliver({ id, ok: true, r: await handler(request.a) });
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const code = error?.code;
|
|
47
|
+
deliver({
|
|
48
|
+
id,
|
|
49
|
+
ok: false,
|
|
50
|
+
e: { code: code ?? 'HOST_ERROR', message: error instanceof Error ? error.message : String(error) },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const bridge = {
|
|
55
|
+
send(json) {
|
|
56
|
+
const request = JSON.parse(json);
|
|
57
|
+
sent.push(request);
|
|
58
|
+
if (respond) {
|
|
59
|
+
void reply(request);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
subscribe(cb) {
|
|
63
|
+
callback = cb;
|
|
64
|
+
subscribeCount++;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
bridge,
|
|
69
|
+
sent,
|
|
70
|
+
get subscribeCount() {
|
|
71
|
+
return subscribeCount;
|
|
72
|
+
},
|
|
73
|
+
install() {
|
|
74
|
+
window.egenHostBridge = bridge;
|
|
75
|
+
},
|
|
76
|
+
uninstall() {
|
|
77
|
+
delete window.egenHostBridge;
|
|
78
|
+
},
|
|
79
|
+
emit(event, data) {
|
|
80
|
+
callback?.(JSON.stringify({ p: 'egen-host', v: replyVersion, ev: event, ...(data === undefined ? {} : { d: data }) }));
|
|
81
|
+
},
|
|
82
|
+
inject(raw) {
|
|
83
|
+
callback?.(raw);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -66,21 +66,43 @@ export interface BiometricCapability {
|
|
|
66
66
|
authenticate(reason: string): Promise<boolean>;
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
69
|
+
* Forme (interface TypeScript) de chaque capacité, indexée par son nom. Les 9 capacités de la v1 y sont déclarées ;
|
|
70
|
+
* une nouvelle capacité s'y ajoute par FUSION DE DÉCLARATION depuis son propre fichier (procédure « ajouter une
|
|
71
|
+
* capacité » du README), sans modifier celui-ci :
|
|
72
|
+
*
|
|
73
|
+
* ```ts
|
|
74
|
+
* declare module './types.js' {
|
|
75
|
+
* interface CapabilityInterfaces {
|
|
76
|
+
* maCapacite: MaCapaciteCapability;
|
|
77
|
+
* }
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* Le nom doit aussi être déclaré dans `EgenCapabilityExtensions` (`@egen-civitas/esm-globals`) pour être un
|
|
82
|
+
* `CapabilityName` valide.
|
|
71
83
|
*/
|
|
72
|
-
export interface
|
|
84
|
+
export interface CapabilityInterfaces {
|
|
85
|
+
session: SessionCapability;
|
|
86
|
+
secureStorage: SecureStorageCapability;
|
|
87
|
+
openExternal: OpenExternalCapability;
|
|
88
|
+
lifecycle: LifecycleCapability;
|
|
89
|
+
permissions: PermissionsCapability;
|
|
90
|
+
files: FilesCapability;
|
|
91
|
+
camera: CameraCapability;
|
|
92
|
+
push: PushCapability;
|
|
93
|
+
biometric: BiometricCapability;
|
|
94
|
+
}
|
|
95
|
+
/** Ce qui est commun à toute plateforme, indépendamment des capacités. */
|
|
96
|
+
export interface PlatformCore {
|
|
73
97
|
readonly environment: PlatformEnvironment;
|
|
74
98
|
/** Résolue quand le handshake avec le Host est terminé (immédiatement en Web). */
|
|
75
99
|
readonly ready: Promise<void>;
|
|
76
100
|
has(capability: CapabilityName): boolean;
|
|
77
|
-
readonly session: SessionCapability;
|
|
78
|
-
readonly secureStorage: SecureStorageCapability;
|
|
79
|
-
readonly openExternal: OpenExternalCapability;
|
|
80
|
-
readonly lifecycle: LifecycleCapability;
|
|
81
|
-
readonly permissions: PermissionsCapability;
|
|
82
|
-
readonly files: FilesCapability;
|
|
83
|
-
readonly camera: CameraCapability;
|
|
84
|
-
readonly push: PushCapability;
|
|
85
|
-
readonly biometric: BiometricCapability;
|
|
86
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Point d'accès unique des apps aux capacités de plateforme.
|
|
104
|
+
* Une capacité indisponible (`has()` faux) existe toujours comme objet : ses méthodes rejettent `UNAVAILABLE`.
|
|
105
|
+
*/
|
|
106
|
+
export type Platform = PlatformCore & {
|
|
107
|
+
readonly [K in keyof CapabilityInterfaces]: CapabilityInterfaces[K];
|
|
108
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@egen-civitas/esm-platform",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Capacités de plateforme EGEN (web, Android, iOS) : contrats TypeScript, détection de la cible, erreurs typées et client du protocole Host. Aucun code natif ici.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"lint": "eslint src --ext ts,tsx"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@egen-civitas/esm-globals": "^2.
|
|
35
|
+
"@egen-civitas/esm-globals": "^2.2.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@egen-civitas/esm-globals": "^2.
|
|
38
|
+
"@egen-civitas/esm-globals": "^2.2.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@swc/cli": "0.8.1",
|