@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,4 @@
|
|
|
1
|
+
export { createBridgeClient } from './client.js';
|
|
2
|
+
export type { BridgeClient, BridgeClientOptions, CallOptions, HostInfo } from './client.js';
|
|
3
|
+
export { DEFAULT_TIMEOUT_MS, HOST_PROTOCOL_NAME, MAX_MESSAGE_LENGTH, SUPPORTED_PROTOCOL, isSupportedProtocolVersion, parseHostMessage, } from './protocol.js';
|
|
4
|
+
export type { HostBridge, HostEvent, HostFailure, HostMessage, HostRequest, HostSuccess } from './protocol.js';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocole Host ↔ Shell, version 1 — voir docs/EGEN-FRAMEWORK-DEV.md, annexe 1.
|
|
3
|
+
* Transport : chaînes JSON UTF-8 via `window.egenHostBridge`.
|
|
4
|
+
*/
|
|
5
|
+
/** Valeur du champ `p` de chaque message : tout message sans elle est ignoré. */
|
|
6
|
+
export declare const HOST_PROTOCOL_NAME = "egen-host";
|
|
7
|
+
/** Versions du protocole que CE client sait parler. Un Host hors de cette plage est `INCOMPATIBLE`. */
|
|
8
|
+
export declare const SUPPORTED_PROTOCOL: {
|
|
9
|
+
readonly min: 1;
|
|
10
|
+
readonly max: 1;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Taille maximale d'un message JSON, en caractères (proposition de la doc, à valider sur appareil). Au-delà, le Host
|
|
14
|
+
* doit fournir une référence de fichier (`FileHandle`) plutôt qu'un binaire inline.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_MESSAGE_LENGTH: number;
|
|
17
|
+
/** Délai par défaut d'un appel avant `TIMEOUT`. */
|
|
18
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
19
|
+
/**
|
|
20
|
+
* Contrat de pont que le Host injecte AVANT tout script de la page : `window.egenHostBridge`.
|
|
21
|
+
* Le framework ne connaît rien d'autre du natif (règles R2 / N2).
|
|
22
|
+
*/
|
|
23
|
+
export interface HostBridge {
|
|
24
|
+
/** web → host : un message JSON. */
|
|
25
|
+
send(json: string): void;
|
|
26
|
+
/** host → web : le Host appelle `callback` avec un message JSON. Appelé UNE fois par le client. */
|
|
27
|
+
subscribe(callback: (json: string) => void): void;
|
|
28
|
+
}
|
|
29
|
+
export interface HostRequest {
|
|
30
|
+
p: typeof HOST_PROTOCOL_NAME;
|
|
31
|
+
v: number;
|
|
32
|
+
id: string;
|
|
33
|
+
ns: string;
|
|
34
|
+
m: string;
|
|
35
|
+
a?: unknown;
|
|
36
|
+
}
|
|
37
|
+
export interface HostSuccess {
|
|
38
|
+
p: typeof HOST_PROTOCOL_NAME;
|
|
39
|
+
v: number;
|
|
40
|
+
id: string;
|
|
41
|
+
ok: true;
|
|
42
|
+
r?: unknown;
|
|
43
|
+
}
|
|
44
|
+
export interface HostFailure {
|
|
45
|
+
p: typeof HOST_PROTOCOL_NAME;
|
|
46
|
+
v: number;
|
|
47
|
+
id: string;
|
|
48
|
+
ok: false;
|
|
49
|
+
/** Contenu NON fiable : validé par le client avant d'être converti en `PlatformError`. */
|
|
50
|
+
e: {
|
|
51
|
+
code?: unknown;
|
|
52
|
+
message?: unknown;
|
|
53
|
+
retryable?: unknown;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface HostEvent {
|
|
57
|
+
p: typeof HOST_PROTOCOL_NAME;
|
|
58
|
+
v: number;
|
|
59
|
+
ev: string;
|
|
60
|
+
d?: unknown;
|
|
61
|
+
}
|
|
62
|
+
export type HostMessage = HostSuccess | HostFailure | HostEvent;
|
|
63
|
+
export declare const KNOWN_ERROR_CODES: ReadonlySet<string>;
|
|
64
|
+
export declare function isSupportedProtocolVersion(version: unknown): version is number;
|
|
65
|
+
/**
|
|
66
|
+
* Décode un message du Host. Retourne `null` pour tout ce qui n'est pas un message du protocole : entrée non
|
|
67
|
+
* textuelle, trop longue, JSON invalide, autre valeur de `p`, forme inconnue. Ne lève JAMAIS et n'évalue rien.
|
|
68
|
+
*
|
|
69
|
+
* Le champ `v` n'est PAS filtré ici : le client doit pouvoir répondre « incompatible » à un appel en attente plutôt
|
|
70
|
+
* que de le laisser expirer.
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseHostMessage(raw: unknown): HostMessage | null;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocole Host ↔ Shell, version 1 — voir docs/EGEN-FRAMEWORK-DEV.md, annexe 1.
|
|
3
|
+
* Transport : chaînes JSON UTF-8 via `window.egenHostBridge`.
|
|
4
|
+
*/
|
|
5
|
+
/** Valeur du champ `p` de chaque message : tout message sans elle est ignoré. */
|
|
6
|
+
export const HOST_PROTOCOL_NAME = 'egen-host';
|
|
7
|
+
/** Versions du protocole que CE client sait parler. Un Host hors de cette plage est `INCOMPATIBLE`. */
|
|
8
|
+
export const SUPPORTED_PROTOCOL = { min: 1, max: 1 };
|
|
9
|
+
/**
|
|
10
|
+
* Taille maximale d'un message JSON, en caractères (proposition de la doc, à valider sur appareil). Au-delà, le Host
|
|
11
|
+
* doit fournir une référence de fichier (`FileHandle`) plutôt qu'un binaire inline.
|
|
12
|
+
*/
|
|
13
|
+
export const MAX_MESSAGE_LENGTH = 256 * 1024;
|
|
14
|
+
/** Délai par défaut d'un appel avant `TIMEOUT`. */
|
|
15
|
+
export const DEFAULT_TIMEOUT_MS = 30000;
|
|
16
|
+
export const KNOWN_ERROR_CODES = new Set([
|
|
17
|
+
'UNAVAILABLE',
|
|
18
|
+
'DENIED',
|
|
19
|
+
'CANCELLED',
|
|
20
|
+
'INVALID_ARGS',
|
|
21
|
+
'TIMEOUT',
|
|
22
|
+
'INCOMPATIBLE',
|
|
23
|
+
'HOST_ERROR',
|
|
24
|
+
]);
|
|
25
|
+
export function isSupportedProtocolVersion(version) {
|
|
26
|
+
return (Number.isInteger(version) &&
|
|
27
|
+
version >= SUPPORTED_PROTOCOL.min &&
|
|
28
|
+
version <= SUPPORTED_PROTOCOL.max);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Décode un message du Host. Retourne `null` pour tout ce qui n'est pas un message du protocole : entrée non
|
|
32
|
+
* textuelle, trop longue, JSON invalide, autre valeur de `p`, forme inconnue. Ne lève JAMAIS et n'évalue rien.
|
|
33
|
+
*
|
|
34
|
+
* Le champ `v` n'est PAS filtré ici : le client doit pouvoir répondre « incompatible » à un appel en attente plutôt
|
|
35
|
+
* que de le laisser expirer.
|
|
36
|
+
*/
|
|
37
|
+
export function parseHostMessage(raw) {
|
|
38
|
+
if (typeof raw !== 'string' || raw.length === 0 || raw.length > MAX_MESSAGE_LENGTH) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
let value;
|
|
42
|
+
try {
|
|
43
|
+
value = JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const message = value;
|
|
52
|
+
if (message.p !== HOST_PROTOCOL_NAME || typeof message.v !== 'number') {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (typeof message.id === 'string' && typeof message.ok === 'boolean') {
|
|
56
|
+
if (message.ok) {
|
|
57
|
+
return { p: HOST_PROTOCOL_NAME, v: message.v, id: message.id, ok: true, r: message.r };
|
|
58
|
+
}
|
|
59
|
+
const e = typeof message.e === 'object' && message.e !== null && !Array.isArray(message.e) ? message.e : {};
|
|
60
|
+
return { p: HOST_PROTOCOL_NAME, v: message.v, id: message.id, ok: false, e };
|
|
61
|
+
}
|
|
62
|
+
if (typeof message.ev === 'string') {
|
|
63
|
+
return { p: HOST_PROTOCOL_NAME, v: message.v, ev: message.ev, d: message.d };
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
function _object_spread(target) {
|
|
15
|
+
for(var i = 1; i < arguments.length; i++){
|
|
16
|
+
var source = arguments[i] != null ? arguments[i] : {};
|
|
17
|
+
var ownKeys = Object.keys(source);
|
|
18
|
+
if (typeof Object.getOwnPropertySymbols === "function") {
|
|
19
|
+
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
20
|
+
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
ownKeys.forEach(function(key) {
|
|
24
|
+
_define_property(target, key, source[key]);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
}
|
|
29
|
+
import { describe, expect, it } from 'vitest';
|
|
30
|
+
import { MAX_MESSAGE_LENGTH, isSupportedProtocolVersion, parseHostMessage } from './protocol';
|
|
31
|
+
var msg = function msg(extra) {
|
|
32
|
+
return JSON.stringify(_object_spread({
|
|
33
|
+
p: 'egen-host',
|
|
34
|
+
v: 1
|
|
35
|
+
}, extra));
|
|
36
|
+
};
|
|
37
|
+
describe('isSupportedProtocolVersion', function() {
|
|
38
|
+
it('accepte la v1 uniquement', function() {
|
|
39
|
+
expect(isSupportedProtocolVersion(1)).toBe(true);
|
|
40
|
+
expect(isSupportedProtocolVersion(0)).toBe(false);
|
|
41
|
+
expect(isSupportedProtocolVersion(2)).toBe(false);
|
|
42
|
+
expect(isSupportedProtocolVersion(1.5)).toBe(false);
|
|
43
|
+
expect(isSupportedProtocolVersion('1')).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('parseHostMessage', function() {
|
|
47
|
+
it('décode une réponse de succès', function() {
|
|
48
|
+
expect(parseHostMessage(msg({
|
|
49
|
+
id: 'a',
|
|
50
|
+
ok: true,
|
|
51
|
+
r: 42
|
|
52
|
+
}))).toEqual({
|
|
53
|
+
p: 'egen-host',
|
|
54
|
+
v: 1,
|
|
55
|
+
id: 'a',
|
|
56
|
+
ok: true,
|
|
57
|
+
r: 42
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it("décode une réponse d'échec et normalise un `e` absent en objet vide", function() {
|
|
61
|
+
expect(parseHostMessage(msg({
|
|
62
|
+
id: 'a',
|
|
63
|
+
ok: false,
|
|
64
|
+
e: {
|
|
65
|
+
code: 'DENIED'
|
|
66
|
+
}
|
|
67
|
+
}))).toMatchObject({
|
|
68
|
+
ok: false,
|
|
69
|
+
e: {
|
|
70
|
+
code: 'DENIED'
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
expect(parseHostMessage(msg({
|
|
74
|
+
id: 'a',
|
|
75
|
+
ok: false
|
|
76
|
+
}))).toMatchObject({
|
|
77
|
+
ok: false,
|
|
78
|
+
e: {}
|
|
79
|
+
});
|
|
80
|
+
expect(parseHostMessage(msg({
|
|
81
|
+
id: 'a',
|
|
82
|
+
ok: false,
|
|
83
|
+
e: 'x'
|
|
84
|
+
}))).toMatchObject({
|
|
85
|
+
ok: false,
|
|
86
|
+
e: {}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
it('décode un événement', function() {
|
|
90
|
+
expect(parseHostMessage(msg({
|
|
91
|
+
ev: 'lifecycle.resume',
|
|
92
|
+
d: {
|
|
93
|
+
a: 1
|
|
94
|
+
}
|
|
95
|
+
}))).toEqual({
|
|
96
|
+
p: 'egen-host',
|
|
97
|
+
v: 1,
|
|
98
|
+
ev: 'lifecycle.resume',
|
|
99
|
+
d: {
|
|
100
|
+
a: 1
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
it('ne filtre pas la version (le client répond « incompatible » plutôt que de laisser expirer)', function() {
|
|
105
|
+
expect(parseHostMessage(JSON.stringify({
|
|
106
|
+
p: 'egen-host',
|
|
107
|
+
v: 9,
|
|
108
|
+
id: 'a',
|
|
109
|
+
ok: true
|
|
110
|
+
}))).toMatchObject({
|
|
111
|
+
v: 9
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
it.each([
|
|
115
|
+
[
|
|
116
|
+
'non textuel',
|
|
117
|
+
42
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
'objet',
|
|
121
|
+
{}
|
|
122
|
+
],
|
|
123
|
+
[
|
|
124
|
+
'null',
|
|
125
|
+
null
|
|
126
|
+
],
|
|
127
|
+
[
|
|
128
|
+
'chaîne vide',
|
|
129
|
+
''
|
|
130
|
+
],
|
|
131
|
+
[
|
|
132
|
+
'JSON invalide',
|
|
133
|
+
'{pas du json'
|
|
134
|
+
],
|
|
135
|
+
[
|
|
136
|
+
'tableau',
|
|
137
|
+
'[]'
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
'nombre JSON',
|
|
141
|
+
'12'
|
|
142
|
+
],
|
|
143
|
+
[
|
|
144
|
+
'sans p',
|
|
145
|
+
JSON.stringify({
|
|
146
|
+
v: 1,
|
|
147
|
+
id: 'a',
|
|
148
|
+
ok: true
|
|
149
|
+
})
|
|
150
|
+
],
|
|
151
|
+
[
|
|
152
|
+
'mauvais p',
|
|
153
|
+
JSON.stringify({
|
|
154
|
+
p: 'autre',
|
|
155
|
+
v: 1,
|
|
156
|
+
id: 'a',
|
|
157
|
+
ok: true
|
|
158
|
+
})
|
|
159
|
+
],
|
|
160
|
+
[
|
|
161
|
+
'v non numérique',
|
|
162
|
+
JSON.stringify({
|
|
163
|
+
p: 'egen-host',
|
|
164
|
+
v: '1',
|
|
165
|
+
id: 'a',
|
|
166
|
+
ok: true
|
|
167
|
+
})
|
|
168
|
+
],
|
|
169
|
+
[
|
|
170
|
+
'ni réponse ni événement',
|
|
171
|
+
msg({
|
|
172
|
+
foo: 1
|
|
173
|
+
})
|
|
174
|
+
],
|
|
175
|
+
[
|
|
176
|
+
'id non textuel',
|
|
177
|
+
msg({
|
|
178
|
+
id: 5,
|
|
179
|
+
ok: true
|
|
180
|
+
})
|
|
181
|
+
],
|
|
182
|
+
[
|
|
183
|
+
'ok non booléen',
|
|
184
|
+
msg({
|
|
185
|
+
id: 'a',
|
|
186
|
+
ok: 'oui'
|
|
187
|
+
})
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
'ev non textuel',
|
|
191
|
+
msg({
|
|
192
|
+
ev: 5
|
|
193
|
+
})
|
|
194
|
+
]
|
|
195
|
+
])('ignore : %s', function(_label, raw) {
|
|
196
|
+
expect(parseHostMessage(raw)).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
it('ignore un message trop long', function() {
|
|
199
|
+
expect(parseHostMessage(msg({
|
|
200
|
+
id: 'a',
|
|
201
|
+
ok: true,
|
|
202
|
+
r: 'x'.repeat(MAX_MESSAGE_LENGTH)
|
|
203
|
+
}))).toBeNull();
|
|
204
|
+
});
|
|
205
|
+
it("n'évalue rien : un contenu qui ressemble à du code reste une donnée", function() {
|
|
206
|
+
var parsed = parseHostMessage(msg({
|
|
207
|
+
id: 'a',
|
|
208
|
+
ok: true,
|
|
209
|
+
r: 'alert(1)//</script>'
|
|
210
|
+
}));
|
|
211
|
+
expect(parsed).toMatchObject({
|
|
212
|
+
r: 'alert(1)//</script>'
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { CapabilityName } from '@egen-civitas/esm-globals';
|
|
2
|
-
import type { Platform, PlatformEnvironment } from './types.js';
|
|
3
|
-
/** Les capacités d'une plateforme, indexées par leur nom (
|
|
4
|
-
export type CapabilityMap =
|
|
2
|
+
import type { CapabilityInterfaces, Platform, PlatformEnvironment } from './types.js';
|
|
3
|
+
/** Les capacités d'une plateforme, indexées par leur nom (clés de `CapabilityInterfaces`). */
|
|
4
|
+
export type CapabilityMap = {
|
|
5
|
+
[K in keyof CapabilityInterfaces]: CapabilityInterfaces[K];
|
|
6
|
+
};
|
|
5
7
|
/** Implémentations (éventuellement partielles) fournies par un adaptateur ; ce qui manque reste `UNAVAILABLE`. */
|
|
6
8
|
export type CapabilityOverrides = {
|
|
7
|
-
[K in
|
|
9
|
+
[K in keyof CapabilityInterfaces]?: Partial<CapabilityInterfaces[K]>;
|
|
8
10
|
};
|
|
9
11
|
/**
|
|
10
|
-
* Toutes les capacités, sous forme d'objets dont chaque méthode échoue en `UNAVAILABLE`. Une capacité
|
|
11
|
-
* donc toujours comme objet (règle C2) ; c'est `has()` qui dit si elle est réellement fournie.
|
|
12
|
+
* Toutes les capacités DU REGISTRE, sous forme d'objets dont chaque méthode échoue en `UNAVAILABLE`. Une capacité
|
|
13
|
+
* absente EXISTE donc toujours comme objet (règle C2) ; c'est `has()` qui dit si elle est réellement fournie.
|
|
14
|
+
* Dérivé de `registry.ts` : ajouter une capacité au registre l'ajoute ici sans rien modifier.
|
|
12
15
|
*/
|
|
13
16
|
export declare function createUnavailableCapabilities(): CapabilityMap;
|
|
14
17
|
export interface AssemblePlatformOptions {
|
|
@@ -17,10 +20,15 @@ export interface AssemblePlatformOptions {
|
|
|
17
20
|
capabilities?: CapabilityOverrides;
|
|
18
21
|
/** Appelée à CHAQUE lecture de `platform.ready` (évite un rejet non observé si personne ne l'attend). */
|
|
19
22
|
ready: () => Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Filtre dynamique en plus de la présence d'un adaptateur : une capacité n'est disponible que si elle est fournie
|
|
25
|
+
* ET que ce prédicat l'accepte (natif : le Host l'a annoncée au handshake). Défaut : toujours vrai.
|
|
26
|
+
*/
|
|
27
|
+
available?: (capability: CapabilityName) => boolean;
|
|
20
28
|
}
|
|
21
29
|
/**
|
|
22
30
|
* Assemble un objet `Platform` : les capacités fournies remplacent celles indisponibles, et `has()` n'est vrai que pour
|
|
23
31
|
* elles. Point unique de construction, partagé par `getPlatform()`, les adaptateurs (C4, C5) et les mocks (C9) : ils
|
|
24
32
|
* ne peuvent donc pas diverger du contrat.
|
|
25
33
|
*/
|
|
26
|
-
export declare function assemblePlatform({ environment, capabilities, ready }: AssemblePlatformOptions): Platform;
|
|
34
|
+
export declare function assemblePlatform({ environment, capabilities, ready, available, }: AssemblePlatformOptions): Platform;
|
package/dist/capabilities.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { unavailable } from './errors.js';
|
|
2
|
+
import { getCapabilityDefinition, listCapabilityNames } from './registry.js';
|
|
2
3
|
/** Méthode asynchrone d'une capacité absente : REJETTE `UNAVAILABLE` (jamais de `null` ambigu). */
|
|
3
4
|
const rejecting = (capability, method) => () => Promise.reject(unavailable(capability, method));
|
|
4
5
|
/**
|
|
@@ -9,52 +10,27 @@ const throwing = (capability, method) => () => {
|
|
|
9
10
|
throw unavailable(capability, method);
|
|
10
11
|
};
|
|
11
12
|
/**
|
|
12
|
-
* Toutes les capacités, sous forme d'objets dont chaque méthode échoue en `UNAVAILABLE`. Une capacité
|
|
13
|
-
* donc toujours comme objet (règle C2) ; c'est `has()` qui dit si elle est réellement fournie.
|
|
13
|
+
* Toutes les capacités DU REGISTRE, sous forme d'objets dont chaque méthode échoue en `UNAVAILABLE`. Une capacité
|
|
14
|
+
* absente EXISTE donc toujours comme objet (règle C2) ; c'est `has()` qui dit si elle est réellement fournie.
|
|
15
|
+
* Dérivé de `registry.ts` : ajouter une capacité au registre l'ajoute ici sans rien modifier.
|
|
14
16
|
*/
|
|
15
17
|
export function createUnavailableCapabilities() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
set: rejecting('secureStorage', 'set'),
|
|
26
|
-
remove: rejecting('secureStorage', 'remove'),
|
|
27
|
-
},
|
|
28
|
-
openExternal: { open: rejecting('openExternal', 'open') },
|
|
29
|
-
lifecycle: {
|
|
30
|
-
onResume: throwing('lifecycle', 'onResume'),
|
|
31
|
-
onPause: throwing('lifecycle', 'onPause'),
|
|
32
|
-
},
|
|
33
|
-
permissions: {
|
|
34
|
-
check: rejecting('permissions', 'check'),
|
|
35
|
-
request: rejecting('permissions', 'request'),
|
|
36
|
-
},
|
|
37
|
-
files: {
|
|
38
|
-
select: rejecting('files', 'select'),
|
|
39
|
-
download: rejecting('files', 'download'),
|
|
40
|
-
},
|
|
41
|
-
camera: { capture: rejecting('camera', 'capture') },
|
|
42
|
-
push: {
|
|
43
|
-
register: rejecting('push', 'register'),
|
|
44
|
-
onMessage: throwing('push', 'onMessage'),
|
|
45
|
-
},
|
|
46
|
-
biometric: {
|
|
47
|
-
isAvailable: rejecting('biometric', 'isAvailable'),
|
|
48
|
-
authenticate: rejecting('biometric', 'authenticate'),
|
|
49
|
-
},
|
|
50
|
-
};
|
|
18
|
+
const capabilities = {};
|
|
19
|
+
for (const name of listCapabilityNames()) {
|
|
20
|
+
const methods = getCapabilityDefinition(name)?.methods ?? {};
|
|
21
|
+
capabilities[name] = Object.fromEntries(Object.entries(methods).map(([method, kind]) => [
|
|
22
|
+
method,
|
|
23
|
+
kind === 'subscribe' ? throwing(name, method) : rejecting(name, method),
|
|
24
|
+
]));
|
|
25
|
+
}
|
|
26
|
+
return capabilities;
|
|
51
27
|
}
|
|
52
28
|
/**
|
|
53
29
|
* Assemble un objet `Platform` : les capacités fournies remplacent celles indisponibles, et `has()` n'est vrai que pour
|
|
54
30
|
* elles. Point unique de construction, partagé par `getPlatform()`, les adaptateurs (C4, C5) et les mocks (C9) : ils
|
|
55
31
|
* ne peuvent donc pas diverger du contrat.
|
|
56
32
|
*/
|
|
57
|
-
export function assemblePlatform({ environment, capabilities = {}, ready }) {
|
|
33
|
+
export function assemblePlatform({ environment, capabilities = {}, ready, available, }) {
|
|
58
34
|
const base = createUnavailableCapabilities();
|
|
59
35
|
const provided = new Set(Object.keys(capabilities));
|
|
60
36
|
const merged = {};
|
|
@@ -66,7 +42,7 @@ export function assemblePlatform({ environment, capabilities = {}, ready }) {
|
|
|
66
42
|
get ready() {
|
|
67
43
|
return ready();
|
|
68
44
|
},
|
|
69
|
-
has: (capability) => provided.has(capability),
|
|
45
|
+
has: (capability) => provided.has(capability) && (available?.(capability) ?? true),
|
|
70
46
|
...merged,
|
|
71
47
|
};
|
|
72
48
|
}
|
package/dist/detect.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HostBridge } from './bridge/protocol.js';
|
|
1
2
|
import type { PlatformEnvironment } from './types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Environnement d'exécution courant. Source UNIQUE : `window.egenTarget`, posé au build.
|
|
@@ -9,10 +10,16 @@ export declare function getPlatformEnvironment(): PlatformEnvironment;
|
|
|
9
10
|
/** `true` en Android/iOS. */
|
|
10
11
|
export declare function isNativePlatform(): boolean;
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Ne vérifie que la FORME du pont : le handshake (version, capacités) est fait par le client de pont
|
|
13
|
+
* Le pont injecté par le Host (`window.egenHostBridge = { send, subscribe }`), ou `null` s'il est absent ou non
|
|
14
|
+
* conforme. Ne vérifie que la FORME du pont : le handshake (version, capacités) est fait par le client de pont.
|
|
14
15
|
* Jamais de détection par User-Agent ni par bibliothèque d'enveloppe (N4).
|
|
15
16
|
*
|
|
16
17
|
* @category Platform
|
|
17
18
|
*/
|
|
19
|
+
export declare function getHostBridge(): HostBridge | null;
|
|
20
|
+
/**
|
|
21
|
+
* `true` si le Host a injecté un pont conforme au contrat.
|
|
22
|
+
*
|
|
23
|
+
* @category Platform
|
|
24
|
+
*/
|
|
18
25
|
export declare function hasHostBridge(): boolean;
|
package/dist/detect.js
CHANGED
|
@@ -13,16 +13,26 @@ export function isNativePlatform() {
|
|
|
13
13
|
return isNativeTarget();
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* Ne vérifie que la FORME du pont : le handshake (version, capacités) est fait par le client de pont
|
|
16
|
+
* Le pont injecté par le Host (`window.egenHostBridge = { send, subscribe }`), ou `null` s'il est absent ou non
|
|
17
|
+
* conforme. Ne vérifie que la FORME du pont : le handshake (version, capacités) est fait par le client de pont.
|
|
18
18
|
* Jamais de détection par User-Agent ni par bibliothèque d'enveloppe (N4).
|
|
19
19
|
*
|
|
20
20
|
* @category Platform
|
|
21
21
|
*/
|
|
22
|
-
export function
|
|
22
|
+
export function getHostBridge() {
|
|
23
23
|
if (typeof window === 'undefined') {
|
|
24
|
-
return
|
|
24
|
+
return null;
|
|
25
25
|
}
|
|
26
26
|
const bridge = window.egenHostBridge;
|
|
27
|
-
return
|
|
27
|
+
return bridge && typeof bridge.send === 'function' && typeof bridge.subscribe === 'function'
|
|
28
|
+
? bridge
|
|
29
|
+
: null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `true` si le Host a injecté un pont conforme au contrat.
|
|
33
|
+
*
|
|
34
|
+
* @category Platform
|
|
35
|
+
*/
|
|
36
|
+
export function hasHostBridge() {
|
|
37
|
+
return getHostBridge() !== null;
|
|
28
38
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED