@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,329 @@
|
|
|
1
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
2
|
+
try {
|
|
3
|
+
var info = gen[key](arg);
|
|
4
|
+
var value = info.value;
|
|
5
|
+
} catch (error) {
|
|
6
|
+
reject(error);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (info.done) {
|
|
10
|
+
resolve(value);
|
|
11
|
+
} else {
|
|
12
|
+
Promise.resolve(value).then(_next, _throw);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function _async_to_generator(fn) {
|
|
16
|
+
return function() {
|
|
17
|
+
var self = this, args = arguments;
|
|
18
|
+
return new Promise(function(resolve, reject) {
|
|
19
|
+
var gen = fn.apply(self, args);
|
|
20
|
+
function _next(value) {
|
|
21
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
22
|
+
}
|
|
23
|
+
function _throw(err) {
|
|
24
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
25
|
+
}
|
|
26
|
+
_next(undefined);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function _ts_generator(thisArg, body) {
|
|
31
|
+
var f, y, t, _ = {
|
|
32
|
+
label: 0,
|
|
33
|
+
sent: function() {
|
|
34
|
+
if (t[0] & 1) throw t[1];
|
|
35
|
+
return t[1];
|
|
36
|
+
},
|
|
37
|
+
trys: [],
|
|
38
|
+
ops: []
|
|
39
|
+
}, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
|
|
40
|
+
return d(g, "next", {
|
|
41
|
+
value: verb(0)
|
|
42
|
+
}), d(g, "throw", {
|
|
43
|
+
value: verb(1)
|
|
44
|
+
}), d(g, "return", {
|
|
45
|
+
value: verb(2)
|
|
46
|
+
}), typeof Symbol === "function" && d(g, Symbol.iterator, {
|
|
47
|
+
value: function() {
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
}), g;
|
|
51
|
+
function verb(n) {
|
|
52
|
+
return function(v) {
|
|
53
|
+
return step([
|
|
54
|
+
n,
|
|
55
|
+
v
|
|
56
|
+
]);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function step(op) {
|
|
60
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
61
|
+
while(g && (g = 0, op[0] && (_ = 0)), _)try {
|
|
62
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
63
|
+
if (y = 0, t) op = [
|
|
64
|
+
op[0] & 2,
|
|
65
|
+
t.value
|
|
66
|
+
];
|
|
67
|
+
switch(op[0]){
|
|
68
|
+
case 0:
|
|
69
|
+
case 1:
|
|
70
|
+
t = op;
|
|
71
|
+
break;
|
|
72
|
+
case 4:
|
|
73
|
+
_.label++;
|
|
74
|
+
return {
|
|
75
|
+
value: op[1],
|
|
76
|
+
done: false
|
|
77
|
+
};
|
|
78
|
+
case 5:
|
|
79
|
+
_.label++;
|
|
80
|
+
y = op[1];
|
|
81
|
+
op = [
|
|
82
|
+
0
|
|
83
|
+
];
|
|
84
|
+
continue;
|
|
85
|
+
case 7:
|
|
86
|
+
op = _.ops.pop();
|
|
87
|
+
_.trys.pop();
|
|
88
|
+
continue;
|
|
89
|
+
default:
|
|
90
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
|
|
91
|
+
_ = 0;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
|
|
95
|
+
_.label = op[1];
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (op[0] === 6 && _.label < t[1]) {
|
|
99
|
+
_.label = t[1];
|
|
100
|
+
t = op;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
if (t && _.label < t[2]) {
|
|
104
|
+
_.label = t[2];
|
|
105
|
+
_.ops.push(op);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
if (t[2]) _.ops.pop();
|
|
109
|
+
_.trys.pop();
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
op = body.call(thisArg, _);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
op = [
|
|
115
|
+
6,
|
|
116
|
+
e
|
|
117
|
+
];
|
|
118
|
+
y = 0;
|
|
119
|
+
} finally{
|
|
120
|
+
f = t = 0;
|
|
121
|
+
}
|
|
122
|
+
if (op[0] & 5) throw op[1];
|
|
123
|
+
return {
|
|
124
|
+
value: op[0] ? op[1] : void 0,
|
|
125
|
+
done: true
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
130
|
+
import { getHostInfo, getPlatform, registerPlatformCapabilities } from './index';
|
|
131
|
+
import { createMockHost } from './testing';
|
|
132
|
+
afterEach(function() {
|
|
133
|
+
var w = window;
|
|
134
|
+
delete w.egenTarget;
|
|
135
|
+
delete w.egenHostBridge;
|
|
136
|
+
});
|
|
137
|
+
describe('getPlatform() natif — handshake avec le Host', function() {
|
|
138
|
+
it('ready attend le handshake ; getHostInfo() expose ce que le Host a annoncé', function() {
|
|
139
|
+
return _async_to_generator(function() {
|
|
140
|
+
var host;
|
|
141
|
+
return _ts_generator(this, function(_state) {
|
|
142
|
+
switch(_state.label){
|
|
143
|
+
case 0:
|
|
144
|
+
window.egenTarget = 'android';
|
|
145
|
+
host = createMockHost({
|
|
146
|
+
host: '4.2.0',
|
|
147
|
+
capabilities: {
|
|
148
|
+
secureStorage: {
|
|
149
|
+
v: 1
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
host.install();
|
|
154
|
+
expect(getHostInfo()).toBeNull(); // pas encore de handshake
|
|
155
|
+
return [
|
|
156
|
+
4,
|
|
157
|
+
getPlatform().ready
|
|
158
|
+
];
|
|
159
|
+
case 1:
|
|
160
|
+
_state.sent();
|
|
161
|
+
expect(getHostInfo()).toEqual({
|
|
162
|
+
protocol: 1,
|
|
163
|
+
host: '4.2.0',
|
|
164
|
+
platform: 'android',
|
|
165
|
+
capabilities: {
|
|
166
|
+
secureStorage: {
|
|
167
|
+
v: 1
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return [
|
|
172
|
+
2
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
})();
|
|
177
|
+
});
|
|
178
|
+
it('plusieurs getPlatform() partagent UN SEUL handshake et UN SEUL abonnement', function() {
|
|
179
|
+
return _async_to_generator(function() {
|
|
180
|
+
var host;
|
|
181
|
+
return _ts_generator(this, function(_state) {
|
|
182
|
+
switch(_state.label){
|
|
183
|
+
case 0:
|
|
184
|
+
window.egenTarget = 'android';
|
|
185
|
+
host = createMockHost();
|
|
186
|
+
host.install();
|
|
187
|
+
return [
|
|
188
|
+
4,
|
|
189
|
+
Promise.all([
|
|
190
|
+
getPlatform().ready,
|
|
191
|
+
getPlatform().ready,
|
|
192
|
+
getPlatform().ready
|
|
193
|
+
])
|
|
194
|
+
];
|
|
195
|
+
case 1:
|
|
196
|
+
_state.sent();
|
|
197
|
+
return [
|
|
198
|
+
4,
|
|
199
|
+
getPlatform().ready
|
|
200
|
+
];
|
|
201
|
+
case 2:
|
|
202
|
+
_state.sent();
|
|
203
|
+
expect(host.sent.filter(function(r) {
|
|
204
|
+
return r.m === 'hello';
|
|
205
|
+
})).toHaveLength(1);
|
|
206
|
+
expect(host.subscribeCount).toBe(1);
|
|
207
|
+
return [
|
|
208
|
+
2
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
})();
|
|
213
|
+
});
|
|
214
|
+
it('Host de version de protocole incompatible ⇒ ready rejette INCOMPATIBLE', function() {
|
|
215
|
+
return _async_to_generator(function() {
|
|
216
|
+
return _ts_generator(this, function(_state) {
|
|
217
|
+
switch(_state.label){
|
|
218
|
+
case 0:
|
|
219
|
+
window.egenTarget = 'ios';
|
|
220
|
+
createMockHost({
|
|
221
|
+
platform: 'ios',
|
|
222
|
+
protocol: 2
|
|
223
|
+
}).install();
|
|
224
|
+
return [
|
|
225
|
+
4,
|
|
226
|
+
expect(getPlatform().ready).rejects.toMatchObject({
|
|
227
|
+
code: 'INCOMPATIBLE'
|
|
228
|
+
})
|
|
229
|
+
];
|
|
230
|
+
case 1:
|
|
231
|
+
_state.sent();
|
|
232
|
+
expect(getHostInfo()).toBeNull();
|
|
233
|
+
return [
|
|
234
|
+
2
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
})();
|
|
239
|
+
});
|
|
240
|
+
it('Host qui annonce une autre plateforme que la cible du build ⇒ INCOMPATIBLE', function() {
|
|
241
|
+
return _async_to_generator(function() {
|
|
242
|
+
return _ts_generator(this, function(_state) {
|
|
243
|
+
switch(_state.label){
|
|
244
|
+
case 0:
|
|
245
|
+
window.egenTarget = 'ios';
|
|
246
|
+
createMockHost({
|
|
247
|
+
platform: 'android'
|
|
248
|
+
}).install();
|
|
249
|
+
return [
|
|
250
|
+
4,
|
|
251
|
+
expect(getPlatform().ready).rejects.toMatchObject({
|
|
252
|
+
code: 'INCOMPATIBLE'
|
|
253
|
+
})
|
|
254
|
+
];
|
|
255
|
+
case 1:
|
|
256
|
+
_state.sent();
|
|
257
|
+
return [
|
|
258
|
+
2
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
})();
|
|
263
|
+
});
|
|
264
|
+
it("tant que les adaptateurs natifs (C7) n'existent pas, aucune capacité n'est disponible — même annoncée par le Host", function() {
|
|
265
|
+
return _async_to_generator(function() {
|
|
266
|
+
var platform;
|
|
267
|
+
return _ts_generator(this, function(_state) {
|
|
268
|
+
switch(_state.label){
|
|
269
|
+
case 0:
|
|
270
|
+
window.egenTarget = 'android';
|
|
271
|
+
createMockHost({
|
|
272
|
+
capabilities: {
|
|
273
|
+
secureStorage: {
|
|
274
|
+
v: 1
|
|
275
|
+
},
|
|
276
|
+
session: {
|
|
277
|
+
v: 1
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}).install();
|
|
281
|
+
platform = getPlatform();
|
|
282
|
+
return [
|
|
283
|
+
4,
|
|
284
|
+
platform.ready
|
|
285
|
+
];
|
|
286
|
+
case 1:
|
|
287
|
+
_state.sent();
|
|
288
|
+
expect(platform.has('secureStorage')).toBe(false);
|
|
289
|
+
return [
|
|
290
|
+
4,
|
|
291
|
+
expect(platform.secureStorage.get('k')).rejects.toMatchObject({
|
|
292
|
+
code: 'UNAVAILABLE'
|
|
293
|
+
})
|
|
294
|
+
];
|
|
295
|
+
case 2:
|
|
296
|
+
_state.sent();
|
|
297
|
+
return [
|
|
298
|
+
2
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
})();
|
|
303
|
+
});
|
|
304
|
+
it('registerPlatformCapabilities() attend le handshake natif', function() {
|
|
305
|
+
return _async_to_generator(function() {
|
|
306
|
+
var host;
|
|
307
|
+
return _ts_generator(this, function(_state) {
|
|
308
|
+
switch(_state.label){
|
|
309
|
+
case 0:
|
|
310
|
+
window.egenTarget = 'android';
|
|
311
|
+
host = createMockHost();
|
|
312
|
+
host.install();
|
|
313
|
+
return [
|
|
314
|
+
4,
|
|
315
|
+
expect(registerPlatformCapabilities()).resolves.toBeUndefined()
|
|
316
|
+
];
|
|
317
|
+
case 1:
|
|
318
|
+
_state.sent();
|
|
319
|
+
expect(host.sent.some(function(r) {
|
|
320
|
+
return r.m === 'hello';
|
|
321
|
+
})).toBe(true);
|
|
322
|
+
return [
|
|
323
|
+
2
|
|
324
|
+
];
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
})();
|
|
328
|
+
});
|
|
329
|
+
});
|
package/dist/native.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type BridgeClient, type HostInfo } from './bridge/client.js';
|
|
2
|
+
import type { HostBridge } from './bridge/protocol.js';
|
|
3
|
+
import type { CapabilityOverrides } from './capabilities.js';
|
|
4
|
+
import type { PlatformEnvironment } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Client du pont donné. `getPlatform()` est appelé plusieurs fois (démarrage, apps) : sans ce partage, chaque appel
|
|
7
|
+
* referait un `subscribe` et un handshake. Un seul client par pont ⇒ un seul handshake (`hello()` est mémorisé : une
|
|
8
|
+
* incompatibilité reste rejetée, un échec passager comme un timeout peut être rejoué).
|
|
9
|
+
*/
|
|
10
|
+
export declare function getNativeClient(bridge: HostBridge, environment: PlatformEnvironment): BridgeClient;
|
|
11
|
+
/**
|
|
12
|
+
* Capacités natives implémentées par le framework, branchées sur le client de pont : celles dont l'entrée du registre
|
|
13
|
+
* (`registry.ts`) déclare un adaptateur `native`. AUCUNE tant que les adaptateurs natifs (tâche C7) ne sont pas écrits :
|
|
14
|
+
* `has()` reste faux partout, même si le Host annonce la capacité — on ne promet jamais ce que le framework ne sait
|
|
15
|
+
* pas encore appeler.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createNativeCapabilities(client: BridgeClient): CapabilityOverrides;
|
|
18
|
+
/**
|
|
19
|
+
* Ce que le Host a annoncé au handshake (version de protocole, version du Host, capacités), ou `null` si le
|
|
20
|
+
* handshake n'a pas (encore) réussi. Utile au diagnostic et à la validation de compatibilité.
|
|
21
|
+
*
|
|
22
|
+
* @category Platform
|
|
23
|
+
*/
|
|
24
|
+
export declare function getHostInfo(): HostInfo | null;
|
package/dist/native.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createBridgeClient } from './bridge/client.js';
|
|
2
|
+
import { getHostBridge } from './detect.js';
|
|
3
|
+
import { createNativeCapabilitiesFromRegistry } from './registry.js';
|
|
4
|
+
/** Un client de pont par pont : ses abonnements et son handshake sont partagés par tous les `getPlatform()` de la page. */
|
|
5
|
+
const clients = new WeakMap();
|
|
6
|
+
function frameworkVersion() {
|
|
7
|
+
try {
|
|
8
|
+
// Remplacé à la compilation par le DefinePlugin du shell ; absent (ReferenceError) hors bundle.
|
|
9
|
+
return process.env.FRAMEWORK_VERSION || undefined;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Client du pont donné. `getPlatform()` est appelé plusieurs fois (démarrage, apps) : sans ce partage, chaque appel
|
|
17
|
+
* referait un `subscribe` et un handshake. Un seul client par pont ⇒ un seul handshake (`hello()` est mémorisé : une
|
|
18
|
+
* incompatibilité reste rejetée, un échec passager comme un timeout peut être rejoué).
|
|
19
|
+
*/
|
|
20
|
+
export function getNativeClient(bridge, environment) {
|
|
21
|
+
let client = clients.get(bridge);
|
|
22
|
+
if (!client) {
|
|
23
|
+
client = createBridgeClient({ bridge, environment, framework: frameworkVersion() });
|
|
24
|
+
clients.set(bridge, client);
|
|
25
|
+
}
|
|
26
|
+
return client;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Capacités natives implémentées par le framework, branchées sur le client de pont : celles dont l'entrée du registre
|
|
30
|
+
* (`registry.ts`) déclare un adaptateur `native`. AUCUNE tant que les adaptateurs natifs (tâche C7) ne sont pas écrits :
|
|
31
|
+
* `has()` reste faux partout, même si le Host annonce la capacité — on ne promet jamais ce que le framework ne sait
|
|
32
|
+
* pas encore appeler.
|
|
33
|
+
*/
|
|
34
|
+
export function createNativeCapabilities(client) {
|
|
35
|
+
return createNativeCapabilitiesFromRegistry(client);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Ce que le Host a annoncé au handshake (version de protocole, version du Host, capacités), ou `null` si le
|
|
39
|
+
* handshake n'a pas (encore) réussi. Utile au diagnostic et à la validation de compatibilité.
|
|
40
|
+
*
|
|
41
|
+
* @category Platform
|
|
42
|
+
*/
|
|
43
|
+
export function getHostInfo() {
|
|
44
|
+
const bridge = getHostBridge();
|
|
45
|
+
return bridge ? (clients.get(bridge)?.hostInfo ?? null) : null;
|
|
46
|
+
}
|
package/dist/platform.d.ts
CHANGED
|
@@ -2,9 +2,12 @@ import type { Platform } from './types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Point d'accès unique des apps aux capacités de plateforme.
|
|
4
4
|
*
|
|
5
|
-
* La cible est fixée au build (`window.egenTarget`).
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* La cible est fixée au build (`window.egenTarget`).
|
|
6
|
+
* - Web : capacités du navigateur (adaptateur C4), rien à attendre.
|
|
7
|
+
* - Natif : le Host DOIT avoir injecté `window.egenHostBridge` avant tout script de la page, sinon `ready` rejette
|
|
8
|
+
* `INCOMPATIBLE` (« Host manquant »). `ready` est le handshake de protocole (version, plateforme, capacités). Une
|
|
9
|
+
* capacité n'est disponible que si le Host l'annonce ET que le framework sait l'appeler : tant que les adaptateurs
|
|
10
|
+
* natifs (C7) ne sont pas écrits, `has()` est faux partout et chaque méthode rejette `UNAVAILABLE`.
|
|
8
11
|
*
|
|
9
12
|
* @category Platform
|
|
10
13
|
*/
|
package/dist/platform.js
CHANGED
|
@@ -1,35 +1,44 @@
|
|
|
1
1
|
import { setCapabilityResolver } from '@egen-civitas/esm-globals';
|
|
2
|
-
import { createWebCapabilities } from './adapters/web/index.js';
|
|
3
2
|
import { assemblePlatform } from './capabilities.js';
|
|
4
|
-
import {
|
|
3
|
+
import { getHostBridge, getPlatformEnvironment } from './detect.js';
|
|
5
4
|
import { PlatformError } from './errors.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*/
|
|
11
|
-
function waitForHost(environment) {
|
|
12
|
-
if (environment === 'web' || hasHostBridge()) {
|
|
13
|
-
return Promise.resolve();
|
|
14
|
-
}
|
|
15
|
-
return Promise.reject(new PlatformError('INCOMPATIBLE', `Host manquant : la cible "${environment}" exige que l'application hôte injecte window.egenHostBridge avant le chargement de la page.`));
|
|
5
|
+
import { createNativeCapabilities, getNativeClient } from './native.js';
|
|
6
|
+
import { createWebCapabilities, isHostVersionSufficient } from './registry.js';
|
|
7
|
+
function hostMissing(environment) {
|
|
8
|
+
return new PlatformError('INCOMPATIBLE', `Host manquant : la cible "${environment}" exige que l'application hôte injecte window.egenHostBridge avant le chargement de la page.`);
|
|
16
9
|
}
|
|
17
10
|
/**
|
|
18
11
|
* Point d'accès unique des apps aux capacités de plateforme.
|
|
19
12
|
*
|
|
20
|
-
* La cible est fixée au build (`window.egenTarget`).
|
|
21
|
-
*
|
|
22
|
-
*
|
|
13
|
+
* La cible est fixée au build (`window.egenTarget`).
|
|
14
|
+
* - Web : capacités du navigateur (adaptateur C4), rien à attendre.
|
|
15
|
+
* - Natif : le Host DOIT avoir injecté `window.egenHostBridge` avant tout script de la page, sinon `ready` rejette
|
|
16
|
+
* `INCOMPATIBLE` (« Host manquant »). `ready` est le handshake de protocole (version, plateforme, capacités). Une
|
|
17
|
+
* capacité n'est disponible que si le Host l'annonce ET que le framework sait l'appeler : tant que les adaptateurs
|
|
18
|
+
* natifs (C7) ne sont pas écrits, `has()` est faux partout et chaque méthode rejette `UNAVAILABLE`.
|
|
23
19
|
*
|
|
24
20
|
* @category Platform
|
|
25
21
|
*/
|
|
26
22
|
export function getPlatform() {
|
|
27
23
|
const environment = getPlatformEnvironment();
|
|
24
|
+
if (environment === 'web') {
|
|
25
|
+
return assemblePlatform({
|
|
26
|
+
environment,
|
|
27
|
+
capabilities: createWebCapabilities(),
|
|
28
|
+
ready: () => Promise.resolve(),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const bridge = getHostBridge();
|
|
32
|
+
if (!bridge) {
|
|
33
|
+
return assemblePlatform({ environment, ready: () => Promise.reject(hostMissing(environment)) });
|
|
34
|
+
}
|
|
35
|
+
const client = getNativeClient(bridge, environment);
|
|
28
36
|
return assemblePlatform({
|
|
29
37
|
environment,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
capabilities: createNativeCapabilities(client),
|
|
39
|
+
ready: () => client.hello().then(() => undefined),
|
|
40
|
+
// Annoncée par le Host ET dans une version suffisante pour l'adaptateur de ce framework (registre).
|
|
41
|
+
available: (capability) => isHostVersionSufficient(capability, client.hostInfo?.capabilities[capability]?.v),
|
|
33
42
|
});
|
|
34
43
|
}
|
|
35
44
|
/**
|
package/dist/platform.test.js
CHANGED
|
@@ -190,7 +190,7 @@ function _ts_generator(thisArg, body) {
|
|
|
190
190
|
}
|
|
191
191
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
192
192
|
import { getPlatform, hasHostBridge, isPlatformError, PlatformError } from './index';
|
|
193
|
-
import { createMockPlatform } from './testing';
|
|
193
|
+
import { createMockHost, createMockPlatform } from './testing';
|
|
194
194
|
/** Toutes les capacités v1 avec leurs méthodes (annexe 2). Ajouter une capacité oblige à compléter ce tableau. */ var ASYNC_METHODS = [
|
|
195
195
|
[
|
|
196
196
|
'session',
|
|
@@ -505,16 +505,13 @@ describe('getPlatform() — environnement et handshake', function() {
|
|
|
505
505
|
});
|
|
506
506
|
})();
|
|
507
507
|
});
|
|
508
|
-
it('natif avec un
|
|
508
|
+
it('natif avec un Host conforme : ready se résout après le handshake', function() {
|
|
509
509
|
return _async_to_generator(function() {
|
|
510
510
|
return _ts_generator(this, function(_state) {
|
|
511
511
|
switch(_state.label){
|
|
512
512
|
case 0:
|
|
513
513
|
window.egenTarget = 'android';
|
|
514
|
-
|
|
515
|
-
send: function send() {},
|
|
516
|
-
subscribe: function subscribe() {}
|
|
517
|
-
};
|
|
514
|
+
createMockHost().install();
|
|
518
515
|
return [
|
|
519
516
|
4,
|
|
520
517
|
expect(getPlatform().ready).resolves.toBeUndefined()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { BridgeClient } from './bridge/client.js';
|
|
2
|
+
import type { CapabilityInterfaces } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Registre des capacités : POINT UNIQUE qui décrit chacune d'elles (tâche C6). Tout ce qui énumère des capacités
|
|
5
|
+
* (gabarit « indisponible », handshake avec le Host, adaptateurs Web et natif, mocks) le lit ici au lieu de tenir sa
|
|
6
|
+
* propre liste. Voir la procédure « ajouter une capacité » dans le README du package.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* `async` : méthode qui retourne une `Promise` (absente ⇒ rejette `UNAVAILABLE`).
|
|
10
|
+
* `subscribe` : abonnement synchrone qui retourne une fonction de désabonnement (absent ⇒ lève `UNAVAILABLE`,
|
|
11
|
+
* faute de pouvoir rejeter).
|
|
12
|
+
*/
|
|
13
|
+
export type MethodKind = 'async' | 'subscribe';
|
|
14
|
+
export interface CapabilityDefinition<T = Record<string, unknown>> {
|
|
15
|
+
/**
|
|
16
|
+
* Version de la capacité implémentée par ce framework. Le Host annonce la sienne au handshake (`{ v }`) : une
|
|
17
|
+
* capacité n'est disponible que si `v` du Host ≥ cette version.
|
|
18
|
+
*/
|
|
19
|
+
version: number;
|
|
20
|
+
/** Toutes les méthodes publiques de la capacité. Exhaustif à la compilation : un oubli ne compile pas. */
|
|
21
|
+
methods: {
|
|
22
|
+
[M in keyof T]: MethodKind;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Adaptateur Web : fabrique appelée à chaque `getPlatform()`. Retourne `undefined` si le navigateur ne la supporte
|
|
26
|
+
* pas (la capacité reste alors absente). Ne retourne jamais de simulation de sécurité.
|
|
27
|
+
*/
|
|
28
|
+
web?: () => Partial<T> | undefined;
|
|
29
|
+
/** Adaptateur natif : simple APPELANT du pont (aucune logique métier), branché sur le client du Host. */
|
|
30
|
+
native?: (client: BridgeClient) => Partial<T> | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Enregistre (ou remplace) une capacité. Les 9 capacités de la v1 sont enregistrées plus bas ; une capacité ajoutée
|
|
34
|
+
* ensuite passe par ici, depuis son propre fichier.
|
|
35
|
+
*/
|
|
36
|
+
export declare function registerCapability<K extends keyof CapabilityInterfaces>(name: K, definition: CapabilityDefinition<CapabilityInterfaces[K]>): void;
|
|
37
|
+
/** Retire une capacité du registre (réservé aux tests qui en ajoutent une fictive). */
|
|
38
|
+
export declare function unregisterCapability(name: string): boolean;
|
|
39
|
+
export declare function getCapabilityDefinition(name: string): CapabilityDefinition | undefined;
|
|
40
|
+
export declare function listCapabilityNames(): string[];
|
|
41
|
+
/** Le nom est-il celui d'une capacité connue de ce framework ? (un Host peut en annoncer d'autres : elles sont ignorées) */
|
|
42
|
+
export declare function isKnownCapability(name: string): boolean;
|
|
43
|
+
/** Le Host annonce-t-il une version suffisante de cette capacité ? */
|
|
44
|
+
export declare function isHostVersionSufficient(name: string, hostVersion: number | undefined): boolean;
|
|
45
|
+
/** Adaptateurs Web de toutes les capacités du registre dont le navigateur est capable. */
|
|
46
|
+
export declare function createWebCapabilities(): Partial<{
|
|
47
|
+
[K in keyof CapabilityInterfaces]: Partial<CapabilityInterfaces[K]>;
|
|
48
|
+
}>;
|
|
49
|
+
/** Adaptateurs natifs de toutes les capacités du registre, branchés sur le client de pont. */
|
|
50
|
+
export declare function createNativeCapabilitiesFromRegistry(client: BridgeClient): Partial<{
|
|
51
|
+
[K in keyof CapabilityInterfaces]: Partial<CapabilityInterfaces[K]>;
|
|
52
|
+
}>;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createWebCamera } from './adapters/web/camera.js';
|
|
2
|
+
import { createWebFiles } from './adapters/web/files.js';
|
|
3
|
+
import { createWebLifecycle } from './adapters/web/lifecycle.js';
|
|
4
|
+
import { createWebOpenExternal } from './adapters/web/open-external.js';
|
|
5
|
+
import { createWebPermissions, isWebPermissionsAvailable } from './adapters/web/permissions.js';
|
|
6
|
+
import { createWebSession } from './adapters/web/session.js';
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
const definitions = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Enregistre (ou remplace) une capacité. Les 9 capacités de la v1 sont enregistrées plus bas ; une capacité ajoutée
|
|
11
|
+
* ensuite passe par ici, depuis son propre fichier.
|
|
12
|
+
*/
|
|
13
|
+
export function registerCapability(name, definition) {
|
|
14
|
+
definitions.set(name, definition);
|
|
15
|
+
}
|
|
16
|
+
/** Retire une capacité du registre (réservé aux tests qui en ajoutent une fictive). */
|
|
17
|
+
export function unregisterCapability(name) {
|
|
18
|
+
return definitions.delete(name);
|
|
19
|
+
}
|
|
20
|
+
export function getCapabilityDefinition(name) {
|
|
21
|
+
return definitions.get(name);
|
|
22
|
+
}
|
|
23
|
+
export function listCapabilityNames() {
|
|
24
|
+
return [...definitions.keys()];
|
|
25
|
+
}
|
|
26
|
+
/** Le nom est-il celui d'une capacité connue de ce framework ? (un Host peut en annoncer d'autres : elles sont ignorées) */
|
|
27
|
+
export function isKnownCapability(name) {
|
|
28
|
+
return definitions.has(name);
|
|
29
|
+
}
|
|
30
|
+
/** Le Host annonce-t-il une version suffisante de cette capacité ? */
|
|
31
|
+
export function isHostVersionSufficient(name, hostVersion) {
|
|
32
|
+
const definition = definitions.get(name);
|
|
33
|
+
return definition !== undefined && hostVersion !== undefined && hostVersion >= definition.version;
|
|
34
|
+
}
|
|
35
|
+
/** Adaptateurs Web de toutes les capacités du registre dont le navigateur est capable. */
|
|
36
|
+
export function createWebCapabilities() {
|
|
37
|
+
const capabilities = {};
|
|
38
|
+
for (const [name, definition] of definitions) {
|
|
39
|
+
const adapter = definition.web?.();
|
|
40
|
+
if (adapter) {
|
|
41
|
+
capabilities[name] = adapter;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return capabilities;
|
|
45
|
+
}
|
|
46
|
+
/** Adaptateurs natifs de toutes les capacités du registre, branchés sur le client de pont. */
|
|
47
|
+
export function createNativeCapabilitiesFromRegistry(client) {
|
|
48
|
+
const capabilities = {};
|
|
49
|
+
for (const [name, definition] of definitions) {
|
|
50
|
+
const adapter = definition.native?.(client);
|
|
51
|
+
if (adapter) {
|
|
52
|
+
capabilities[name] = adapter;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return capabilities;
|
|
56
|
+
}
|
|
57
|
+
// ── Capacités v1 ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
// Les adaptateurs natifs (`native`) sont l'objet de la tâche C7 (une capacité à la fois, quand une app l'utilise, N9).
|
|
59
|
+
registerCapability('session', {
|
|
60
|
+
version: 1,
|
|
61
|
+
methods: { getAuthorization: 'async', setTokens: 'async', refresh: 'async', clear: 'async' },
|
|
62
|
+
web: createWebSession,
|
|
63
|
+
});
|
|
64
|
+
registerCapability('secureStorage', {
|
|
65
|
+
version: 1,
|
|
66
|
+
methods: { get: 'async', set: 'async', remove: 'async' },
|
|
67
|
+
// Pas d'adaptateur Web : jamais de faux coffre dans le navigateur.
|
|
68
|
+
});
|
|
69
|
+
registerCapability('openExternal', {
|
|
70
|
+
version: 1,
|
|
71
|
+
methods: { open: 'async' },
|
|
72
|
+
web: createWebOpenExternal,
|
|
73
|
+
});
|
|
74
|
+
registerCapability('lifecycle', {
|
|
75
|
+
version: 1,
|
|
76
|
+
methods: { onResume: 'subscribe', onPause: 'subscribe' },
|
|
77
|
+
web: createWebLifecycle,
|
|
78
|
+
});
|
|
79
|
+
registerCapability('permissions', {
|
|
80
|
+
version: 1,
|
|
81
|
+
methods: { check: 'async', request: 'async' },
|
|
82
|
+
web: () => (isWebPermissionsAvailable() ? createWebPermissions() : undefined),
|
|
83
|
+
});
|
|
84
|
+
registerCapability('files', {
|
|
85
|
+
version: 1,
|
|
86
|
+
methods: { select: 'async', download: 'async' },
|
|
87
|
+
web: createWebFiles,
|
|
88
|
+
});
|
|
89
|
+
registerCapability('camera', {
|
|
90
|
+
version: 1,
|
|
91
|
+
methods: { capture: 'async' },
|
|
92
|
+
web: createWebCamera,
|
|
93
|
+
});
|
|
94
|
+
registerCapability('push', {
|
|
95
|
+
version: 1,
|
|
96
|
+
methods: { register: 'async', onMessage: 'subscribe' },
|
|
97
|
+
});
|
|
98
|
+
registerCapability('biometric', {
|
|
99
|
+
version: 1,
|
|
100
|
+
methods: { isAvailable: 'async', authenticate: 'async' },
|
|
101
|
+
});
|