@nativewindow/ipc 0.1.1 → 1.0.1
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/README.md +22 -10
- package/dist/client.d.ts +26 -12
- package/dist/client.js +4 -2
- package/dist/index.d.ts +62 -25
- package/dist/index.js +5 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@nativewindow/ipc)
|
|
4
4
|
|
|
5
|
-
> [!
|
|
6
|
-
> This project is in **
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> This project is in **beta**. APIs may change without notice.
|
|
7
7
|
|
|
8
8
|
Pure TypeScript typesafe IPC channel layer for [native-window](https://github.com/nativewindow/webview). Schema-based validation with compile-time checked event maps.
|
|
9
9
|
|
|
@@ -25,13 +25,18 @@ const ch = createWindow(
|
|
|
25
25
|
{ title: "Typed IPC" },
|
|
26
26
|
{
|
|
27
27
|
schemas: {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
host: {
|
|
29
|
+
"update-title": z.string(),
|
|
30
|
+
},
|
|
31
|
+
client: {
|
|
32
|
+
"user-click": z.object({ x: z.number(), y: z.number() }),
|
|
33
|
+
counter: z.number(),
|
|
34
|
+
},
|
|
31
35
|
},
|
|
32
36
|
},
|
|
33
37
|
);
|
|
34
38
|
|
|
39
|
+
// Receive typed messages from the webview (client events)
|
|
35
40
|
ch.on("user-click", (pos) => {
|
|
36
41
|
// pos: { x: number; y: number }
|
|
37
42
|
console.log(`Click at ${pos.x}, ${pos.y}`);
|
|
@@ -42,8 +47,8 @@ ch.on("counter", (n) => {
|
|
|
42
47
|
ch.send("update-title", `Count: ${n}`);
|
|
43
48
|
});
|
|
44
49
|
|
|
45
|
-
// ch.send("counter", "wrong"); // Type error
|
|
46
|
-
// ch.send("typo", 123); // Type error
|
|
50
|
+
// ch.send("counter", "wrong"); // Type error: "counter" is a client event
|
|
51
|
+
// ch.send("typo", 123); // Type error: "typo" does not exist
|
|
47
52
|
|
|
48
53
|
ch.window.loadHtml(`<html>...</html>`);
|
|
49
54
|
```
|
|
@@ -71,12 +76,19 @@ import { createChannelClient } from "@nativewindow/ipc/client";
|
|
|
71
76
|
|
|
72
77
|
const ch = createChannelClient({
|
|
73
78
|
schemas: {
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
host: {
|
|
80
|
+
"update-title": z.string(),
|
|
81
|
+
},
|
|
82
|
+
client: {
|
|
83
|
+
counter: z.number(),
|
|
84
|
+
},
|
|
76
85
|
},
|
|
77
86
|
});
|
|
78
87
|
|
|
88
|
+
// Send client events to the host
|
|
79
89
|
ch.send("counter", 42); // Typed!
|
|
90
|
+
|
|
91
|
+
// Receive host events from the host
|
|
80
92
|
ch.on("update-title", (t) => {
|
|
81
93
|
// t: string
|
|
82
94
|
document.title = t;
|
|
@@ -105,7 +117,7 @@ Create a typed channel client inside the webview for use in bundled apps.
|
|
|
105
117
|
|
|
106
118
|
| Option | Type | Default | Description |
|
|
107
119
|
| ---------------------- | ----------- | ---------- | -------------------------------------- |
|
|
108
|
-
| `schemas` | `SchemaMap` | _required_ |
|
|
120
|
+
| `schemas` | `{ host: SchemaMap; client: SchemaMap }` | _required_ | Directional schemas — `host` for events the host sends, `client` for events the webview sends |
|
|
109
121
|
| `injectClient` | `boolean` | `true` | Auto-inject client script into webview |
|
|
110
122
|
| `onValidationError` | `function` | — | Called when a payload fails validation |
|
|
111
123
|
| `trustedOrigins` | `string[]` | — | Restrict IPC to specific origins |
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TypedChannel, ValidationErrorHandler, SchemaMap, InferSchemaMap } from '.';
|
|
1
|
+
import { TypedChannel, ValidationErrorHandler, SchemaMap, InferSchemaMap } from './index.ts';
|
|
2
2
|
declare global {
|
|
3
3
|
interface Window {
|
|
4
|
-
__channel__?: TypedChannel<any>;
|
|
4
|
+
__channel__?: TypedChannel<any, any>;
|
|
5
5
|
__native_message__?: (msg: string) => void;
|
|
6
6
|
__native_message_listeners__?: {
|
|
7
7
|
add(fn: (msg: string) => void): void;
|
|
@@ -14,20 +14,30 @@ declare global {
|
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Options for {@link createChannelClient}.
|
|
17
|
-
* The `schemas` field is required — it
|
|
18
|
-
*
|
|
17
|
+
* The `schemas` field is required — it uses directional groups matching
|
|
18
|
+
* the host-side `createChannel` options.
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
21
|
* ```ts
|
|
22
22
|
* import { z } from "zod";
|
|
23
23
|
* const ch = createChannelClient({
|
|
24
|
-
* schemas: {
|
|
24
|
+
* schemas: {
|
|
25
|
+
* host: { "update-title": z.string() },
|
|
26
|
+
* client: { "user-click": z.object({ x: z.number(), y: z.number() }) },
|
|
27
|
+
* },
|
|
25
28
|
* });
|
|
26
29
|
* ```
|
|
27
30
|
*/
|
|
28
|
-
export interface ChannelClientOptions<
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
+
export interface ChannelClientOptions<H extends SchemaMap, C extends SchemaMap> {
|
|
32
|
+
/**
|
|
33
|
+
* Directional schemas for the channel.
|
|
34
|
+
* - `host`: events the host sends to the client (validated on receive).
|
|
35
|
+
* - `client`: events the client sends to the host (type-checked on send).
|
|
36
|
+
*/
|
|
37
|
+
schemas: {
|
|
38
|
+
host: H;
|
|
39
|
+
client: C;
|
|
40
|
+
};
|
|
31
41
|
/**
|
|
32
42
|
* Called when an incoming payload fails schema validation.
|
|
33
43
|
* If not provided, failed payloads are silently dropped.
|
|
@@ -38,6 +48,9 @@ export interface ChannelClientOptions<S extends SchemaMap> {
|
|
|
38
48
|
* Create a typed channel client for use inside the webview.
|
|
39
49
|
* Call this once; it hooks into the native IPC bridge.
|
|
40
50
|
*
|
|
51
|
+
* The client sends events defined by the `client` schemas and receives
|
|
52
|
+
* events defined by the `host` schemas — the inverse of the host side.
|
|
53
|
+
*
|
|
41
54
|
* @example
|
|
42
55
|
* ```ts
|
|
43
56
|
* import { z } from "zod";
|
|
@@ -45,14 +58,15 @@ export interface ChannelClientOptions<S extends SchemaMap> {
|
|
|
45
58
|
*
|
|
46
59
|
* const ch = createChannelClient({
|
|
47
60
|
* schemas: {
|
|
48
|
-
*
|
|
49
|
-
* "
|
|
61
|
+
* host: { "update-title": z.string() },
|
|
62
|
+
* client: { "user-click": z.object({ x: z.number(), y: z.number() }) },
|
|
50
63
|
* },
|
|
51
64
|
* });
|
|
52
65
|
*
|
|
53
|
-
* ch.
|
|
66
|
+
* ch.send("user-click", { x: 10, y: 20 }); // only client events
|
|
67
|
+
* ch.on("update-title", (title) => { // only host events
|
|
54
68
|
* document.title = title;
|
|
55
69
|
* });
|
|
56
70
|
* ```
|
|
57
71
|
*/
|
|
58
|
-
export declare function createChannelClient<
|
|
72
|
+
export declare function createChannelClient<H extends SchemaMap, C extends SchemaMap>(options: ChannelClientOptions<H, C>): TypedChannel<InferSchemaMap<C>, InferSchemaMap<H>>;
|
package/dist/client.js
CHANGED
|
@@ -23,6 +23,8 @@ function validatePayload(schema, data) {
|
|
|
23
23
|
}
|
|
24
24
|
function createChannelClient(options) {
|
|
25
25
|
const { schemas, onValidationError } = options;
|
|
26
|
+
const hostSchemas = schemas.host;
|
|
27
|
+
schemas.client;
|
|
26
28
|
const _push = Array.prototype.push;
|
|
27
29
|
const _indexOf = Array.prototype.indexOf;
|
|
28
30
|
const _splice = Array.prototype.splice;
|
|
@@ -52,7 +54,7 @@ function createChannelClient(options) {
|
|
|
52
54
|
value(msg) {
|
|
53
55
|
const env = decode(msg);
|
|
54
56
|
if (env) {
|
|
55
|
-
if (!(env.$ch in
|
|
57
|
+
if (!(env.$ch in hostSchemas)) {
|
|
56
58
|
for (const fn of externalListeners) {
|
|
57
59
|
try {
|
|
58
60
|
fn(msg);
|
|
@@ -64,7 +66,7 @@ function createChannelClient(options) {
|
|
|
64
66
|
}
|
|
65
67
|
const set = listeners.get(env.$ch);
|
|
66
68
|
if (set) {
|
|
67
|
-
const schema =
|
|
69
|
+
const schema = hostSchemas[env.$ch];
|
|
68
70
|
let validatedPayload = env.p;
|
|
69
71
|
if (schema) {
|
|
70
72
|
const result = validatePayload(schema, env.p);
|
package/dist/index.d.ts
CHANGED
|
@@ -96,37 +96,56 @@ export type ValidationErrorHandler = (type: string, payload: unknown) => void;
|
|
|
96
96
|
* @internal
|
|
97
97
|
*/
|
|
98
98
|
export type SendArgs<T extends EventMap, K extends keyof T & string> = [T[K]] extends [void | never] ? [type: K] | [type: K, payload: T[K]] : [type: K, payload: T[K]];
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Typed channel interface with separate Send and Receive event maps.
|
|
101
|
+
* `Send` determines which events are available via `send()`, while
|
|
102
|
+
* `Receive` determines which events are available via `on()` / `off()`.
|
|
103
|
+
*/
|
|
104
|
+
export interface TypedChannel<Send extends EventMap, Receive extends EventMap> {
|
|
105
|
+
/** Send a typed message. Only events from the Send map are allowed. */
|
|
106
|
+
send<K extends keyof Send & string>(...args: SendArgs<Send, K>): void;
|
|
107
|
+
/** Register a handler for an incoming event. Only events from the Receive map are allowed. */
|
|
108
|
+
on<K extends keyof Receive & string>(type: K, handler: (payload: Receive[K]) => void): void;
|
|
109
|
+
/** Remove a handler for an incoming event. Only events from the Receive map are allowed. */
|
|
110
|
+
off<K extends keyof Receive & string>(type: K, handler: (payload: Receive[K]) => void): void;
|
|
107
111
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Host-side channel wrapping a NativeWindow.
|
|
114
|
+
* The host sends events defined by the `host` schemas and receives
|
|
115
|
+
* events defined by the `client` schemas.
|
|
116
|
+
*/
|
|
117
|
+
export interface NativeWindowChannel<Send extends EventMap, Receive extends EventMap> extends TypedChannel<Send, Receive> {
|
|
110
118
|
/** The underlying NativeWindow instance. */
|
|
111
119
|
readonly window: NativeWindow;
|
|
112
120
|
}
|
|
113
121
|
/**
|
|
114
122
|
* Options for {@link createChannel}.
|
|
115
123
|
* The `schemas` field is required — it provides both TypeScript types
|
|
116
|
-
* and runtime validation for each event.
|
|
124
|
+
* and runtime validation for each event direction.
|
|
117
125
|
*
|
|
118
126
|
* @example
|
|
119
127
|
* ```ts
|
|
120
128
|
* import { z } from "zod";
|
|
121
129
|
* const ch = createChannel(win, {
|
|
122
|
-
* schemas: {
|
|
130
|
+
* schemas: {
|
|
131
|
+
* host: { "update-title": z.string() },
|
|
132
|
+
* client: { "user-click": z.object({ x: z.number(), y: z.number() }) },
|
|
133
|
+
* },
|
|
123
134
|
* });
|
|
124
|
-
* ch.send("
|
|
135
|
+
* ch.send("update-title", "Hello"); // only host events
|
|
136
|
+
* ch.on("user-click", (p) => {}); // only client events
|
|
125
137
|
* ```
|
|
126
138
|
*/
|
|
127
|
-
export interface ChannelOptions<
|
|
128
|
-
/**
|
|
129
|
-
|
|
139
|
+
export interface ChannelOptions<H extends SchemaMap, C extends SchemaMap> {
|
|
140
|
+
/**
|
|
141
|
+
* Directional schemas for the channel.
|
|
142
|
+
* - `host`: events the host sends to the client.
|
|
143
|
+
* - `client`: events the client sends to the host.
|
|
144
|
+
*/
|
|
145
|
+
schemas: {
|
|
146
|
+
host: H;
|
|
147
|
+
client: C;
|
|
148
|
+
};
|
|
130
149
|
/** Inject the client script into the webview automatically. Default: true */
|
|
131
150
|
injectClient?: boolean;
|
|
132
151
|
/**
|
|
@@ -153,7 +172,10 @@ export interface ChannelOptions<S extends SchemaMap> {
|
|
|
153
172
|
* @example
|
|
154
173
|
* ```ts
|
|
155
174
|
* createChannel(win, {
|
|
156
|
-
* schemas: {
|
|
175
|
+
* schemas: {
|
|
176
|
+
* host: { ping: z.string() },
|
|
177
|
+
* client: { pong: z.number() },
|
|
178
|
+
* },
|
|
157
179
|
* trustedOrigins: ["https://myapp.com", "https://cdn.myapp.com"],
|
|
158
180
|
* });
|
|
159
181
|
* ```
|
|
@@ -231,6 +253,13 @@ export declare function getClientScript(options?: {
|
|
|
231
253
|
* validation for each event. Compatible with Zod v4, Valibot v1, and
|
|
232
254
|
* any schema library implementing the `safeParse()` interface.
|
|
233
255
|
*
|
|
256
|
+
* The `schemas` option uses directional groups:
|
|
257
|
+
* - `host`: events the host sends to the client.
|
|
258
|
+
* - `client`: events the client sends to the host.
|
|
259
|
+
*
|
|
260
|
+
* On the returned channel, `send()` only accepts `host` event types and
|
|
261
|
+
* `on()`/`off()` only accept `client` event types.
|
|
262
|
+
*
|
|
234
263
|
* @security **Origin restriction:** When `trustedOrigins` is configured,
|
|
235
264
|
* both client script injection and incoming IPC messages are restricted to
|
|
236
265
|
* pages whose URL origin matches the whitelist. The native `onMessage`
|
|
@@ -244,13 +273,16 @@ export declare function getClientScript(options?: {
|
|
|
244
273
|
* import { createChannel } from "native-window-ipc";
|
|
245
274
|
*
|
|
246
275
|
* const ch = createChannel(win, {
|
|
247
|
-
* schemas: {
|
|
276
|
+
* schemas: {
|
|
277
|
+
* host: { "update-title": z.string() },
|
|
278
|
+
* client: { "user-click": z.object({ x: z.number(), y: z.number() }) },
|
|
279
|
+
* },
|
|
248
280
|
* });
|
|
249
|
-
* ch.send("
|
|
250
|
-
* ch.on("
|
|
281
|
+
* ch.send("update-title", "Hello"); // only host events
|
|
282
|
+
* ch.on("user-click", (p) => {}); // only client events, p: { x: number; y: number }
|
|
251
283
|
* ```
|
|
252
284
|
*/
|
|
253
|
-
export declare function createChannel<
|
|
285
|
+
export declare function createChannel<H extends SchemaMap, C extends SchemaMap>(win: NativeWindow, options: ChannelOptions<H, C>): NativeWindowChannel<InferSchemaMap<H>, InferSchemaMap<C>>;
|
|
254
286
|
/**
|
|
255
287
|
* Create a new NativeWindow and immediately wrap it with a typed channel.
|
|
256
288
|
*
|
|
@@ -261,10 +293,15 @@ export declare function createChannel<S extends SchemaMap>(win: NativeWindow, op
|
|
|
261
293
|
*
|
|
262
294
|
* const ch = createWindow(
|
|
263
295
|
* { title: "My App" },
|
|
264
|
-
* {
|
|
296
|
+
* {
|
|
297
|
+
* schemas: {
|
|
298
|
+
* host: { "update-title": z.string() },
|
|
299
|
+
* client: { "user-click": z.object({ x: z.number(), y: z.number() }) },
|
|
300
|
+
* },
|
|
301
|
+
* },
|
|
265
302
|
* );
|
|
266
|
-
* ch.send("
|
|
267
|
-
* ch.
|
|
303
|
+
* ch.send("update-title", "Hello"); // only host events
|
|
304
|
+
* ch.on("user-click", (p) => {}); // only client events
|
|
268
305
|
* ```
|
|
269
306
|
*/
|
|
270
|
-
export declare function createWindow<
|
|
307
|
+
export declare function createWindow<H extends SchemaMap, C extends SchemaMap>(windowOptions: WindowOptions | undefined, channelOptions: ChannelOptions<H, C>): NativeWindowChannel<InferSchemaMap<H>, InferSchemaMap<C>>;
|
package/dist/index.js
CHANGED
|
@@ -81,6 +81,8 @@ function createChannel(win, options) {
|
|
|
81
81
|
maxListenersPerEvent,
|
|
82
82
|
channelId: channelIdOpt
|
|
83
83
|
} = options;
|
|
84
|
+
schemas.host;
|
|
85
|
+
const clientSchemas = schemas.client;
|
|
84
86
|
const channelId = channelIdOpt === true ? crypto.randomUUID().replace(/-/g, "").slice(0, 16) : channelIdOpt ?? "";
|
|
85
87
|
const normalizedOrigins = trustedOrigins?.map(normalizeOrigin).filter((o) => o !== null);
|
|
86
88
|
const prefixCh = (type) => channelId ? `${channelId}:${type}` : type;
|
|
@@ -112,8 +114,8 @@ function createChannel(win, options) {
|
|
|
112
114
|
}
|
|
113
115
|
const set = listeners.get(eventType);
|
|
114
116
|
if (!set) return;
|
|
115
|
-
if (!(eventType in
|
|
116
|
-
const schema =
|
|
117
|
+
if (!(eventType in clientSchemas)) return;
|
|
118
|
+
const schema = clientSchemas[eventType];
|
|
117
119
|
let validatedPayload = env.p;
|
|
118
120
|
if (schema) {
|
|
119
121
|
const result = validatePayload(schema, env.p);
|
|
@@ -149,7 +151,7 @@ function createChannel(win, options) {
|
|
|
149
151
|
win.postMessage(encode(prefixCh(type), payload));
|
|
150
152
|
},
|
|
151
153
|
on(type, handler) {
|
|
152
|
-
if (!(type in
|
|
154
|
+
if (!(type in clientSchemas)) return;
|
|
153
155
|
let set = listeners.get(type);
|
|
154
156
|
if (!set) {
|
|
155
157
|
set = /* @__PURE__ */ new Set();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nativewindow/ipc",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Typesafe IPC channels for native-window (
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Typesafe IPC channels for native-window (beta)",
|
|
5
5
|
"homepage": "https://nativewindow.fcannizzaro.com",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/nativewindow/webview/issues"
|