aether-wv 0.0.0-stage → 1.0.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 +201 -0
- package/README.md +313 -2
- package/bin/aetherc.js +4 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/packages/client/src/client.d.ts +26 -0
- package/dist/packages/client/src/client.d.ts.map +1 -0
- package/dist/packages/client/src/client.js +58 -0
- package/dist/packages/client/src/client.js.map +1 -0
- package/dist/packages/client/src/index.d.ts +8 -0
- package/dist/packages/client/src/index.d.ts.map +1 -0
- package/dist/packages/client/src/index.js +8 -0
- package/dist/packages/client/src/index.js.map +1 -0
- package/dist/packages/client/src/transports/android.d.ts +10 -0
- package/dist/packages/client/src/transports/android.d.ts.map +1 -0
- package/dist/packages/client/src/transports/android.js +38 -0
- package/dist/packages/client/src/transports/android.js.map +1 -0
- package/dist/packages/client/src/transports/electron.d.ts +39 -0
- package/dist/packages/client/src/transports/electron.d.ts.map +1 -0
- package/dist/packages/client/src/transports/electron.js +51 -0
- package/dist/packages/client/src/transports/electron.js.map +1 -0
- package/dist/packages/client/src/transports/ios.d.ts +9 -0
- package/dist/packages/client/src/transports/ios.d.ts.map +1 -0
- package/dist/packages/client/src/transports/ios.js +37 -0
- package/dist/packages/client/src/transports/ios.js.map +1 -0
- package/dist/packages/client/src/transports/memory.d.ts +10 -0
- package/dist/packages/client/src/transports/memory.d.ts.map +1 -0
- package/dist/packages/client/src/transports/memory.js +29 -0
- package/dist/packages/client/src/transports/memory.js.map +1 -0
- package/dist/packages/client/src/transports/transport.d.ts +3 -0
- package/dist/packages/client/src/transports/transport.d.ts.map +1 -0
- package/dist/packages/client/src/transports/transport.js +2 -0
- package/dist/packages/client/src/transports/transport.js.map +1 -0
- package/dist/packages/client/src/transports/webview2.d.ts +25 -0
- package/dist/packages/client/src/transports/webview2.d.ts.map +1 -0
- package/dist/packages/client/src/transports/webview2.js +45 -0
- package/dist/packages/client/src/transports/webview2.js.map +1 -0
- package/dist/packages/compiler/src/ast.d.ts +27 -0
- package/dist/packages/compiler/src/ast.d.ts.map +1 -0
- package/dist/packages/compiler/src/ast.js +2 -0
- package/dist/packages/compiler/src/ast.js.map +1 -0
- package/dist/packages/compiler/src/cli.d.ts +2 -0
- package/dist/packages/compiler/src/cli.d.ts.map +1 -0
- package/dist/packages/compiler/src/cli.js +68 -0
- package/dist/packages/compiler/src/cli.js.map +1 -0
- package/dist/packages/compiler/src/codegen-kotlin.d.ts +3 -0
- package/dist/packages/compiler/src/codegen-kotlin.d.ts.map +1 -0
- package/dist/packages/compiler/src/codegen-kotlin.js +119 -0
- package/dist/packages/compiler/src/codegen-kotlin.js.map +1 -0
- package/dist/packages/compiler/src/codegen-swift.d.ts +3 -0
- package/dist/packages/compiler/src/codegen-swift.d.ts.map +1 -0
- package/dist/packages/compiler/src/codegen-swift.js +129 -0
- package/dist/packages/compiler/src/codegen-swift.js.map +1 -0
- package/dist/packages/compiler/src/codegen-ts.d.ts +3 -0
- package/dist/packages/compiler/src/codegen-ts.d.ts.map +1 -0
- package/dist/packages/compiler/src/codegen-ts.js +121 -0
- package/dist/packages/compiler/src/codegen-ts.js.map +1 -0
- package/dist/packages/compiler/src/index.d.ts +7 -0
- package/dist/packages/compiler/src/index.d.ts.map +1 -0
- package/dist/packages/compiler/src/index.js +7 -0
- package/dist/packages/compiler/src/index.js.map +1 -0
- package/dist/packages/compiler/src/parser.d.ts +3 -0
- package/dist/packages/compiler/src/parser.d.ts.map +1 -0
- package/dist/packages/compiler/src/parser.js +80 -0
- package/dist/packages/compiler/src/parser.js.map +1 -0
- package/dist/packages/core/src/index.d.ts +7 -0
- package/dist/packages/core/src/index.d.ts.map +1 -0
- package/dist/packages/core/src/index.js +7 -0
- package/dist/packages/core/src/index.js.map +1 -0
- package/dist/packages/core/src/multiplexer/backpressure.d.ts +41 -0
- package/dist/packages/core/src/multiplexer/backpressure.d.ts.map +1 -0
- package/dist/packages/core/src/multiplexer/backpressure.js +80 -0
- package/dist/packages/core/src/multiplexer/backpressure.js.map +1 -0
- package/dist/packages/core/src/multiplexer/multiplexer.d.ts +36 -0
- package/dist/packages/core/src/multiplexer/multiplexer.d.ts.map +1 -0
- package/dist/packages/core/src/multiplexer/multiplexer.js +238 -0
- package/dist/packages/core/src/multiplexer/multiplexer.js.map +1 -0
- package/dist/packages/core/src/protocol/frames.d.ts +46 -0
- package/dist/packages/core/src/protocol/frames.d.ts.map +1 -0
- package/dist/packages/core/src/protocol/frames.js +89 -0
- package/dist/packages/core/src/protocol/frames.js.map +1 -0
- package/dist/packages/core/src/protocol/serializer.d.ts +38 -0
- package/dist/packages/core/src/protocol/serializer.d.ts.map +1 -0
- package/dist/packages/core/src/protocol/serializer.js +145 -0
- package/dist/packages/core/src/protocol/serializer.js.map +1 -0
- package/dist/packages/core/src/security/capability.d.ts +18 -0
- package/dist/packages/core/src/security/capability.d.ts.map +1 -0
- package/dist/packages/core/src/security/capability.js +56 -0
- package/dist/packages/core/src/security/capability.js.map +1 -0
- package/dist/packages/core/src/session/session.d.ts +29 -0
- package/dist/packages/core/src/session/session.d.ts.map +1 -0
- package/dist/packages/core/src/session/session.js +52 -0
- package/dist/packages/core/src/session/session.js.map +1 -0
- package/dist/packages/native-host/src/host.d.ts +30 -0
- package/dist/packages/native-host/src/host.d.ts.map +1 -0
- package/dist/packages/native-host/src/host.js +232 -0
- package/dist/packages/native-host/src/host.js.map +1 -0
- package/dist/packages/native-host/src/index.d.ts +2 -0
- package/dist/packages/native-host/src/index.d.ts.map +1 -0
- package/dist/packages/native-host/src/index.js +2 -0
- package/dist/packages/native-host/src/index.js.map +1 -0
- package/package.json +37 -4
- package/packages/platform-templates/android/AetherNativeDriver.kt +57 -0
- package/packages/platform-templates/ios/AetherNativeDriver.swift +78 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
2
|
+
export class CapabilityGatekeeper {
|
|
3
|
+
secretKey;
|
|
4
|
+
constructor(secretKey = 'aether-internal-secret') {
|
|
5
|
+
this.secretKey = secretKey;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generates a signed capability token for a trusted mini-app origin
|
|
9
|
+
*/
|
|
10
|
+
generateToken(manifest) {
|
|
11
|
+
const payload = JSON.stringify(manifest);
|
|
12
|
+
const signature = createHmac('sha256', this.secretKey).update(payload).digest('hex');
|
|
13
|
+
const b64Payload = Buffer.from(payload).toString('base64url');
|
|
14
|
+
return `${b64Payload}.${signature}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Verifies a capability token and checks if the service/method is authorized
|
|
18
|
+
*/
|
|
19
|
+
authorize(token, currentOrigin, serviceName, methodName) {
|
|
20
|
+
if (!token)
|
|
21
|
+
return false;
|
|
22
|
+
const parts = token.split('.');
|
|
23
|
+
if (parts.length !== 2)
|
|
24
|
+
return false;
|
|
25
|
+
const [b64Payload, signature] = parts;
|
|
26
|
+
const payload = Buffer.from(b64Payload, 'base64url').toString('utf8');
|
|
27
|
+
const expectedSig = createHmac('sha256', this.secretKey).update(payload).digest('hex');
|
|
28
|
+
if (signature !== expectedSig) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const manifest = JSON.parse(payload);
|
|
33
|
+
if (manifest.expiresAt < Date.now()) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (manifest.origin !== '*' && currentOrigin !== '*' && manifest.origin !== currentOrigin) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
// Check wildcards and exact service matches
|
|
40
|
+
const target = `${serviceName}.${methodName}`;
|
|
41
|
+
return manifest.allowedServices.some((pattern) => {
|
|
42
|
+
if (pattern === '*')
|
|
43
|
+
return true;
|
|
44
|
+
if (pattern === serviceName || pattern === `${serviceName}.*`)
|
|
45
|
+
return true;
|
|
46
|
+
if (pattern === target)
|
|
47
|
+
return true;
|
|
48
|
+
return false;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../../../../../packages/core/src/security/capability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAQzC,MAAM,OAAO,oBAAoB;IACF;IAA7B,YAA6B,YAAoB,wBAAwB;QAA5C,cAAS,GAAT,SAAS,CAAmC;IAAG,CAAC;IAE7E;;OAEG;IACH,aAAa,CAAC,QAA4B;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACrF,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC9D,OAAO,GAAG,UAAU,IAAI,SAAS,EAAE,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,KAAa,EAAE,aAAqB,EAAE,WAAmB,EAAE,UAAkB;QACrF,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAErC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC;QACtC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACtE,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEvF,IAAI,SAAS,KAAK,WAAW,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAuB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACzD,IAAI,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBACpC,OAAO,KAAK,CAAC;YACf,CAAC;YACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,aAAa,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;gBAC1F,OAAO,KAAK,CAAC;YACf,CAAC;YAED,4CAA4C;YAC5C,MAAM,MAAM,GAAG,GAAG,WAAW,IAAI,UAAU,EAAE,CAAC;YAC9C,OAAO,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC/C,IAAI,OAAO,KAAK,GAAG;oBAAE,OAAO,IAAI,CAAC;gBACjC,IAAI,OAAO,KAAK,WAAW,IAAI,OAAO,KAAK,GAAG,WAAW,IAAI;oBAAE,OAAO,IAAI,CAAC;gBAC3E,IAAI,OAAO,KAAK,MAAM;oBAAE,OAAO,IAAI,CAAC;gBACpC,OAAO,KAAK,CAAC;YACf,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AetherFrame } from '../protocol/frames.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ring buffer of recent frames for session resumption following WebContent process OOM reload.
|
|
4
|
+
*/
|
|
5
|
+
export declare class SessionRingBuffer {
|
|
6
|
+
private readonly capacity;
|
|
7
|
+
private buffer;
|
|
8
|
+
constructor(capacity?: number);
|
|
9
|
+
add(frame: AetherFrame): void;
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves all frames with sequenceId > lastAckSeq
|
|
12
|
+
*/
|
|
13
|
+
getFramesAfter(lastAckSeq: number): AetherFrame[];
|
|
14
|
+
clear(): void;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionState {
|
|
17
|
+
sessionId: string;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
lastActive: number;
|
|
20
|
+
lastSequenceId: number;
|
|
21
|
+
ringBuffer: SessionRingBuffer;
|
|
22
|
+
}
|
|
23
|
+
export declare class SessionRegistry {
|
|
24
|
+
private sessions;
|
|
25
|
+
getOrCreateSession(sessionId: string): SessionState;
|
|
26
|
+
getSession(sessionId: string): SessionState | undefined;
|
|
27
|
+
deleteSession(sessionId: string): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../../../packages/core/src/session/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD;;GAEG;AACH,qBAAa,iBAAiB;IAGhB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,OAAO,CAAC,MAAM,CAAqB;gBAEN,QAAQ,GAAE,MAAY;IAEnD,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAO7B;;OAEG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE;IAIjD,KAAK,IAAI,IAAI;CAGd;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,iBAAiB,CAAC;CAC/B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAmC;IAEnD,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY;IAiBnD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIvD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAGvC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ring buffer of recent frames for session resumption following WebContent process OOM reload.
|
|
3
|
+
*/
|
|
4
|
+
export class SessionRingBuffer {
|
|
5
|
+
capacity;
|
|
6
|
+
buffer = [];
|
|
7
|
+
constructor(capacity = 256) {
|
|
8
|
+
this.capacity = capacity;
|
|
9
|
+
}
|
|
10
|
+
add(frame) {
|
|
11
|
+
if (this.buffer.length >= this.capacity) {
|
|
12
|
+
this.buffer.shift();
|
|
13
|
+
}
|
|
14
|
+
this.buffer.push(frame);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves all frames with sequenceId > lastAckSeq
|
|
18
|
+
*/
|
|
19
|
+
getFramesAfter(lastAckSeq) {
|
|
20
|
+
return this.buffer.filter((f) => f.sequenceId > lastAckSeq);
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.buffer = [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class SessionRegistry {
|
|
27
|
+
sessions = new Map();
|
|
28
|
+
getOrCreateSession(sessionId) {
|
|
29
|
+
let session = this.sessions.get(sessionId);
|
|
30
|
+
if (!session) {
|
|
31
|
+
session = {
|
|
32
|
+
sessionId,
|
|
33
|
+
createdAt: Date.now(),
|
|
34
|
+
lastActive: Date.now(),
|
|
35
|
+
lastSequenceId: 0,
|
|
36
|
+
ringBuffer: new SessionRingBuffer(256),
|
|
37
|
+
};
|
|
38
|
+
this.sessions.set(sessionId, session);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
session.lastActive = Date.now();
|
|
42
|
+
}
|
|
43
|
+
return session;
|
|
44
|
+
}
|
|
45
|
+
getSession(sessionId) {
|
|
46
|
+
return this.sessions.get(sessionId);
|
|
47
|
+
}
|
|
48
|
+
deleteSession(sessionId) {
|
|
49
|
+
this.sessions.delete(sessionId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../../../packages/core/src/session/session.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,iBAAiB;IAGC;IAFrB,MAAM,GAAkB,EAAE,CAAC;IAEnC,YAA6B,WAAmB,GAAG;QAAtB,aAAQ,GAAR,QAAQ,CAAc;IAAG,CAAC;IAEvD,GAAG,CAAC,KAAkB;QACpB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,cAAc,CAAC,UAAkB;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;CACF;AAUD,MAAM,OAAO,eAAe;IAClB,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;IAEnD,kBAAkB,CAAC,SAAiB;QAClC,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG;gBACR,SAAS;gBACT,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;gBACtB,cAAc,EAAE,CAAC;gBACjB,UAAU,EAAE,IAAI,iBAAiB,CAAC,GAAG,CAAC;aACvC,CAAC;YACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAClC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,aAAa,CAAC,SAAiB;QAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;CACF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Transport, CapabilityGatekeeper, SessionRegistry } from '../../core/src/index.js';
|
|
2
|
+
export interface StreamEmitter {
|
|
3
|
+
send(payload: Uint8Array): Promise<void>;
|
|
4
|
+
close(): void;
|
|
5
|
+
error(err: Error): void;
|
|
6
|
+
onCancel(cb: () => void): void;
|
|
7
|
+
}
|
|
8
|
+
export type RpcHandler = (payload: Uint8Array) => Promise<Uint8Array>;
|
|
9
|
+
export type StreamHandler = (payload: Uint8Array, emitter: StreamEmitter) => void;
|
|
10
|
+
export declare class AetherNativeHost {
|
|
11
|
+
private transport;
|
|
12
|
+
private rpcHandlers;
|
|
13
|
+
private streamHandlers;
|
|
14
|
+
private activeEmitters;
|
|
15
|
+
private streamControllers;
|
|
16
|
+
private sequenceId;
|
|
17
|
+
gatekeeper: CapabilityGatekeeper;
|
|
18
|
+
sessionRegistry: SessionRegistry;
|
|
19
|
+
constructor(transport: Transport, secretKey?: string);
|
|
20
|
+
registerRpc(service: string, method: string, handler: RpcHandler): void;
|
|
21
|
+
registerStream(service: string, method: string, handler: StreamHandler): void;
|
|
22
|
+
private handleRawBytes;
|
|
23
|
+
private sendFrame;
|
|
24
|
+
private sendError;
|
|
25
|
+
private handleFrame;
|
|
26
|
+
private handleRpcRequest;
|
|
27
|
+
private handleStreamInit;
|
|
28
|
+
private handleHandshake;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../../../packages/native-host/src/host.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAST,oBAAoB,EACpB,eAAe,EAChB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,KAAK,IAAI,IAAI,CAAC;IACd,KAAK,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;AAElF,qBAAa,gBAAgB;IAWzB,OAAO,CAAC,SAAS;IAVnB,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,iBAAiB,CAA6C;IACtE,OAAO,CAAC,UAAU,CAAK;IAEhB,UAAU,EAAE,oBAAoB,CAAC;IACjC,eAAe,EAAE,eAAe,CAAC;gBAG9B,SAAS,EAAE,SAAS,EAC5B,SAAS,CAAC,EAAE,MAAM;IAQpB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIvE,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI;IAI7E,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,WAAW;IA4CnB,OAAO,CAAC,gBAAgB;IAwCxB,OAAO,CAAC,gBAAgB;IA8ExB,OAAO,CAAC,eAAe;CAyCxB"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { FrameType, FrameFlags, encodeFrame, decodeFrame, BinaryReader, BinaryWriter, StreamCreditController, CapabilityGatekeeper, SessionRegistry, } from '../../core/src/index.js';
|
|
2
|
+
export class AetherNativeHost {
|
|
3
|
+
transport;
|
|
4
|
+
rpcHandlers = new Map();
|
|
5
|
+
streamHandlers = new Map();
|
|
6
|
+
activeEmitters = new Map();
|
|
7
|
+
streamControllers = new Map();
|
|
8
|
+
sequenceId = 1;
|
|
9
|
+
gatekeeper;
|
|
10
|
+
sessionRegistry;
|
|
11
|
+
constructor(transport, secretKey) {
|
|
12
|
+
this.transport = transport;
|
|
13
|
+
this.gatekeeper = new CapabilityGatekeeper(secretKey);
|
|
14
|
+
this.sessionRegistry = new SessionRegistry();
|
|
15
|
+
this.transport.onReceive((data) => this.handleRawBytes(data));
|
|
16
|
+
}
|
|
17
|
+
registerRpc(service, method, handler) {
|
|
18
|
+
this.rpcHandlers.set(`${service}.${method}`, handler);
|
|
19
|
+
}
|
|
20
|
+
registerStream(service, method, handler) {
|
|
21
|
+
this.streamHandlers.set(`${service}.${method}`, handler);
|
|
22
|
+
}
|
|
23
|
+
handleRawBytes(bytes) {
|
|
24
|
+
const frame = decodeFrame(bytes);
|
|
25
|
+
this.handleFrame(frame);
|
|
26
|
+
}
|
|
27
|
+
sendFrame(frame, sessionId) {
|
|
28
|
+
if (sessionId) {
|
|
29
|
+
const session = this.sessionRegistry.getOrCreateSession(sessionId);
|
|
30
|
+
session.ringBuffer.add(frame);
|
|
31
|
+
}
|
|
32
|
+
const bytes = encodeFrame(frame);
|
|
33
|
+
this.transport.send(bytes);
|
|
34
|
+
}
|
|
35
|
+
sendError(streamId, message, sessionId) {
|
|
36
|
+
const errorFrame = {
|
|
37
|
+
type: FrameType.ERROR,
|
|
38
|
+
flags: FrameFlags.NONE,
|
|
39
|
+
streamId,
|
|
40
|
+
sequenceId: this.sequenceId++,
|
|
41
|
+
payload: new TextEncoder().encode(message),
|
|
42
|
+
};
|
|
43
|
+
this.sendFrame(errorFrame, sessionId);
|
|
44
|
+
}
|
|
45
|
+
handleFrame(frame) {
|
|
46
|
+
switch (frame.type) {
|
|
47
|
+
case FrameType.RPC_REQ: {
|
|
48
|
+
this.handleRpcRequest(frame);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case FrameType.STREAM_DATA: {
|
|
52
|
+
this.handleStreamInit(frame);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case FrameType.FLOW_ACK: {
|
|
56
|
+
const credits = new DataView(frame.payload.buffer, frame.payload.byteOffset).getUint32(0, false);
|
|
57
|
+
const controller = this.streamControllers.get(frame.streamId);
|
|
58
|
+
if (controller) {
|
|
59
|
+
controller.grantCredits(credits);
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case FrameType.STREAM_CANCEL: {
|
|
64
|
+
const emitter = this.activeEmitters.get(frame.streamId);
|
|
65
|
+
if (emitter) {
|
|
66
|
+
// Trigger the handler's onCancel callback so it can stop producing data
|
|
67
|
+
emitter.__cancelCallback?.();
|
|
68
|
+
this.activeEmitters.delete(frame.streamId);
|
|
69
|
+
}
|
|
70
|
+
const controller = this.streamControllers.get(frame.streamId);
|
|
71
|
+
if (controller) {
|
|
72
|
+
controller.cancel();
|
|
73
|
+
this.streamControllers.delete(frame.streamId);
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case FrameType.HANDSHAKE: {
|
|
78
|
+
this.handleHandshake(frame);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
handleRpcRequest(frame) {
|
|
84
|
+
try {
|
|
85
|
+
const reader = new BinaryReader(frame.payload);
|
|
86
|
+
const sessionId = reader.readString();
|
|
87
|
+
const service = reader.readString();
|
|
88
|
+
const method = reader.readString();
|
|
89
|
+
const token = reader.readString();
|
|
90
|
+
const reqPayload = reader.readBytes();
|
|
91
|
+
// Check ACL permissions if token is provided or expected
|
|
92
|
+
if (token && !this.gatekeeper.authorize(token, '*', service, method)) {
|
|
93
|
+
this.sendError(frame.streamId, `[Security] Access denied for ${service}.${method}`, sessionId);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const handler = this.rpcHandlers.get(`${service}.${method}`);
|
|
97
|
+
if (!handler) {
|
|
98
|
+
this.sendError(frame.streamId, `[RpcError] Method not found: ${service}.${method}`, sessionId);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
handler(reqPayload)
|
|
102
|
+
.then((resPayload) => {
|
|
103
|
+
const respFrame = {
|
|
104
|
+
type: FrameType.RPC_RESP,
|
|
105
|
+
flags: FrameFlags.NONE,
|
|
106
|
+
streamId: frame.streamId,
|
|
107
|
+
sequenceId: this.sequenceId++,
|
|
108
|
+
payload: resPayload,
|
|
109
|
+
};
|
|
110
|
+
this.sendFrame(respFrame, sessionId);
|
|
111
|
+
})
|
|
112
|
+
.catch((err) => {
|
|
113
|
+
this.sendError(frame.streamId, err.message || String(err), sessionId);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
this.sendError(frame.streamId, `[MalformedRequest] ${err.message}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
handleStreamInit(frame) {
|
|
121
|
+
try {
|
|
122
|
+
const reader = new BinaryReader(frame.payload);
|
|
123
|
+
const sessionId = reader.readString();
|
|
124
|
+
const service = reader.readString();
|
|
125
|
+
const method = reader.readString();
|
|
126
|
+
const token = reader.readString();
|
|
127
|
+
const reqPayload = reader.readBytes();
|
|
128
|
+
if (token && !this.gatekeeper.authorize(token, '*', service, method)) {
|
|
129
|
+
this.sendError(frame.streamId, `[Security] Access denied for ${service}.${method}`, sessionId);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const handler = this.streamHandlers.get(`${service}.${method}`);
|
|
133
|
+
if (!handler) {
|
|
134
|
+
this.sendError(frame.streamId, `[StreamError] Stream method not found: ${service}.${method}`, sessionId);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const controller = new StreamCreditController({ initialCredits: 8, ackBatchSize: 4 });
|
|
138
|
+
this.streamControllers.set(frame.streamId, controller);
|
|
139
|
+
let cancelCallback = null;
|
|
140
|
+
let closed = false;
|
|
141
|
+
const emitter = {
|
|
142
|
+
send: async (data) => {
|
|
143
|
+
if (closed)
|
|
144
|
+
return;
|
|
145
|
+
// Wait for available credit from client
|
|
146
|
+
await controller.acquireCredit();
|
|
147
|
+
if (closed)
|
|
148
|
+
return;
|
|
149
|
+
const dataFrame = {
|
|
150
|
+
type: FrameType.STREAM_DATA,
|
|
151
|
+
flags: FrameFlags.NONE,
|
|
152
|
+
streamId: frame.streamId,
|
|
153
|
+
sequenceId: this.sequenceId++,
|
|
154
|
+
payload: data,
|
|
155
|
+
};
|
|
156
|
+
this.sendFrame(dataFrame, sessionId);
|
|
157
|
+
},
|
|
158
|
+
close: () => {
|
|
159
|
+
if (closed)
|
|
160
|
+
return;
|
|
161
|
+
closed = true;
|
|
162
|
+
this.activeEmitters.delete(frame.streamId);
|
|
163
|
+
this.streamControllers.delete(frame.streamId);
|
|
164
|
+
const closeFrame = {
|
|
165
|
+
type: FrameType.STREAM_CLOSE,
|
|
166
|
+
flags: FrameFlags.END_STREAM,
|
|
167
|
+
streamId: frame.streamId,
|
|
168
|
+
sequenceId: this.sequenceId++,
|
|
169
|
+
payload: new Uint8Array(0),
|
|
170
|
+
};
|
|
171
|
+
this.sendFrame(closeFrame, sessionId);
|
|
172
|
+
},
|
|
173
|
+
error: (err) => {
|
|
174
|
+
if (closed)
|
|
175
|
+
return;
|
|
176
|
+
closed = true;
|
|
177
|
+
this.activeEmitters.delete(frame.streamId);
|
|
178
|
+
this.streamControllers.delete(frame.streamId);
|
|
179
|
+
this.sendError(frame.streamId, err.message, sessionId);
|
|
180
|
+
},
|
|
181
|
+
onCancel: (cb) => {
|
|
182
|
+
cancelCallback = cb;
|
|
183
|
+
emitter.__cancelCallback = cb;
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
this.activeEmitters.set(frame.streamId, emitter);
|
|
187
|
+
handler(reqPayload, emitter);
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
this.sendError(frame.streamId, `[MalformedStreamInit] ${err.message}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
handleHandshake(frame) {
|
|
194
|
+
try {
|
|
195
|
+
const reader = new BinaryReader(frame.payload);
|
|
196
|
+
const sessionId = reader.readString();
|
|
197
|
+
const lastAckSeq = reader.readUint32();
|
|
198
|
+
const session = this.sessionRegistry.getSession(sessionId);
|
|
199
|
+
if (!session) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Replay lost frames from ring buffer
|
|
203
|
+
const replayFrames = session.ringBuffer.getFramesAfter(lastAckSeq);
|
|
204
|
+
for (const replay of replayFrames) {
|
|
205
|
+
const replayCopy = {
|
|
206
|
+
...replay,
|
|
207
|
+
flags: replay.flags | FrameFlags.RESUMED,
|
|
208
|
+
};
|
|
209
|
+
const bytes = encodeFrame(replayCopy);
|
|
210
|
+
this.transport.send(bytes);
|
|
211
|
+
}
|
|
212
|
+
// Send Handshake ACK
|
|
213
|
+
const ackPayload = new BinaryWriter()
|
|
214
|
+
.writeString(sessionId)
|
|
215
|
+
.writeUint32(replayFrames.length)
|
|
216
|
+
.finish();
|
|
217
|
+
const ackFrame = {
|
|
218
|
+
type: FrameType.HANDSHAKE_ACK,
|
|
219
|
+
flags: FrameFlags.NONE,
|
|
220
|
+
streamId: 0,
|
|
221
|
+
sequenceId: this.sequenceId++,
|
|
222
|
+
payload: ackPayload,
|
|
223
|
+
};
|
|
224
|
+
const bytes = encodeFrame(ackFrame);
|
|
225
|
+
this.transport.send(bytes);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// Ignored
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../../../packages/native-host/src/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,SAAS,EACT,UAAU,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,oBAAoB,EACpB,eAAe,GAChB,MAAM,yBAAyB,CAAC;AAYjC,MAAM,OAAO,gBAAgB;IAWjB;IAVF,WAAW,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,cAAc,GAAG,IAAI,GAAG,EAAyB,CAAC;IAClD,cAAc,GAAG,IAAI,GAAG,EAAyB,CAAC;IAClD,iBAAiB,GAAG,IAAI,GAAG,EAAkC,CAAC;IAC9D,UAAU,GAAG,CAAC,CAAC;IAEhB,UAAU,CAAuB;IACjC,eAAe,CAAkB;IAExC,YACU,SAAoB,EAC5B,SAAkB;QADV,cAAS,GAAT,SAAS,CAAW;QAG5B,IAAI,CAAC,UAAU,GAAG,IAAI,oBAAoB,CAAC,SAAS,CAAC,CAAC;QACtD,IAAI,CAAC,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAE7C,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,WAAW,CAAC,OAAe,EAAE,MAAc,EAAE,OAAmB;QAC9D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED,cAAc,CAAC,OAAe,EAAE,MAAc,EAAE,OAAsB;QACpE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;IAEO,cAAc,CAAC,KAAiB;QACtC,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAEO,SAAS,CAAC,KAAkB,EAAE,SAAkB;QACtD,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;YACnE,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;QACD,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAEO,SAAS,CAAC,QAAgB,EAAE,OAAe,EAAE,SAAkB;QACrE,MAAM,UAAU,GAAgB;YAC9B,IAAI,EAAE,SAAS,CAAC,KAAK;YACrB,KAAK,EAAE,UAAU,CAAC,IAAI;YACtB,QAAQ;YACR,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;YAC7B,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC;SAC3C,CAAC;QACF,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IACxC,CAAC;IAEO,WAAW,CAAC,KAAkB;QACpC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;gBACvB,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;gBAC7B,MAAM;YACR,CAAC;YAED,KAAK,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;gBAC3B,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;gBAC7B,MAAM;YACR,CAAC;YAED,KAAK,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACxB,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBACjG,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC9D,IAAI,UAAU,EAAE,CAAC;oBACf,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;gBACnC,CAAC;gBACD,MAAM;YACR,CAAC;YAED,KAAK,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;gBAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACxD,IAAI,OAAO,EAAE,CAAC;oBACZ,wEAAwE;oBACvE,OAAe,CAAC,gBAAgB,EAAE,EAAE,CAAC;oBACtC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC7C,CAAC;gBACD,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC9D,IAAI,UAAU,EAAE,CAAC;oBACf,UAAU,CAAC,MAAM,EAAE,CAAC;oBACpB,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAChD,CAAC;gBACD,MAAM;YACR,CAAC;YAGD,KAAK,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;gBACzB,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;gBAC5B,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,KAAkB;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YACtC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YAClC,MAAM,UAAU,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;YAEtC,yDAAyD;YACzD,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;gBACrE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,gCAAgC,OAAO,IAAI,MAAM,EAAE,EAAE,SAAS,CAAC,CAAC;gBAC/F,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,MAAM,EAAE,CAAC,CAAC;YAC7D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,gCAAgC,OAAO,IAAI,MAAM,EAAE,EAAE,SAAS,CAAC,CAAC;gBAC/F,OAAO;YACT,CAAC;YAED,OAAO,CAAC,UAAU,CAAC;iBAChB,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE;gBACnB,MAAM,SAAS,GAAgB;oBAC7B,IAAI,EAAE,SAAS,CAAC,QAAQ;oBACxB,KAAK,EAAE,UAAU,CAAC,IAAI;oBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;oBAC7B,OAAO,EAAE,UAAU;iBACpB,CAAC;gBACF,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACvC,CAAC,CAAC;iBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACb,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,CAAC;YACxE,CAAC,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,sBAAsB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,KAAkB;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YACtC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YAClC,MAAM,UAAU,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;YAEtC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;gBACrE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,gCAAgC,OAAO,IAAI,MAAM,EAAE,EAAE,SAAS,CAAC,CAAC;gBAC/F,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,MAAM,EAAE,CAAC,CAAC;YAChE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,0CAA0C,OAAO,IAAI,MAAM,EAAE,EAAE,SAAS,CAAC,CAAC;gBACzG,OAAO;YACT,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,sBAAsB,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;YACtF,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAEvD,IAAI,cAAc,GAAwB,IAAI,CAAC;YAC/C,IAAI,MAAM,GAAG,KAAK,CAAC;YAEnB,MAAM,OAAO,GAAsD;gBACjE,IAAI,EAAE,KAAK,EAAE,IAAgB,EAAE,EAAE;oBAC/B,IAAI,MAAM;wBAAE,OAAO;oBACnB,wCAAwC;oBACxC,MAAM,UAAU,CAAC,aAAa,EAAE,CAAC;oBACjC,IAAI,MAAM;wBAAE,OAAO;oBAEnB,MAAM,SAAS,GAAgB;wBAC7B,IAAI,EAAE,SAAS,CAAC,WAAW;wBAC3B,KAAK,EAAE,UAAU,CAAC,IAAI;wBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;wBAC7B,OAAO,EAAE,IAAI;qBACd,CAAC;oBACF,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;gBACvC,CAAC;gBACD,KAAK,EAAE,GAAG,EAAE;oBACV,IAAI,MAAM;wBAAE,OAAO;oBACnB,MAAM,GAAG,IAAI,CAAC;oBACd,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;oBAC3C,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;oBAE9C,MAAM,UAAU,GAAgB;wBAC9B,IAAI,EAAE,SAAS,CAAC,YAAY;wBAC5B,KAAK,EAAE,UAAU,CAAC,UAAU;wBAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;wBAC7B,OAAO,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC;qBAC3B,CAAC;oBACF,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;gBACxC,CAAC;gBACD,KAAK,EAAE,CAAC,GAAU,EAAE,EAAE;oBACpB,IAAI,MAAM;wBAAE,OAAO;oBACnB,MAAM,GAAG,IAAI,CAAC;oBACd,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;oBAC3C,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;oBAC9C,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;gBACzD,CAAC;gBACD,QAAQ,EAAE,CAAC,EAAc,EAAE,EAAE;oBAC3B,cAAc,GAAG,EAAE,CAAC;oBACpB,OAAO,CAAC,gBAAgB,GAAG,EAAE,CAAC;gBAChC,CAAC;aACF,CAAC;YAGF,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACjD,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,yBAAyB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAEO,eAAe,CAAC,KAAkB;QACxC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YACtC,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YAEvC,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAC3D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO;YACT,CAAC;YAED,sCAAsC;YACtC,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;YACnE,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;gBAClC,MAAM,UAAU,GAAgB;oBAC9B,GAAG,MAAM;oBACT,KAAK,EAAE,MAAM,CAAC,KAAK,GAAG,UAAU,CAAC,OAAO;iBACzC,CAAC;gBACF,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;gBACtC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7B,CAAC;YAED,qBAAqB;YACrB,MAAM,UAAU,GAAG,IAAI,YAAY,EAAE;iBAClC,WAAW,CAAC,SAAS,CAAC;iBACtB,WAAW,CAAC,YAAY,CAAC,MAAM,CAAC;iBAChC,MAAM,EAAE,CAAC;YAEZ,MAAM,QAAQ,GAAgB;gBAC5B,IAAI,EAAE,SAAS,CAAC,aAAa;gBAC7B,KAAK,EAAE,UAAU,CAAC,IAAI;gBACtB,QAAQ,EAAE,CAAC;gBACX,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;gBAC7B,OAAO,EAAE,UAAU;aACpB,CAAC;YACF,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,UAAU;QACZ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../packages/native-host/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/native-host/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aether-wv",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Aether-WV: The Heterogeneous Local IPC Fabric for Modern WebViews",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist/index.*",
|
|
8
|
+
"dist/packages",
|
|
9
|
+
"bin",
|
|
10
|
+
"packages/platform-templates"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"aetherc": "./bin/aetherc.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"test": "tsc && node --test dist/tests/*.test.js",
|
|
19
|
+
"test:src": "tsc && node --test dist/tests/*.test.js",
|
|
20
|
+
"demo": "tsc && node dist/examples/run-demo.js",
|
|
21
|
+
"benchmark": "tsc && node dist/tests/benchmark.test.js"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"webview",
|
|
25
|
+
"ipc",
|
|
26
|
+
"bridge",
|
|
27
|
+
"binary-protocol",
|
|
28
|
+
"reactive-streams",
|
|
29
|
+
"backpressure",
|
|
30
|
+
"wkwebview",
|
|
31
|
+
"android-webview"
|
|
32
|
+
],
|
|
33
|
+
"author": "Aether Architecture Team",
|
|
34
|
+
"license": "Apache-2.0",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
37
|
+
"typescript": "^5.6.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AetherNativeDriver.kt
|
|
3
|
+
// Aether-WV Android Chromium Driver
|
|
4
|
+
//
|
|
5
|
+
// Production WebViewCompat integration with binary WebMessagePort and origin validation
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
package io.aether.webview
|
|
9
|
+
|
|
10
|
+
import android.net.Uri
|
|
11
|
+
import android.webkit.WebView
|
|
12
|
+
import androidx.webkit.WebMessageCompat
|
|
13
|
+
import androidx.webkit.WebMessagePortCompat
|
|
14
|
+
import androidx.webkit.WebViewCompat
|
|
15
|
+
import androidx.webkit.WebViewFeature
|
|
16
|
+
import java.util.concurrent.Executors
|
|
17
|
+
|
|
18
|
+
class AetherAndroidDriver(
|
|
19
|
+
private val webView: WebView,
|
|
20
|
+
private val allowedOrigins: Set<String>,
|
|
21
|
+
private val onFrameReceived: (ByteArray) -> Unit
|
|
22
|
+
) {
|
|
23
|
+
private val backgroundExecutor = Executors.newSingleThreadExecutor()
|
|
24
|
+
private var nativePort: WebMessagePortCompat? = null
|
|
25
|
+
|
|
26
|
+
init {
|
|
27
|
+
setupDriver()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private fun setupDriver() {
|
|
31
|
+
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
|
|
32
|
+
throw UnsupportedOperationException("WEB_MESSAGE_LISTENER is required for Aether-WV")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Establish channel messaging on document load
|
|
36
|
+
WebViewCompat.addWebMessageListener(
|
|
37
|
+
webView,
|
|
38
|
+
"aetherNativeChannel",
|
|
39
|
+
allowedOrigins
|
|
40
|
+
) { _, message, sourceOrigin, isMainFrame, _ ->
|
|
41
|
+
// Extract raw bytes or channel port
|
|
42
|
+
val bytes = message.data?.toByteArray(Charsets.ISO_8859_1)
|
|
43
|
+
if (bytes != null) {
|
|
44
|
+
backgroundExecutor.execute {
|
|
45
|
+
onFrameReceived(bytes)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fun sendFrameToWeb(data: ByteArray) {
|
|
52
|
+
val message = WebMessageCompat(data.toString(Charsets.ISO_8859_1))
|
|
53
|
+
webView.post {
|
|
54
|
+
nativePort?.postMessage(message)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AetherNativeDriver.swift
|
|
3
|
+
// Aether-WV iOS WebKit Driver
|
|
4
|
+
//
|
|
5
|
+
// Production WebKit multi-process integration using WKContentWorld and WKScriptMessageHandlerWithReply
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import WebKit
|
|
10
|
+
|
|
11
|
+
public final class AetherIosDriver: NSObject, WKScriptMessageHandlerWithReply {
|
|
12
|
+
private weak var webView: WKWebView?
|
|
13
|
+
private let contentWorldName = "AetherIsolatedWorld"
|
|
14
|
+
private let handlerName = "aether"
|
|
15
|
+
private var onFrameReceived: ((Data) -> Void)?
|
|
16
|
+
|
|
17
|
+
public init(webView: WKWebView, onFrameReceived: @escaping (Data) -> Void) {
|
|
18
|
+
self.webView = webView
|
|
19
|
+
self.onFrameReceived = onFrameReceived
|
|
20
|
+
super.init()
|
|
21
|
+
self.setupDriver()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private func setupDriver() {
|
|
25
|
+
guard let webView = self.webView else { return }
|
|
26
|
+
|
|
27
|
+
let world = WKContentWorld.world(name: contentWorldName)
|
|
28
|
+
let ucc = webView.configuration.userContentController
|
|
29
|
+
|
|
30
|
+
// Register script message handler in isolated content world to prevent page XSS access
|
|
31
|
+
ucc.addScriptMessageHandler(self, contentWorld: world, name: handlerName)
|
|
32
|
+
|
|
33
|
+
// Inject bootstrap hook to bridge message to page context
|
|
34
|
+
let bootstrapScript = """
|
|
35
|
+
(function() {
|
|
36
|
+
if (window.__aether_initialized) return;
|
|
37
|
+
window.__aether_initialized = true;
|
|
38
|
+
console.log('[Aether-WV] Isolated bridge attached');
|
|
39
|
+
})();
|
|
40
|
+
"""
|
|
41
|
+
let userScript = WKUserScript(source: bootstrapScript, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: world)
|
|
42
|
+
ucc.addUserScript(userScript)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// MARK: - WKScriptMessageHandlerWithReply
|
|
46
|
+
public func userContentController(
|
|
47
|
+
_ userContentController: WKUserContentController,
|
|
48
|
+
didReceive message: WKScriptMessage,
|
|
49
|
+
replyHandler: @escaping (Any?, String?) -> Void
|
|
50
|
+
) {
|
|
51
|
+
guard let dict = message.body as? [String: Any],
|
|
52
|
+
let b64 = dict["payload"] as? String,
|
|
53
|
+
let data = Data(base64Encoded: b64) else {
|
|
54
|
+
replyHandler(nil, "[AetherIosDriver] Malformed binary payload")
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Process incoming frame on background queue
|
|
59
|
+
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
60
|
+
self?.onFrameReceived?(data)
|
|
61
|
+
replyHandler(["status": "ack"], nil)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// MARK: - Dispatch frame back to WebView
|
|
66
|
+
public func sendFrameToWeb(data: Data) {
|
|
67
|
+
let b64 = data.base64EncodedString()
|
|
68
|
+
let js = "window.__aether_native_dispatch && window.__aether_native_dispatch('\(b64)');"
|
|
69
|
+
|
|
70
|
+
DispatchQueue.main.async { [weak self] in
|
|
71
|
+
self?.webView?.evaluateJavaScript(js, in: nil, in: .page) { _, error in
|
|
72
|
+
if let error = error {
|
|
73
|
+
print("[AetherIosDriver] evaluateJavaScript error: \(error.localizedDescription)")
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|