@deadair/plugin-sdk 0.39.0 → 0.40.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/README.md +95 -0
- package/dist/boundary.json.safe.d.ts +13 -3
- package/dist/boundary.json.safe.d.ts.map +1 -1
- package/dist/capabilities/output.d.ts +168 -0
- package/dist/capabilities/output.d.ts.map +1 -0
- package/dist/define.plugin.d.ts +3 -0
- package/dist/define.plugin.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin.host.d.ts +109 -0
- package/dist/plugin.host.d.ts.map +1 -1
- package/dist/plugin.manifest.d.ts +16 -1
- package/dist/plugin.manifest.d.ts.map +1 -1
- package/dist/plugin.permissions.d.ts +23 -0
- package/dist/plugin.permissions.d.ts.map +1 -1
- package/dist/testing/fake.plugin.host.d.ts +34 -1
- package/dist/testing/fake.plugin.host.d.ts.map +1 -1
- package/dist/testing/index.js +56 -0
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin.host.d.ts
CHANGED
|
@@ -82,6 +82,84 @@ export interface PluginSocket {
|
|
|
82
82
|
*/
|
|
83
83
|
onClose(listener: (code?: number, reason?: string) => void): void;
|
|
84
84
|
}
|
|
85
|
+
/** What {@link PluginHost.tls} may be told. */
|
|
86
|
+
export interface PluginTlsOptions {
|
|
87
|
+
/**
|
|
88
|
+
* How long to wait for the handshake to finish, in ms. Clamped to the host's
|
|
89
|
+
* own ceiling; asking for more buys nothing.
|
|
90
|
+
*/
|
|
91
|
+
connectTimeoutMs?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Whether the peer's certificate must chain to a trusted root and name the
|
|
94
|
+
* host. Absent means it must.
|
|
95
|
+
*
|
|
96
|
+
* `false` is for a device on the operator's own network that presents a
|
|
97
|
+
* certificate no public root signs, which is what a Chromecast does. The host
|
|
98
|
+
* honours it only for an address the operator supplied (a `fromConfig`
|
|
99
|
+
* entry); for a host the manifest names, or one reached through
|
|
100
|
+
* `network.open`, it is refused rather than quietly ignored.
|
|
101
|
+
*/
|
|
102
|
+
verifyCertificate?: boolean;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* An open outbound TLS connection, from {@link PluginHost.tls}.
|
|
106
|
+
*
|
|
107
|
+
* Bytes, not frames: TLS is a stream, so `onData` hands over whatever arrived
|
|
108
|
+
* in one read and the plugin reassembles its own protocol's messages. There is
|
|
109
|
+
* no `onOpen`: the promise that handed it over resolved when the handshake did.
|
|
110
|
+
*/
|
|
111
|
+
export interface PluginTlsSocket {
|
|
112
|
+
/**
|
|
113
|
+
* Write bytes. Does nothing once the connection has closed, rather than
|
|
114
|
+
* throwing, for {@link PluginSocket.send}'s reason: the likeliest writer is a
|
|
115
|
+
* heartbeat timer.
|
|
116
|
+
*/
|
|
117
|
+
send(bytes: Uint8Array): void;
|
|
118
|
+
/** Close it. Safe to call twice. */
|
|
119
|
+
close(): void;
|
|
120
|
+
/** Called with every chunk read. A listener that throws is logged, never propagated. */
|
|
121
|
+
onData(listener: (bytes: Uint8Array) => void): void;
|
|
122
|
+
/**
|
|
123
|
+
* Called once when the connection closes, for any reason but one: a
|
|
124
|
+
* connection the host closes because the plugin is being disposed tells
|
|
125
|
+
* nobody, since there is nobody left to reconnect. `reason` is the error that
|
|
126
|
+
* closed it, when one did.
|
|
127
|
+
*/
|
|
128
|
+
onClose(listener: (reason?: string) => void): void;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* What {@link PluginHost.discover} is asked to look for: an mDNS service type such as
|
|
132
|
+
* `_googlecast._tcp`, or an SSDP search target such as
|
|
133
|
+
* `urn:schemas-upnp-org:device:MediaRenderer:1`. Each must be named in the manifest's
|
|
134
|
+
* `permissions.discovery`.
|
|
135
|
+
*/
|
|
136
|
+
export type DiscoveryQuery = {
|
|
137
|
+
protocol: 'mdns';
|
|
138
|
+
service: string;
|
|
139
|
+
} | {
|
|
140
|
+
protocol: 'ssdp';
|
|
141
|
+
searchTarget: string;
|
|
142
|
+
};
|
|
143
|
+
/** What {@link PluginHost.discover} may be told. */
|
|
144
|
+
export interface DiscoveryOptions {
|
|
145
|
+
/** How long to listen for answers, in ms. Clamped to the host's own ceiling of a few seconds. */
|
|
146
|
+
timeoutMs?: number;
|
|
147
|
+
}
|
|
148
|
+
/** One device that answered a discovery query. */
|
|
149
|
+
export interface DiscoveredService {
|
|
150
|
+
/** The mDNS instance name, or the SSDP unique service name: stable for one device. */
|
|
151
|
+
name: string;
|
|
152
|
+
/** The IPv4 address it answered from, which the host now lets this plugin reach. */
|
|
153
|
+
address: string;
|
|
154
|
+
/** The port the service is on, where the protocol says (mDNS does; SSDP puts it in `location`). */
|
|
155
|
+
port?: number;
|
|
156
|
+
/** mDNS TXT record, key to value: a Cast device's friendly name is `fn`, its model `md`. */
|
|
157
|
+
txt?: Record<string, string>;
|
|
158
|
+
/** SSDP: the absolute URL of the device's description. */
|
|
159
|
+
location?: string;
|
|
160
|
+
/** SSDP: what the device says it runs. */
|
|
161
|
+
server?: string;
|
|
162
|
+
}
|
|
85
163
|
/**
|
|
86
164
|
* Namespaced key/value store, private to this plugin. Values must be
|
|
87
165
|
* JSON-serialisable. Requires the `storage` permission.
|
|
@@ -261,6 +339,37 @@ export interface PluginHost {
|
|
|
261
339
|
* is the backstop, not the plan.
|
|
262
340
|
*/
|
|
263
341
|
socket(url: string, options?: PluginSocketOptions): Promise<PluginSocket>;
|
|
342
|
+
/**
|
|
343
|
+
* Open an outbound TLS connection, for a device that speaks its own protocol
|
|
344
|
+
* over one (a Chromecast's control channel on port 8009). Requires the `tls`
|
|
345
|
+
* permission.
|
|
346
|
+
*
|
|
347
|
+
* `url` is `tls://host:port`, and the port is required: there is no default
|
|
348
|
+
* to fall back on. The same policy as {@link socket}: the host must be in
|
|
349
|
+
* `permissions.network` (checked as the `https:` URL on the same host and
|
|
350
|
+
* port), a host reached through `network.open` must not resolve to a private
|
|
351
|
+
* address, and the connect costs one token from the matching rate bucket.
|
|
352
|
+
* Resolves once the handshake is done; rejects with a `PluginError` when it
|
|
353
|
+
* is refused or never completes.
|
|
354
|
+
*
|
|
355
|
+
* Outlives the call that opened it, like a socket, and is bounded the same
|
|
356
|
+
* way: a plugin may hold only so many at once, and disposing the plugin
|
|
357
|
+
* closes every one it still has.
|
|
358
|
+
*/
|
|
359
|
+
tls(url: string, options?: PluginTlsOptions): Promise<PluginTlsSocket>;
|
|
360
|
+
/**
|
|
361
|
+
* Find devices on the station's network, by mDNS or SSDP. The query must be one the manifest
|
|
362
|
+
* names in `permissions.discovery`.
|
|
363
|
+
*
|
|
364
|
+
* The host owns the sockets, so plugins never bind the multicast ports themselves, and it
|
|
365
|
+
* remembers every address that answered: this plugin may then reach those addresses through
|
|
366
|
+
* `fetch`, `socket` and `tls` as though the operator had typed them in.
|
|
367
|
+
*
|
|
368
|
+
* Resolves with whatever answered within the timeout, which is an empty list on a network that
|
|
369
|
+
* cannot carry multicast to the station (a container on a bridge network is the usual one). An
|
|
370
|
+
* empty answer is not an error; say so as such rather than reporting no devices as a fault.
|
|
371
|
+
*/
|
|
372
|
+
discover(query: DiscoveryQuery, options?: DiscoveryOptions): Promise<DiscoveredService[]>;
|
|
264
373
|
/**
|
|
265
374
|
* Aborts when the host gives up on the call you are currently inside.
|
|
266
375
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.host.d.ts","sourceRoot":"","sources":["../src/plugin.host.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.host.d.ts","sourceRoot":"","sources":["../src/plugin.host.ts"],"names":[],"mappings":"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"}
|
|
@@ -193,7 +193,14 @@ export declare const PLUGIN_CAPABILITY_SCROBBLE = "scrobble";
|
|
|
193
193
|
* pushing them; `capabilities/messaging.ts` says why.
|
|
194
194
|
*/
|
|
195
195
|
export declare const PLUGIN_CAPABILITY_MESSAGING = "messaging";
|
|
196
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Puts the station on a speaker somewhere else: the plugin is told a mount's
|
|
198
|
+
* URL and a device, and the device fetches the stream itself. Separate from
|
|
199
|
+
* `steer`, which hands a music provider's OWN player the records; this hands a
|
|
200
|
+
* speaker the station. `capabilities/output.ts` says why it carries no audio.
|
|
201
|
+
*/
|
|
202
|
+
export declare const PLUGIN_CAPABILITY_OUTPUT = "output";
|
|
203
|
+
export declare const KNOWN_PLUGIN_CAPABILITIES: readonly ["catalog", "stream", "steer", "oauth", "enrichment", "speech", "llm", "analysis", "mixer", "transcode", "charts", "news", "narration", "podcast", "similarity", "search", "weather", "almanac", "scrobble", "messaging", "output"];
|
|
197
204
|
export type KnownPluginCapability = (typeof KNOWN_PLUGIN_CAPABILITIES)[number];
|
|
198
205
|
/**
|
|
199
206
|
* Known capabilities get autocomplete; the type stays open so a plugin built
|
|
@@ -264,6 +271,14 @@ export declare const pluginManifestSchema: z.ZodObject<{
|
|
|
264
271
|
oauth: z.ZodBoolean;
|
|
265
272
|
trackFetcher: z.ZodOptional<z.ZodBoolean>;
|
|
266
273
|
sockets: z.ZodOptional<z.ZodBoolean>;
|
|
274
|
+
tls: z.ZodOptional<z.ZodBoolean>;
|
|
275
|
+
discovery: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
276
|
+
protocol: z.ZodLiteral<"mdns">;
|
|
277
|
+
service: z.ZodString;
|
|
278
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
279
|
+
protocol: z.ZodLiteral<"ssdp">;
|
|
280
|
+
searchTarget: z.ZodString;
|
|
281
|
+
}, z.core.$strip>]>>>;
|
|
267
282
|
grants: z.ZodOptional<z.ZodArray<z.ZodType<import("./plugin.permissions.js").PluginGrantRequest, unknown, z.core.$ZodTypeInternals<import("./plugin.permissions.js").PluginGrantRequest, unknown>>>>;
|
|
268
283
|
}, z.core.$strip>;
|
|
269
284
|
configFields: z.ZodArray<z.ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.manifest.d.ts","sourceRoot":"","sources":["../src/plugin.manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE1F;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,WAAW,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,UAAU,CAAC;AAC/C,eAAO,MAAM,uBAAuB,UAAU,CAAC;AAC/C,eAAO,MAAM,4BAA4B,eAAe,CAAC;AAEzD,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,WAAW,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,aAAa,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,UAAU,CAAC;AAE/C;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,cAAc,CAAC;AAEvD;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,WAAW,CAAC;AAEjD;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,SAAS,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,2BAA2B,cAAc,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,eAAe,CAAC;AAEzD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,WAAW,CAAC;AAEjD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD;;;;;;;GAOG;AACH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD,eAAO,MAAM,0BAA0B,aAAa,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,cAAc,CAAC;AAEvD,eAAO,MAAM,yBAAyB,
|
|
1
|
+
{"version":3,"file":"plugin.manifest.d.ts","sourceRoot":"","sources":["../src/plugin.manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import type { DiscoveryQuery } from './plugin.host.js';
|
|
2
3
|
/** The pacing knobs, shared by both kinds of entry. */
|
|
3
4
|
interface NetworkPermissionPacing {
|
|
4
5
|
/**
|
|
@@ -169,6 +170,20 @@ export interface PluginPermissions {
|
|
|
169
170
|
* hosts it may reach are already the ones listed above.
|
|
170
171
|
*/
|
|
171
172
|
sockets?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Whether the plugin may use `host.tls` (an outbound TLS connection carrying
|
|
175
|
+
* a device's own protocol, held to the same {@link network} allowlist).
|
|
176
|
+
*
|
|
177
|
+
* Optional and a disclosure for `sockets`' reasons: only a plugin talking to
|
|
178
|
+
* something that is neither HTTP nor a WebSocket wants one.
|
|
179
|
+
*/
|
|
180
|
+
tls?: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* What the plugin may look for with `host.discover`: each mDNS service type or SSDP search
|
|
183
|
+
* target, exactly. Optional, and absent means none: a plugin that is told its devices' addresses
|
|
184
|
+
* never needs to look for them.
|
|
185
|
+
*/
|
|
186
|
+
discovery?: DiscoveryQuery[];
|
|
172
187
|
/**
|
|
173
188
|
* Capabilities this plugin is ASKING for, each with the reason an operator
|
|
174
189
|
* reads before deciding. See {@link PluginGrantRequest}.
|
|
@@ -190,6 +205,14 @@ export declare const pluginPermissionsSchema: z.ZodObject<{
|
|
|
190
205
|
oauth: z.ZodBoolean;
|
|
191
206
|
trackFetcher: z.ZodOptional<z.ZodBoolean>;
|
|
192
207
|
sockets: z.ZodOptional<z.ZodBoolean>;
|
|
208
|
+
tls: z.ZodOptional<z.ZodBoolean>;
|
|
209
|
+
discovery: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
210
|
+
protocol: z.ZodLiteral<"mdns">;
|
|
211
|
+
service: z.ZodString;
|
|
212
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
213
|
+
protocol: z.ZodLiteral<"ssdp">;
|
|
214
|
+
searchTarget: z.ZodString;
|
|
215
|
+
}, z.core.$strip>]>>>;
|
|
193
216
|
grants: z.ZodOptional<z.ZodArray<z.ZodType<PluginGrantRequest, unknown, z.core.$ZodTypeInternals<PluginGrantRequest, unknown>>>>;
|
|
194
217
|
}, z.core.$strip>;
|
|
195
218
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.permissions.d.ts","sourceRoot":"","sources":["../src/plugin.permissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,uDAAuD;AACvD,UAAU,uBAAuB;IAC7B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,uBAAuB;IAClE;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,WAAW,2BAA4B,SAAQ,uBAAuB;IACxE,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,yEAAyE;AACzE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,qBAAqB,GAAG,2BAA2B,CAAC;AAE7F;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAC/D,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAC9B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAE7B,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;IAEjB,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAC;IAEf;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACjC;AASD,eAAO,MAAM,uBAAuB,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAI/D,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAO3D,CAAC;AAEH,eAAO,MAAM,uBAAuB
|
|
1
|
+
{"version":3,"file":"plugin.permissions.d.ts","sourceRoot":"","sources":["../src/plugin.permissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,uDAAuD;AACvD,UAAU,uBAAuB;IAC7B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,uBAAuB;IAClE;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,WAAW,2BAA4B,SAAQ,uBAAuB;IACxE,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,yEAAyE;AACzE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,qBAAqB,GAAG,2BAA2B,CAAC;AAE7F;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAC/D,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAC9B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAE7B,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;IAEjB,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAC;IAEf;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IAEd;;;;OAIG;IACH,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAE7B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACjC;AASD,eAAO,MAAM,uBAAuB,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAI/D,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAO3D,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;iBAgBlC,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HostFetchInit, HostFetchMethod, PluginHost, PluginSocket, ProviderStream, ProviderTrack } from '../index.js';
|
|
1
|
+
import type { DiscoveredService, DiscoveryQuery, HostFetchInit, HostFetchMethod, PluginHost, PluginSocket, PluginTlsOptions, PluginTlsSocket, ProviderStream, ProviderTrack } from '../index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Shipped from the SDK rather than copied into each plugin, which three of them
|
|
4
4
|
* were doing — two of those carrying a comment saying the copy was deliberate
|
|
@@ -40,6 +40,23 @@ export interface FakePluginSocket extends PluginSocket {
|
|
|
40
40
|
/** Close it from the far end, telling the plugin's close listeners. */
|
|
41
41
|
closeFromServer(code?: number, reason?: string): void;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* One `host.tls` connection the plugin opened, driven from the test: bytes the plugin wrote are in
|
|
45
|
+
* `sent`, and `receive` / `closeFromServer` play the device.
|
|
46
|
+
*/
|
|
47
|
+
export interface FakePluginTlsSocket extends PluginTlsSocket {
|
|
48
|
+
readonly url: string;
|
|
49
|
+
/** The options the plugin opened it with. */
|
|
50
|
+
readonly options: PluginTlsOptions | undefined;
|
|
51
|
+
/** Every write the plugin made, in order, one entry per `send`. */
|
|
52
|
+
readonly sent: Uint8Array[];
|
|
53
|
+
/** Whether either end has closed it. */
|
|
54
|
+
readonly closed: boolean;
|
|
55
|
+
/** Deliver one chunk to the plugin's data listeners. */
|
|
56
|
+
receive(bytes: Uint8Array): void;
|
|
57
|
+
/** Close it from the far end, telling the plugin's close listeners. */
|
|
58
|
+
closeFromServer(reason?: string): void;
|
|
59
|
+
}
|
|
43
60
|
/**
|
|
44
61
|
* A `PluginHost` for tests. `fetch` replays a queue of scripted
|
|
45
62
|
* `Response`s (FIFO, one per call) — or a custom handler installed
|
|
@@ -84,6 +101,20 @@ export interface FakePluginHost extends PluginHost {
|
|
|
84
101
|
readonly sockets: FakePluginSocket[];
|
|
85
102
|
/** Make every later `host.socket` reject with `error`; `undefined` lets them open again. */
|
|
86
103
|
refuseSockets(error: Error | undefined): void;
|
|
104
|
+
/** Every connection `host.tls` opened, in order, closed ones included. */
|
|
105
|
+
readonly tlsSockets: FakePluginTlsSocket[];
|
|
106
|
+
/** Make every later `host.tls` reject with `error`; `undefined` lets them open again. */
|
|
107
|
+
refuseTls(error: Error | undefined): void;
|
|
108
|
+
/**
|
|
109
|
+
* Called with each connection as `host.tls` opens it, before the plugin sees it, so a test can
|
|
110
|
+
* script the device's answers to whatever the plugin writes.
|
|
111
|
+
*/
|
|
112
|
+
onTlsOpen(listener: ((socket: FakePluginTlsSocket) => void) | undefined): void;
|
|
113
|
+
/**
|
|
114
|
+
* What `host.discover` answers from now on: a fixed list, or one worked out per query. Nothing
|
|
115
|
+
* is found until a test says otherwise, which is what a station on a bridge network sees.
|
|
116
|
+
*/
|
|
117
|
+
seedDiscovery(found: DiscoveredService[] | ((query: DiscoveryQuery) => DiscoveredService[])): void;
|
|
87
118
|
}
|
|
88
119
|
/**
|
|
89
120
|
* What a test says a scripted reply should be.
|
|
@@ -112,5 +143,7 @@ export interface FakeResponseInit {
|
|
|
112
143
|
export declare function fakeHostFetchResponse(init?: FakeResponseInit): Response;
|
|
113
144
|
/** A socket that is open until either end closes it. Exported for a test that wants one on its own. */
|
|
114
145
|
export declare function createFakePluginSocket(url: string): FakePluginSocket;
|
|
146
|
+
/** A TLS connection that is open until either end closes it. Exported for a test that wants one on its own. */
|
|
147
|
+
export declare function createFakePluginTlsSocket(url: string, options?: PluginTlsOptions): FakePluginTlsSocket;
|
|
115
148
|
export declare function createFakePluginHost(): FakePluginHost;
|
|
116
149
|
//# sourceMappingURL=fake.plugin.host.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake.plugin.host.d.ts","sourceRoot":"","sources":["../../src/testing/fake.plugin.host.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"fake.plugin.host.d.ts","sourceRoot":"","sources":["../../src/testing/fake.plugin.host.ts"],"names":[],"mappings":"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"}
|
package/dist/testing/index.js
CHANGED
|
@@ -62,6 +62,39 @@ function createFakePluginSocket(url) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
__name(createFakePluginSocket, "createFakePluginSocket");
|
|
65
|
+
function createFakePluginTlsSocket(url, options) {
|
|
66
|
+
const dataListeners = [];
|
|
67
|
+
const closeListeners = [];
|
|
68
|
+
const sent = [];
|
|
69
|
+
let closed = false;
|
|
70
|
+
const shut = /* @__PURE__ */ __name((reason) => {
|
|
71
|
+
if (closed) return;
|
|
72
|
+
closed = true;
|
|
73
|
+
for (const listener of closeListeners) listener(reason);
|
|
74
|
+
}, "shut");
|
|
75
|
+
return {
|
|
76
|
+
url,
|
|
77
|
+
options,
|
|
78
|
+
sent,
|
|
79
|
+
get closed() {
|
|
80
|
+
return closed;
|
|
81
|
+
},
|
|
82
|
+
send: vi.fn((bytes) => {
|
|
83
|
+
if (!closed) sent.push(bytes);
|
|
84
|
+
}),
|
|
85
|
+
close: vi.fn(() => shut(void 0)),
|
|
86
|
+
onData: /* @__PURE__ */ __name((listener) => void dataListeners.push(listener), "onData"),
|
|
87
|
+
onClose: /* @__PURE__ */ __name((listener) => void closeListeners.push(listener), "onClose"),
|
|
88
|
+
receive(bytes) {
|
|
89
|
+
if (closed) throw new Error(`fake plugin tls socket: ${url} is closed`);
|
|
90
|
+
for (const listener of dataListeners) listener(bytes);
|
|
91
|
+
},
|
|
92
|
+
closeFromServer(reason) {
|
|
93
|
+
shut(reason);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
__name(createFakePluginTlsSocket, "createFakePluginTlsSocket");
|
|
65
98
|
function createFakePluginHost() {
|
|
66
99
|
const calls = [];
|
|
67
100
|
const queue = [];
|
|
@@ -78,6 +111,10 @@ function createFakePluginHost() {
|
|
|
78
111
|
let fetchedPlaylist;
|
|
79
112
|
const sockets = [];
|
|
80
113
|
let socketRefusal;
|
|
114
|
+
const tlsSockets = [];
|
|
115
|
+
let tlsRefusal;
|
|
116
|
+
let tlsOpened;
|
|
117
|
+
let discovered = /* @__PURE__ */ __name(() => [], "discovered");
|
|
81
118
|
const fetchImpl = vi.fn(async (url, init) => {
|
|
82
119
|
calls.push({
|
|
83
120
|
url,
|
|
@@ -104,6 +141,14 @@ function createFakePluginHost() {
|
|
|
104
141
|
sockets.push(socket);
|
|
105
142
|
return socket;
|
|
106
143
|
}),
|
|
144
|
+
tls: vi.fn(async (url, options) => {
|
|
145
|
+
if (tlsRefusal !== void 0) throw tlsRefusal;
|
|
146
|
+
const socket = createFakePluginTlsSocket(url, options);
|
|
147
|
+
tlsSockets.push(socket);
|
|
148
|
+
tlsOpened?.(socket);
|
|
149
|
+
return socket;
|
|
150
|
+
}),
|
|
151
|
+
discover: vi.fn(async (query) => discovered(query)),
|
|
107
152
|
// Never aborts: these tests are about what a plugin does with a reply,
|
|
108
153
|
// not about being cancelled half way through one.
|
|
109
154
|
signal: new AbortController().signal,
|
|
@@ -148,6 +193,16 @@ function createFakePluginHost() {
|
|
|
148
193
|
refuseSockets(error) {
|
|
149
194
|
socketRefusal = error;
|
|
150
195
|
},
|
|
196
|
+
tlsSockets,
|
|
197
|
+
refuseTls(error) {
|
|
198
|
+
tlsRefusal = error;
|
|
199
|
+
},
|
|
200
|
+
onTlsOpen(listener) {
|
|
201
|
+
tlsOpened = listener;
|
|
202
|
+
},
|
|
203
|
+
seedDiscovery(found) {
|
|
204
|
+
discovered = typeof found === "function" ? found : () => found;
|
|
205
|
+
},
|
|
151
206
|
seedRemainingMs(ms) {
|
|
152
207
|
remainingMs = ms;
|
|
153
208
|
},
|
|
@@ -193,6 +248,7 @@ __name(createFakePluginHost, "createFakePluginHost");
|
|
|
193
248
|
export {
|
|
194
249
|
createFakePluginHost,
|
|
195
250
|
createFakePluginSocket,
|
|
251
|
+
createFakePluginTlsSocket,
|
|
196
252
|
fakeHostFetchResponse
|
|
197
253
|
};
|
|
198
254
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/fake.plugin.host.ts"],"sourcesContent":["import { vi } from 'vitest';\n\nimport type { HostFetchInit, HostFetchMethod, PluginHost, PluginSocket, ProviderStream, ProviderTrack } from '../index.js';\n\n/**\n * Shipped from the SDK rather than copied into each plugin, which three of them\n * were doing — two of those carrying a comment saying the copy was deliberate\n * because plugins do not depend on each other.\n *\n * That reasoning held for the copies and does not hold for this: every plugin\n * already depends on the SDK, and `PluginHost` is the SDK's own contract, so its\n * test double belongs beside the interface it doubles. A plugin importing this\n * gains no dependency on any other plugin. It also means a host method added to\n * the contract turns up in one fake rather than being added to three and\n * forgotten in the other three.\n *\n * Reached as `@deadair/plugin-sdk/testing`, which is kept out of the runtime\n * entry so nothing ships a dependency on vitest.\n */\n\n/** One `host.fetch` call, recorded with only the fields tests care about. */\nexport interface RecordedFetchCall {\n url: string;\n method: HostFetchMethod | undefined;\n headers: Record<string, string> | undefined;\n body: string | undefined;\n}\n\n/**\n * One `host.socket` the plugin opened, driven from the test: frames the plugin sent are in `sent`,\n * and `receive` / `closeFromServer` play the far end.\n */\nexport interface FakePluginSocket extends PluginSocket {\n readonly url: string;\n /** Every text frame the plugin sent, in order. */\n readonly sent: string[];\n /** Whether either end has closed it. */\n readonly closed: boolean;\n /** The frames the plugin sent, each parsed as JSON. */\n sentJson(): unknown[];\n /** Deliver one text frame to the plugin's listeners. */\n receive(text: string): void;\n /** Deliver `JSON.stringify(value)`. */\n receiveJson(value: unknown): void;\n /** Close it from the far end, telling the plugin's close listeners. */\n closeFromServer(code?: number, reason?: string): void;\n}\n\n/**\n * A `PluginHost` for tests. `fetch` replays a queue of scripted\n * `Response`s (FIFO, one per call) — or a custom handler installed\n * with `setFetchImpl`, for call-count-driven scenarios like a 401-then-200\n * retry — and records every call it received in `calls`. `storage`, `config`,\n * `secrets` and the `oauth` vault are in-memory and can be seeded or read\n * directly, bypassing the `PluginHost` methods, so a test can assert on state\n * without going through the code under test twice.\n */\nexport interface FakePluginHost extends PluginHost {\n /** Every `host.fetch` call so far, in call order. */\n readonly calls: RecordedFetchCall[];\n /** Set what `host.remainingMs()` reports, for testing budget shedding. */\n seedRemainingMs(ms: number): void;\n /** Push one scripted response onto the back of the reply queue. */\n queueResponse(response: FakeResponseInit): void;\n /** Replace the fetch handler outright; overrides the queue while set. */\n setFetchImpl(impl: (url: string, init?: HostFetchInit) => Promise<Response>): void;\n /** Seed the value `host.config.get()` resolves to. */\n seedConfig(config: Record<string, unknown>): void;\n /** Seed a value `host.secrets.get(key)` resolves to. */\n seedSecret(key: string, value: string): void;\n /** Seed a storage entry directly, bypassing `host.storage.set`. */\n seedStorage(key: string, value: unknown): void;\n /** Read a storage entry directly, bypassing `host.storage.get`. */\n getStorageEntry(key: string): unknown;\n /** All storage keys currently set, unfiltered. */\n storageKeys(): string[];\n /** Seed the oauth vault directly, bypassing `host.oauth.saveTokens`. */\n seedTokens(tokens: Record<string, string>): void;\n /** Read the oauth vault directly, bypassing `host.oauth.getTokens`. */\n getVaultTokens(): Record<string, string> | undefined;\n /** Set what `host.trackFetcher.serve()` answers with; `undefined` means \"this station has no fetcher\". */\n seedFetchedTrack(stream: ProviderStream | undefined): void;\n /**\n * Set what `host.trackFetcher.playlistTracks()` answers with. `undefined` means \"this station\n * has no fetcher\"; pass an `Error` to make it throw, which is the fetcher having tried and\n * failed and is a different case for a plugin to handle.\n */\n seedFetchedPlaylist(tracks: ProviderTrack[] | Error | undefined): void;\n /** Every socket `host.socket` opened, in order, closed ones included. */\n readonly sockets: FakePluginSocket[];\n /** Make every later `host.socket` reject with `error`; `undefined` lets them open again. */\n refuseSockets(error: Error | undefined): void;\n}\n\n/**\n * What `host.remainingMs()` reports unless a test says otherwise. Matches the\n * host's default per-call deadline, so a plugin that only sheds work when the\n * budget is tight behaves in tests the way it does on a healthy station.\n */\nconst DEFAULT_REMAINING_MS = 15_000;\n\n/**\n * What a test says a scripted reply should be.\n *\n * Not `ResponseInit`, because `url` is not on it: `host.fetch` reports the last\n * hop of the redirect chain there, and a `Response` built by hand has an empty\n * one unless it is defined in.\n */\nexport interface FakeResponseInit {\n status?: number;\n statusText?: string;\n headers?: Record<string, string>;\n /**\n * Text for a JSON or error reply, bytes for a binary one.\n *\n * `Uint8Array<ArrayBuffer>` rather than a bare `Uint8Array`: the DOM lib's\n * `BodyInit` accepts a view over a real `ArrayBuffer`, and the unparameterized\n * form widens to `ArrayBufferLike`, which includes `SharedArrayBuffer` and is\n * not something `Response` will take. `new Uint8Array([...])` already has the\n * narrow type, so no test has to say so.\n */\n body?: string | Uint8Array<ArrayBuffer>;\n url?: string;\n}\n\n/** Statuses whose `Response` must carry a null body, or the constructor throws. */\nconst NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);\n\n/** Builds a default 200 `Response`, overridable field by field. */\nexport function fakeHostFetchResponse(init: FakeResponseInit = {}): Response {\n const status = init.status ?? 200;\n\n const response = new Response(NULL_BODY_STATUSES.has(status) ? null : (init.body ?? ''), {\n status,\n statusText: init.statusText ?? 'OK',\n headers: init.headers ?? { 'content-type': 'application/json' },\n });\n\n // Read-only on a constructed response, and part of what the host promises.\n Object.defineProperty(response, 'url', { value: init.url ?? 'https://example.test/' });\n\n return response;\n}\n\n/** A socket that is open until either end closes it. Exported for a test that wants one on its own. */\nexport function createFakePluginSocket(url: string): FakePluginSocket {\n const messageListeners: ((text: string) => void)[] = [];\n const closeListeners: ((code?: number, reason?: string) => void)[] = [];\n const sent: string[] = [];\n let closed = false;\n\n const shut = (code: number | undefined, reason: string | undefined): void => {\n if (closed) return;\n closed = true;\n for (const listener of closeListeners) listener(code, reason);\n };\n\n return {\n url,\n sent,\n get closed() {\n return closed;\n },\n send: vi.fn((text: string) => {\n if (!closed) sent.push(text);\n }),\n close: vi.fn((code?: number, reason?: string) => shut(code ?? 1000, reason)),\n onMessage: listener => void messageListeners.push(listener),\n onClose: listener => void closeListeners.push(listener),\n sentJson: () => sent.map(text => JSON.parse(text) as unknown),\n receive(text) {\n if (closed) throw new Error(`fake plugin socket: ${url} is closed`);\n for (const listener of messageListeners) listener(text);\n },\n receiveJson(value) {\n this.receive(JSON.stringify(value));\n },\n closeFromServer(code = 1000, reason = '') {\n shut(code, reason);\n },\n };\n}\n\nexport function createFakePluginHost(): FakePluginHost {\n const calls: RecordedFetchCall[] = [];\n const queue: Response[] = [];\n let customImpl: ((url: string, init?: HostFetchInit) => Promise<Response>) | undefined;\n\n const storageData = new Map<string, unknown>();\n let configData: Record<string, unknown> = {};\n const secretsData = new Map<string, string>();\n let tokens: Record<string, string> | undefined;\n let remainingMs = DEFAULT_REMAINING_MS;\n let fetchedTrack: ProviderStream | undefined = { url: 'http://127.0.0.1:3679/track/song-1?t=signed', expiresAt: 1_893_456_000_000 };\n // Nothing by default: a plugin should ask its own API first, and a test that means to exercise\n // the fetcher says so.\n let fetchedPlaylist: ProviderTrack[] | Error | undefined;\n const sockets: FakePluginSocket[] = [];\n let socketRefusal: Error | undefined;\n\n const fetchImpl = vi.fn(async (url: string, init?: HostFetchInit): Promise<Response> => {\n calls.push({ url, method: init?.method, headers: init?.headers, body: init?.body });\n if (customImpl) return customImpl(url, init);\n const next = queue.shift();\n if (!next) throw new Error(`fake plugin host: no response queued for ${init?.method ?? 'GET'} ${url}`);\n return next;\n });\n\n const host: FakePluginHost = {\n logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },\n fetch: fetchImpl,\n socket: vi.fn(async (url: string) => {\n if (socketRefusal !== undefined) throw socketRefusal;\n const socket = createFakePluginSocket(url);\n sockets.push(socket);\n return socket;\n }),\n // Never aborts: these tests are about what a plugin does with a reply,\n // not about being cancelled half way through one.\n signal: new AbortController().signal,\n remainingMs: vi.fn(() => remainingMs),\n storage: {\n get: vi.fn(async (key: string) => storageData.get(key)),\n set: vi.fn(async (key: string, value: unknown) => {\n storageData.set(key, value);\n }),\n delete: vi.fn(async (key: string) => {\n storageData.delete(key);\n }),\n list: vi.fn(async (prefix?: string) => [...storageData.keys()].filter(key => !prefix || key.startsWith(prefix))),\n },\n secrets: { get: vi.fn(async (key: string) => secretsData.get(key)) },\n config: { get: vi.fn(async () => configData) },\n oauth: {\n getRedirectUri: vi.fn(async () => 'https://example.test/callback'),\n saveTokens: vi.fn(async (next: Record<string, string>) => {\n tokens = next;\n }),\n getTokens: vi.fn(async () => tokens),\n },\n events: { emit: vi.fn() },\n trackFetcher: {\n serve: vi.fn(async () => fetchedTrack),\n playlistTracks: vi.fn(async () => {\n if (fetchedPlaylist instanceof Error) throw fetchedPlaylist;\n return fetchedPlaylist;\n }),\n },\n calls,\n sockets,\n refuseSockets(error) {\n socketRefusal = error;\n },\n seedRemainingMs(ms) {\n remainingMs = ms;\n },\n queueResponse(response: FakeResponseInit) {\n queue.push(fakeHostFetchResponse(response));\n },\n setFetchImpl(impl) {\n customImpl = impl;\n },\n seedConfig(config) {\n configData = config;\n },\n seedSecret(key, value) {\n secretsData.set(key, value);\n },\n seedStorage(key, value) {\n storageData.set(key, value);\n },\n getStorageEntry(key) {\n return storageData.get(key);\n },\n storageKeys() {\n return [...storageData.keys()];\n },\n seedTokens(next) {\n tokens = next;\n },\n getVaultTokens() {\n return tokens;\n },\n seedFetchedTrack(stream) {\n fetchedTrack = stream;\n },\n seedFetchedPlaylist(tracks) {\n fetchedPlaylist = tracks;\n },\n };\n\n return host;\n}\n"],"mappings":";;;;;AAAA,SAASA,UAAU;AAmGnB,IAAMC,uBAAuB;AA2B7B,IAAMC,qBAAqB,oBAAIC,IAAI;EAAC;EAAK;EAAK;EAAK;CAAI;AAGhD,SAASC,sBAAsBC,OAAyB,CAAC,GAAC;AAC7D,QAAMC,SAASD,KAAKC,UAAU;AAE9B,QAAMC,WAAW,IAAIC,SAASN,mBAAmBO,IAAIH,MAAAA,IAAU,OAAQD,KAAKK,QAAQ,IAAK;IACrFJ;IACAK,YAAYN,KAAKM,cAAc;IAC/BC,SAASP,KAAKO,WAAW;MAAE,gBAAgB;IAAmB;EAClE,CAAA;AAGAC,SAAOC,eAAeP,UAAU,OAAO;IAAEQ,OAAOV,KAAKW,OAAO;EAAwB,CAAA;AAEpF,SAAOT;AACX;AAbgBH;AAgBT,SAASa,uBAAuBD,KAAW;AAC9C,QAAME,mBAA+C,CAAA;AACrD,QAAMC,iBAA+D,CAAA;AACrE,QAAMC,OAAiB,CAAA;AACvB,MAAIC,SAAS;AAEb,QAAMC,OAAO,wBAACC,MAA0BC,WAAAA;AACpC,QAAIH,OAAQ;AACZA,aAAS;AACT,eAAWI,YAAYN,eAAgBM,UAASF,MAAMC,MAAAA;EAC1D,GAJa;AAMb,SAAO;IACHR;IACAI;IACA,IAAIC,SAAS;AACT,aAAOA;IACX;IACAK,MAAMC,GAAGC,GAAG,CAACC,SAAAA;AACT,UAAI,CAACR,OAAQD,MAAKU,KAAKD,IAAAA;IAC3B,CAAA;IACAE,OAAOJ,GAAGC,GAAG,CAACL,MAAeC,WAAoBF,KAAKC,QAAQ,KAAMC,MAAAA,CAAAA;IACpEQ,WAAWP,wBAAAA,aAAY,KAAKP,iBAAiBY,KAAKL,QAAAA,GAAvCA;IACXQ,SAASR,wBAAAA,aAAY,KAAKN,eAAeW,KAAKL,QAAAA,GAArCA;IACTS,UAAU,6BAAMd,KAAKe,IAAIN,CAAAA,SAAQO,KAAKC,MAAMR,IAAAA,CAAAA,GAAlC;IACVS,QAAQT,MAAI;AACR,UAAIR,OAAQ,OAAM,IAAIkB,MAAM,uBAAuBvB,GAAAA,YAAe;AAClE,iBAAWS,YAAYP,iBAAkBO,UAASI,IAAAA;IACtD;IACAW,YAAYzB,OAAK;AACb,WAAKuB,QAAQF,KAAKK,UAAU1B,KAAAA,CAAAA;IAChC;IACA2B,gBAAgBnB,OAAO,KAAMC,SAAS,IAAE;AACpCF,WAAKC,MAAMC,MAAAA;IACf;EACJ;AACJ;AApCgBP;AAsCT,SAAS0B,uBAAAA;AACZ,QAAMC,QAA6B,CAAA;AACnC,QAAMC,QAAoB,CAAA;AAC1B,MAAIC;AAEJ,QAAMC,cAAc,oBAAIC,IAAAA;AACxB,MAAIC,aAAsC,CAAC;AAC3C,QAAMC,cAAc,oBAAIF,IAAAA;AACxB,MAAIG;AACJ,MAAIC,cAAcnD;AAClB,MAAIoD,eAA2C;IAAErC,KAAK;IAA+CsC,WAAW;EAAkB;AAGlI,MAAIC;AACJ,QAAMC,UAA8B,CAAA;AACpC,MAAIC;AAEJ,QAAMC,YAAY/B,GAAGC,GAAG,OAAOZ,KAAaX,SAAAA;AACxCuC,UAAMd,KAAK;MAAEd;MAAK2C,QAAQtD,MAAMsD;MAAQ/C,SAASP,MAAMO;MAASF,MAAML,MAAMK;IAAK,CAAA;AACjF,QAAIoC,WAAY,QAAOA,WAAW9B,KAAKX,IAAAA;AACvC,UAAMuD,OAAOf,MAAMgB,MAAK;AACxB,QAAI,CAACD,KAAM,OAAM,IAAIrB,MAAM,4CAA4ClC,MAAMsD,UAAU,KAAA,IAAS3C,GAAAA,EAAK;AACrG,WAAO4C;EACX,CAAA;AAEA,QAAME,OAAuB;IACzBC,QAAQ;MAAEC,OAAOrC,GAAGC,GAAE;MAAIqC,MAAMtC,GAAGC,GAAE;MAAIsC,MAAMvC,GAAGC,GAAE;MAAIuC,OAAOxC,GAAGC,GAAE;IAAG;IACvEwC,OAAOV;IACPW,QAAQ1C,GAAGC,GAAG,OAAOZ,QAAAA;AACjB,UAAIyC,kBAAkBa,OAAW,OAAMb;AACvC,YAAMY,SAASpD,uBAAuBD,GAAAA;AACtCwC,cAAQ1B,KAAKuC,MAAAA;AACb,aAAOA;IACX,CAAA;;;IAGAE,QAAQ,IAAIC,gBAAAA,EAAkBD;IAC9BnB,aAAazB,GAAGC,GAAG,MAAMwB,WAAAA;IACzBqB,SAAS;MACLC,KAAK/C,GAAGC,GAAG,OAAO+C,QAAgB5B,YAAY2B,IAAIC,GAAAA,CAAAA;MAClDC,KAAKjD,GAAGC,GAAG,OAAO+C,KAAa5D,UAAAA;AAC3BgC,oBAAY6B,IAAID,KAAK5D,KAAAA;MACzB,CAAA;MACA8D,QAAQlD,GAAGC,GAAG,OAAO+C,QAAAA;AACjB5B,oBAAY8B,OAAOF,GAAAA;MACvB,CAAA;MACAG,MAAMnD,GAAGC,GAAG,OAAOmD,WAAoB;WAAIhC,YAAYiC,KAAI;QAAIC,OAAON,CAAAA,QAAO,CAACI,UAAUJ,IAAIO,WAAWH,MAAAA,CAAAA,CAAAA;IAC3G;IACAI,SAAS;MAAET,KAAK/C,GAAGC,GAAG,OAAO+C,QAAgBzB,YAAYwB,IAAIC,GAAAA,CAAAA;IAAM;IACnES,QAAQ;MAAEV,KAAK/C,GAAGC,GAAG,YAAYqB,UAAAA;IAAY;IAC7CoC,OAAO;MACHC,gBAAgB3D,GAAGC,GAAG,YAAY,+BAAA;MAClC2D,YAAY5D,GAAGC,GAAG,OAAOgC,SAAAA;AACrBT,iBAASS;MACb,CAAA;MACA4B,WAAW7D,GAAGC,GAAG,YAAYuB,MAAAA;IACjC;IACAsC,QAAQ;MAAEC,MAAM/D,GAAGC,GAAE;IAAG;IACxB+D,cAAc;MACVC,OAAOjE,GAAGC,GAAG,YAAYyB,YAAAA;MACzBwC,gBAAgBlE,GAAGC,GAAG,YAAA;AAClB,YAAI2B,2BAA2BhB,MAAO,OAAMgB;AAC5C,eAAOA;MACX,CAAA;IACJ;IACAX;IACAY;IACAsC,cAAc3B,OAAK;AACfV,sBAAgBU;IACpB;IACA4B,gBAAgBC,IAAE;AACd5C,oBAAc4C;IAClB;IACAC,cAAc1F,UAA0B;AACpCsC,YAAMf,KAAK1B,sBAAsBG,QAAAA,CAAAA;IACrC;IACA2F,aAAaC,MAAI;AACbrD,mBAAaqD;IACjB;IACAC,WAAWhB,QAAM;AACbnC,mBAAamC;IACjB;IACAiB,WAAW1B,KAAK5D,OAAK;AACjBmC,kBAAY0B,IAAID,KAAK5D,KAAAA;IACzB;IACAuF,YAAY3B,KAAK5D,OAAK;AAClBgC,kBAAY6B,IAAID,KAAK5D,KAAAA;IACzB;IACAwF,gBAAgB5B,KAAG;AACf,aAAO5B,YAAY2B,IAAIC,GAAAA;IAC3B;IACA6B,cAAAA;AACI,aAAO;WAAIzD,YAAYiC,KAAI;;IAC/B;IACAyB,WAAW7C,MAAI;AACXT,eAASS;IACb;IACA8C,iBAAAA;AACI,aAAOvD;IACX;IACAwD,iBAAiBC,QAAM;AACnBvD,qBAAeuD;IACnB;IACAC,oBAAoBC,QAAM;AACtBvD,wBAAkBuD;IACtB;EACJ;AAEA,SAAOhD;AACX;AA7GgBnB;","names":["vi","DEFAULT_REMAINING_MS","NULL_BODY_STATUSES","Set","fakeHostFetchResponse","init","status","response","Response","has","body","statusText","headers","Object","defineProperty","value","url","createFakePluginSocket","messageListeners","closeListeners","sent","closed","shut","code","reason","listener","send","vi","fn","text","push","close","onMessage","onClose","sentJson","map","JSON","parse","receive","Error","receiveJson","stringify","closeFromServer","createFakePluginHost","calls","queue","customImpl","storageData","Map","configData","secretsData","tokens","remainingMs","fetchedTrack","expiresAt","fetchedPlaylist","sockets","socketRefusal","fetchImpl","method","next","shift","host","logger","debug","info","warn","error","fetch","socket","undefined","signal","AbortController","storage","get","key","set","delete","list","prefix","keys","filter","startsWith","secrets","config","oauth","getRedirectUri","saveTokens","getTokens","events","emit","trackFetcher","serve","playlistTracks","refuseSockets","seedRemainingMs","ms","queueResponse","setFetchImpl","impl","seedConfig","seedSecret","seedStorage","getStorageEntry","storageKeys","seedTokens","getVaultTokens","seedFetchedTrack","stream","seedFetchedPlaylist","tracks"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/fake.plugin.host.ts"],"sourcesContent":["import { vi } from 'vitest';\n\nimport type {\n DiscoveredService,\n DiscoveryQuery,\n HostFetchInit,\n HostFetchMethod,\n PluginHost,\n PluginSocket,\n PluginTlsOptions,\n PluginTlsSocket,\n ProviderStream,\n ProviderTrack,\n} from '../index.js';\n\n/**\n * Shipped from the SDK rather than copied into each plugin, which three of them\n * were doing — two of those carrying a comment saying the copy was deliberate\n * because plugins do not depend on each other.\n *\n * That reasoning held for the copies and does not hold for this: every plugin\n * already depends on the SDK, and `PluginHost` is the SDK's own contract, so its\n * test double belongs beside the interface it doubles. A plugin importing this\n * gains no dependency on any other plugin. It also means a host method added to\n * the contract turns up in one fake rather than being added to three and\n * forgotten in the other three.\n *\n * Reached as `@deadair/plugin-sdk/testing`, which is kept out of the runtime\n * entry so nothing ships a dependency on vitest.\n */\n\n/** One `host.fetch` call, recorded with only the fields tests care about. */\nexport interface RecordedFetchCall {\n url: string;\n method: HostFetchMethod | undefined;\n headers: Record<string, string> | undefined;\n body: string | undefined;\n}\n\n/**\n * One `host.socket` the plugin opened, driven from the test: frames the plugin sent are in `sent`,\n * and `receive` / `closeFromServer` play the far end.\n */\nexport interface FakePluginSocket extends PluginSocket {\n readonly url: string;\n /** Every text frame the plugin sent, in order. */\n readonly sent: string[];\n /** Whether either end has closed it. */\n readonly closed: boolean;\n /** The frames the plugin sent, each parsed as JSON. */\n sentJson(): unknown[];\n /** Deliver one text frame to the plugin's listeners. */\n receive(text: string): void;\n /** Deliver `JSON.stringify(value)`. */\n receiveJson(value: unknown): void;\n /** Close it from the far end, telling the plugin's close listeners. */\n closeFromServer(code?: number, reason?: string): void;\n}\n\n/**\n * One `host.tls` connection the plugin opened, driven from the test: bytes the plugin wrote are in\n * `sent`, and `receive` / `closeFromServer` play the device.\n */\nexport interface FakePluginTlsSocket extends PluginTlsSocket {\n readonly url: string;\n /** The options the plugin opened it with. */\n readonly options: PluginTlsOptions | undefined;\n /** Every write the plugin made, in order, one entry per `send`. */\n readonly sent: Uint8Array[];\n /** Whether either end has closed it. */\n readonly closed: boolean;\n /** Deliver one chunk to the plugin's data listeners. */\n receive(bytes: Uint8Array): void;\n /** Close it from the far end, telling the plugin's close listeners. */\n closeFromServer(reason?: string): void;\n}\n\n/**\n * A `PluginHost` for tests. `fetch` replays a queue of scripted\n * `Response`s (FIFO, one per call) — or a custom handler installed\n * with `setFetchImpl`, for call-count-driven scenarios like a 401-then-200\n * retry — and records every call it received in `calls`. `storage`, `config`,\n * `secrets` and the `oauth` vault are in-memory and can be seeded or read\n * directly, bypassing the `PluginHost` methods, so a test can assert on state\n * without going through the code under test twice.\n */\nexport interface FakePluginHost extends PluginHost {\n /** Every `host.fetch` call so far, in call order. */\n readonly calls: RecordedFetchCall[];\n /** Set what `host.remainingMs()` reports, for testing budget shedding. */\n seedRemainingMs(ms: number): void;\n /** Push one scripted response onto the back of the reply queue. */\n queueResponse(response: FakeResponseInit): void;\n /** Replace the fetch handler outright; overrides the queue while set. */\n setFetchImpl(impl: (url: string, init?: HostFetchInit) => Promise<Response>): void;\n /** Seed the value `host.config.get()` resolves to. */\n seedConfig(config: Record<string, unknown>): void;\n /** Seed a value `host.secrets.get(key)` resolves to. */\n seedSecret(key: string, value: string): void;\n /** Seed a storage entry directly, bypassing `host.storage.set`. */\n seedStorage(key: string, value: unknown): void;\n /** Read a storage entry directly, bypassing `host.storage.get`. */\n getStorageEntry(key: string): unknown;\n /** All storage keys currently set, unfiltered. */\n storageKeys(): string[];\n /** Seed the oauth vault directly, bypassing `host.oauth.saveTokens`. */\n seedTokens(tokens: Record<string, string>): void;\n /** Read the oauth vault directly, bypassing `host.oauth.getTokens`. */\n getVaultTokens(): Record<string, string> | undefined;\n /** Set what `host.trackFetcher.serve()` answers with; `undefined` means \"this station has no fetcher\". */\n seedFetchedTrack(stream: ProviderStream | undefined): void;\n /**\n * Set what `host.trackFetcher.playlistTracks()` answers with. `undefined` means \"this station\n * has no fetcher\"; pass an `Error` to make it throw, which is the fetcher having tried and\n * failed and is a different case for a plugin to handle.\n */\n seedFetchedPlaylist(tracks: ProviderTrack[] | Error | undefined): void;\n /** Every socket `host.socket` opened, in order, closed ones included. */\n readonly sockets: FakePluginSocket[];\n /** Make every later `host.socket` reject with `error`; `undefined` lets them open again. */\n refuseSockets(error: Error | undefined): void;\n /** Every connection `host.tls` opened, in order, closed ones included. */\n readonly tlsSockets: FakePluginTlsSocket[];\n /** Make every later `host.tls` reject with `error`; `undefined` lets them open again. */\n refuseTls(error: Error | undefined): void;\n /**\n * Called with each connection as `host.tls` opens it, before the plugin sees it, so a test can\n * script the device's answers to whatever the plugin writes.\n */\n onTlsOpen(listener: ((socket: FakePluginTlsSocket) => void) | undefined): void;\n /**\n * What `host.discover` answers from now on: a fixed list, or one worked out per query. Nothing\n * is found until a test says otherwise, which is what a station on a bridge network sees.\n */\n seedDiscovery(found: DiscoveredService[] | ((query: DiscoveryQuery) => DiscoveredService[])): void;\n}\n\n/**\n * What `host.remainingMs()` reports unless a test says otherwise. Matches the\n * host's default per-call deadline, so a plugin that only sheds work when the\n * budget is tight behaves in tests the way it does on a healthy station.\n */\nconst DEFAULT_REMAINING_MS = 15_000;\n\n/**\n * What a test says a scripted reply should be.\n *\n * Not `ResponseInit`, because `url` is not on it: `host.fetch` reports the last\n * hop of the redirect chain there, and a `Response` built by hand has an empty\n * one unless it is defined in.\n */\nexport interface FakeResponseInit {\n status?: number;\n statusText?: string;\n headers?: Record<string, string>;\n /**\n * Text for a JSON or error reply, bytes for a binary one.\n *\n * `Uint8Array<ArrayBuffer>` rather than a bare `Uint8Array`: the DOM lib's\n * `BodyInit` accepts a view over a real `ArrayBuffer`, and the unparameterized\n * form widens to `ArrayBufferLike`, which includes `SharedArrayBuffer` and is\n * not something `Response` will take. `new Uint8Array([...])` already has the\n * narrow type, so no test has to say so.\n */\n body?: string | Uint8Array<ArrayBuffer>;\n url?: string;\n}\n\n/** Statuses whose `Response` must carry a null body, or the constructor throws. */\nconst NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);\n\n/** Builds a default 200 `Response`, overridable field by field. */\nexport function fakeHostFetchResponse(init: FakeResponseInit = {}): Response {\n const status = init.status ?? 200;\n\n const response = new Response(NULL_BODY_STATUSES.has(status) ? null : (init.body ?? ''), {\n status,\n statusText: init.statusText ?? 'OK',\n headers: init.headers ?? { 'content-type': 'application/json' },\n });\n\n // Read-only on a constructed response, and part of what the host promises.\n Object.defineProperty(response, 'url', { value: init.url ?? 'https://example.test/' });\n\n return response;\n}\n\n/** A socket that is open until either end closes it. Exported for a test that wants one on its own. */\nexport function createFakePluginSocket(url: string): FakePluginSocket {\n const messageListeners: ((text: string) => void)[] = [];\n const closeListeners: ((code?: number, reason?: string) => void)[] = [];\n const sent: string[] = [];\n let closed = false;\n\n const shut = (code: number | undefined, reason: string | undefined): void => {\n if (closed) return;\n closed = true;\n for (const listener of closeListeners) listener(code, reason);\n };\n\n return {\n url,\n sent,\n get closed() {\n return closed;\n },\n send: vi.fn((text: string) => {\n if (!closed) sent.push(text);\n }),\n close: vi.fn((code?: number, reason?: string) => shut(code ?? 1000, reason)),\n onMessage: listener => void messageListeners.push(listener),\n onClose: listener => void closeListeners.push(listener),\n sentJson: () => sent.map(text => JSON.parse(text) as unknown),\n receive(text) {\n if (closed) throw new Error(`fake plugin socket: ${url} is closed`);\n for (const listener of messageListeners) listener(text);\n },\n receiveJson(value) {\n this.receive(JSON.stringify(value));\n },\n closeFromServer(code = 1000, reason = '') {\n shut(code, reason);\n },\n };\n}\n\n/** A TLS connection that is open until either end closes it. Exported for a test that wants one on its own. */\nexport function createFakePluginTlsSocket(url: string, options?: PluginTlsOptions): FakePluginTlsSocket {\n const dataListeners: ((bytes: Uint8Array) => void)[] = [];\n const closeListeners: ((reason?: string) => void)[] = [];\n const sent: Uint8Array[] = [];\n let closed = false;\n\n const shut = (reason: string | undefined): void => {\n if (closed) return;\n closed = true;\n for (const listener of closeListeners) listener(reason);\n };\n\n return {\n url,\n options,\n sent,\n get closed() {\n return closed;\n },\n send: vi.fn((bytes: Uint8Array) => {\n if (!closed) sent.push(bytes);\n }),\n close: vi.fn(() => shut(undefined)),\n onData: listener => void dataListeners.push(listener),\n onClose: listener => void closeListeners.push(listener),\n receive(bytes) {\n if (closed) throw new Error(`fake plugin tls socket: ${url} is closed`);\n for (const listener of dataListeners) listener(bytes);\n },\n closeFromServer(reason) {\n shut(reason);\n },\n };\n}\n\nexport function createFakePluginHost(): FakePluginHost {\n const calls: RecordedFetchCall[] = [];\n const queue: Response[] = [];\n let customImpl: ((url: string, init?: HostFetchInit) => Promise<Response>) | undefined;\n\n const storageData = new Map<string, unknown>();\n let configData: Record<string, unknown> = {};\n const secretsData = new Map<string, string>();\n let tokens: Record<string, string> | undefined;\n let remainingMs = DEFAULT_REMAINING_MS;\n let fetchedTrack: ProviderStream | undefined = { url: 'http://127.0.0.1:3679/track/song-1?t=signed', expiresAt: 1_893_456_000_000 };\n // Nothing by default: a plugin should ask its own API first, and a test that means to exercise\n // the fetcher says so.\n let fetchedPlaylist: ProviderTrack[] | Error | undefined;\n const sockets: FakePluginSocket[] = [];\n let socketRefusal: Error | undefined;\n const tlsSockets: FakePluginTlsSocket[] = [];\n let tlsRefusal: Error | undefined;\n let tlsOpened: ((socket: FakePluginTlsSocket) => void) | undefined;\n let discovered: (query: DiscoveryQuery) => DiscoveredService[] = () => [];\n\n const fetchImpl = vi.fn(async (url: string, init?: HostFetchInit): Promise<Response> => {\n calls.push({ url, method: init?.method, headers: init?.headers, body: init?.body });\n if (customImpl) return customImpl(url, init);\n const next = queue.shift();\n if (!next) throw new Error(`fake plugin host: no response queued for ${init?.method ?? 'GET'} ${url}`);\n return next;\n });\n\n const host: FakePluginHost = {\n logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },\n fetch: fetchImpl,\n socket: vi.fn(async (url: string) => {\n if (socketRefusal !== undefined) throw socketRefusal;\n const socket = createFakePluginSocket(url);\n sockets.push(socket);\n return socket;\n }),\n tls: vi.fn(async (url: string, options?: PluginTlsOptions) => {\n if (tlsRefusal !== undefined) throw tlsRefusal;\n const socket = createFakePluginTlsSocket(url, options);\n tlsSockets.push(socket);\n tlsOpened?.(socket);\n return socket;\n }),\n discover: vi.fn(async (query: DiscoveryQuery) => discovered(query)),\n // Never aborts: these tests are about what a plugin does with a reply,\n // not about being cancelled half way through one.\n signal: new AbortController().signal,\n remainingMs: vi.fn(() => remainingMs),\n storage: {\n get: vi.fn(async (key: string) => storageData.get(key)),\n set: vi.fn(async (key: string, value: unknown) => {\n storageData.set(key, value);\n }),\n delete: vi.fn(async (key: string) => {\n storageData.delete(key);\n }),\n list: vi.fn(async (prefix?: string) => [...storageData.keys()].filter(key => !prefix || key.startsWith(prefix))),\n },\n secrets: { get: vi.fn(async (key: string) => secretsData.get(key)) },\n config: { get: vi.fn(async () => configData) },\n oauth: {\n getRedirectUri: vi.fn(async () => 'https://example.test/callback'),\n saveTokens: vi.fn(async (next: Record<string, string>) => {\n tokens = next;\n }),\n getTokens: vi.fn(async () => tokens),\n },\n events: { emit: vi.fn() },\n trackFetcher: {\n serve: vi.fn(async () => fetchedTrack),\n playlistTracks: vi.fn(async () => {\n if (fetchedPlaylist instanceof Error) throw fetchedPlaylist;\n return fetchedPlaylist;\n }),\n },\n calls,\n sockets,\n refuseSockets(error) {\n socketRefusal = error;\n },\n tlsSockets,\n refuseTls(error) {\n tlsRefusal = error;\n },\n onTlsOpen(listener) {\n tlsOpened = listener;\n },\n seedDiscovery(found) {\n discovered = typeof found === 'function' ? found : () => found;\n },\n seedRemainingMs(ms) {\n remainingMs = ms;\n },\n queueResponse(response: FakeResponseInit) {\n queue.push(fakeHostFetchResponse(response));\n },\n setFetchImpl(impl) {\n customImpl = impl;\n },\n seedConfig(config) {\n configData = config;\n },\n seedSecret(key, value) {\n secretsData.set(key, value);\n },\n seedStorage(key, value) {\n storageData.set(key, value);\n },\n getStorageEntry(key) {\n return storageData.get(key);\n },\n storageKeys() {\n return [...storageData.keys()];\n },\n seedTokens(next) {\n tokens = next;\n },\n getVaultTokens() {\n return tokens;\n },\n seedFetchedTrack(stream) {\n fetchedTrack = stream;\n },\n seedFetchedPlaylist(tracks) {\n fetchedPlaylist = tracks;\n },\n };\n\n return host;\n}\n"],"mappings":"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","names":["vi","DEFAULT_REMAINING_MS","NULL_BODY_STATUSES","Set","fakeHostFetchResponse","init","status","response","Response","has","body","statusText","headers","Object","defineProperty","value","url","createFakePluginSocket","messageListeners","closeListeners","sent","closed","shut","code","reason","listener","send","vi","fn","text","push","close","onMessage","onClose","sentJson","map","JSON","parse","receive","Error","receiveJson","stringify","closeFromServer","createFakePluginTlsSocket","options","dataListeners","bytes","undefined","onData","createFakePluginHost","calls","queue","customImpl","storageData","Map","configData","secretsData","tokens","remainingMs","fetchedTrack","expiresAt","fetchedPlaylist","sockets","socketRefusal","tlsSockets","tlsRefusal","tlsOpened","discovered","fetchImpl","method","next","shift","host","logger","debug","info","warn","error","fetch","socket","tls","discover","query","signal","AbortController","storage","get","key","set","delete","list","prefix","keys","filter","startsWith","secrets","config","oauth","getRedirectUri","saveTokens","getTokens","events","emit","trackFetcher","serve","playlistTracks","refuseSockets","refuseTls","onTlsOpen","seedDiscovery","found","seedRemainingMs","ms","queueResponse","setFetchImpl","impl","seedConfig","seedSecret","seedStorage","getStorageEntry","storageKeys","seedTokens","getVaultTokens","seedFetchedTrack","stream","seedFetchedPlaylist","tracks"]}
|
package/package.json
CHANGED