@deadair/plugin-sdk 0.11.1 → 0.12.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 +31 -0
- package/dist/boundary.json.safe.d.ts +3 -2
- package/dist/boundary.json.safe.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin.host.d.ts +51 -14
- package/dist/plugin.host.d.ts.map +1 -1
- package/dist/testing/fake.plugin.host.d.ts +7 -1
- package/dist/testing/fake.plugin.host.d.ts.map +1 -1
- package/dist/testing/index.js +9 -1
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin.host.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* them. `TrackFetchSession` and `TrackFetchRequest` are the exceptions in this
|
|
12
12
|
* file: they go over HTTP to the track fetcher, so they stay asserted.
|
|
13
13
|
*/
|
|
14
|
-
import type { ProviderStream } from './capabilities/music.provider.js';
|
|
14
|
+
import type { ProviderStream, ProviderTrack } from './capabilities/music.provider.js';
|
|
15
15
|
/** Structured logging. Goes to the host's logger, tagged with the plugin id. */
|
|
16
16
|
export interface PluginLogger {
|
|
17
17
|
debug(message: string, meta?: Record<string, unknown>): void;
|
|
@@ -99,23 +99,41 @@ export interface TrackFetchRequest {
|
|
|
99
99
|
trackId: string;
|
|
100
100
|
session: TrackFetchSession;
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* One page of a playlist, asked of the fetcher rather than of the provider's own API.
|
|
104
|
+
*
|
|
105
|
+
* No session, unlike {@link TrackFetchRequest}: the fetcher reads on the login IT holds. That is
|
|
106
|
+
* not a convenience — a token minted for the plugin's own app is refused for this by the provider,
|
|
107
|
+
* which is the whole reason the fetcher has an authorization of its own.
|
|
108
|
+
*/
|
|
109
|
+
export interface PlaylistTracksRequest {
|
|
110
|
+
/** Provider-scoped playlist id, exactly as the plugin's own listing reports it. */
|
|
111
|
+
playlistId: string;
|
|
112
|
+
/** Where in the playlist to read from. Absent means the start. */
|
|
113
|
+
offset?: number;
|
|
114
|
+
/** How many tracks to describe. Absent lets the fetcher choose its own page size. */
|
|
115
|
+
limit?: number;
|
|
116
|
+
}
|
|
102
117
|
/**
|
|
103
118
|
* The station's own track fetcher: a helper process, running beside the audio
|
|
104
|
-
* player, that speaks a provider's protocol
|
|
105
|
-
* audio over HTTP.
|
|
119
|
+
* player, that speaks a provider's protocol on a login of its own.
|
|
106
120
|
*
|
|
107
|
-
* This exists for one shape of provider: the
|
|
108
|
-
* process speaking a protocol the plugin does not. Spotify is
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
121
|
+
* This exists for one shape of provider: what the plugin needs is reachable,
|
|
122
|
+
* but only to a process speaking a protocol the plugin does not. Spotify is
|
|
123
|
+
* the reason on both counts. Its tracks come off the CDN encrypted, so there
|
|
124
|
+
* is no URL for `MusicProviderStream.resolveStreamUrl` to mint and a separate
|
|
125
|
+
* binary beside Liquidsoap fetches them instead; and since February 2026 its
|
|
126
|
+
* Web API hands a playlist's items only to the account that owns them, while
|
|
127
|
+
* that same binary — holding the streaming client's own session rather than a
|
|
128
|
+
* developer app's — can still read the ones the account merely follows. Named
|
|
129
|
+
* in backticks rather than linked: importing that type solely to make a doc
|
|
130
|
+
* reference clickable leaves an import nothing uses, which is a lint failure
|
|
131
|
+
* in a package whose gate is zero warnings.
|
|
115
132
|
*
|
|
116
|
-
* Do NOT reach for this
|
|
117
|
-
* the URL yourself and keep your credentials to
|
|
118
|
-
* and narrower. Requires the `trackFetcher`
|
|
133
|
+
* Do NOT reach for this for anything you can ask your provider for directly.
|
|
134
|
+
* Mint the URL yourself, read your own API, and keep your credentials to
|
|
135
|
+
* yourself, which is both simpler and narrower. Requires the `trackFetcher`
|
|
136
|
+
* permission.
|
|
119
137
|
*/
|
|
120
138
|
export interface PluginTrackFetcher {
|
|
121
139
|
/**
|
|
@@ -127,6 +145,25 @@ export interface PluginTrackFetcher {
|
|
|
127
145
|
* stream side up is a normal state, not a fault.
|
|
128
146
|
*/
|
|
129
147
|
serve(request: TrackFetchRequest): Promise<ProviderStream | undefined>;
|
|
148
|
+
/**
|
|
149
|
+
* Read a page of a playlist your own API will not hand over.
|
|
150
|
+
*
|
|
151
|
+
* For the playlist your provider lists but refuses the contents of. Ask
|
|
152
|
+
* your API first and reach for this only when it says no: this is a second
|
|
153
|
+
* protocol, on a session the station also airs from, and it is the
|
|
154
|
+
* expensive way to learn what you could have been told.
|
|
155
|
+
*
|
|
156
|
+
* The two failures are deliberately different. `undefined` is "this
|
|
157
|
+
* station has no fetcher", exactly as {@link PluginTrackFetcher.serve}
|
|
158
|
+
* means it, and a plugin should answer whatever it would have answered
|
|
159
|
+
* before this method existed. A THROW is the fetcher having tried and
|
|
160
|
+
* failed, which is a provider failure like any other.
|
|
161
|
+
*
|
|
162
|
+
* Tracks come back in the playlist's own order, and nothing is left out of
|
|
163
|
+
* a page: a page shorter than the one asked for means the end of the
|
|
164
|
+
* playlist, so a caller can walk one the same way it walks its own API's.
|
|
165
|
+
*/
|
|
166
|
+
playlistTracks(request: PlaylistTracksRequest): Promise<ProviderTrack[] | undefined>;
|
|
130
167
|
}
|
|
131
168
|
/**
|
|
132
169
|
* The single object handed to a plugin at init, and the sanctioned way to get
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.host.d.ts","sourceRoot":"","sources":["../src/plugin.host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.host.d.ts","sourceRoot":"","sources":["../src/plugin.host.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HostFetchInit, HostFetchMethod, PluginHost, ProviderStream } from '../index.js';
|
|
1
|
+
import type { HostFetchInit, HostFetchMethod, PluginHost, 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
|
|
@@ -55,6 +55,12 @@ export interface FakePluginHost extends PluginHost {
|
|
|
55
55
|
getVaultTokens(): Record<string, string> | undefined;
|
|
56
56
|
/** Set what `host.trackFetcher.serve()` answers with; `undefined` means "this station has no fetcher". */
|
|
57
57
|
seedFetchedTrack(stream: ProviderStream | undefined): void;
|
|
58
|
+
/**
|
|
59
|
+
* Set what `host.trackFetcher.playlistTracks()` answers with. `undefined` means "this station
|
|
60
|
+
* has no fetcher"; pass an `Error` to make it throw, which is the fetcher having tried and
|
|
61
|
+
* failed and is a different case for a plugin to handle.
|
|
62
|
+
*/
|
|
63
|
+
seedFetchedPlaylist(tracks: ProviderTrack[] | Error | undefined): void;
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
60
66
|
* What a test says a scripted reply should be.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake.plugin.host.d.ts","sourceRoot":"","sources":["../../src/testing/fake.plugin.host.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"fake.plugin.host.d.ts","sourceRoot":"","sources":["../../src/testing/fake.plugin.host.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7G;;;;;;;;;;;;;;GAcG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC5C,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAe,SAAQ,UAAU;IAC9C,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,iBAAiB,EAAE,CAAC;IACpC,0EAA0E;IAC1E,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mEAAmE;IACnE,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAChD,yEAAyE;IACzE,YAAY,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IACnF,sDAAsD;IACtD,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAClD,wDAAwD;IACxD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,mEAAmE;IACnE,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/C,mEAAmE;IACnE,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC,kDAAkD;IAClD,WAAW,IAAI,MAAM,EAAE,CAAC;IACxB,wEAAwE;IACxE,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACjD,uEAAuE;IACvE,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACrD,0GAA0G;IAC1G,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAAC;IAC3D;;;;OAIG;IACH,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,KAAK,GAAG,SAAS,GAAG,IAAI,CAAC;CAC1E;AASD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD,mEAAmE;AACnE,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,gBAAqB,GAAG,QAAQ,CAa3E;AAED,wBAAgB,oBAAoB,IAAI,cAAc,CAiGrD"}
|
package/dist/testing/index.js
CHANGED
|
@@ -39,6 +39,7 @@ function createFakePluginHost() {
|
|
|
39
39
|
url: "http://127.0.0.1:3679/track/song-1?t=signed",
|
|
40
40
|
expiresAt: 1893456e6
|
|
41
41
|
};
|
|
42
|
+
let fetchedPlaylist;
|
|
42
43
|
const fetchImpl = vi.fn(async (url, init) => {
|
|
43
44
|
calls.push({
|
|
44
45
|
url,
|
|
@@ -92,7 +93,11 @@ function createFakePluginHost() {
|
|
|
92
93
|
emit: vi.fn()
|
|
93
94
|
},
|
|
94
95
|
trackFetcher: {
|
|
95
|
-
serve: vi.fn(async () => fetchedTrack)
|
|
96
|
+
serve: vi.fn(async () => fetchedTrack),
|
|
97
|
+
playlistTracks: vi.fn(async () => {
|
|
98
|
+
if (fetchedPlaylist instanceof Error) throw fetchedPlaylist;
|
|
99
|
+
return fetchedPlaylist;
|
|
100
|
+
})
|
|
96
101
|
},
|
|
97
102
|
calls,
|
|
98
103
|
seedRemainingMs(ms) {
|
|
@@ -129,6 +134,9 @@ function createFakePluginHost() {
|
|
|
129
134
|
},
|
|
130
135
|
seedFetchedTrack(stream) {
|
|
131
136
|
fetchedTrack = stream;
|
|
137
|
+
},
|
|
138
|
+
seedFetchedPlaylist(tracks) {
|
|
139
|
+
fetchedPlaylist = tracks;
|
|
132
140
|
}
|
|
133
141
|
};
|
|
134
142
|
return host;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/fake.plugin.host.ts"],"sourcesContent":["import { vi } from 'vitest';\n\nimport type { HostFetchInit, HostFetchMethod, PluginHost, ProviderStream } 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 * 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\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\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\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 // 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: { serve: vi.fn(async () => fetchedTrack) },\n calls,\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 };\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","createFakePluginHost","calls","queue","customImpl","storageData","Map","configData","secretsData","tokens","remainingMs","fetchedTrack","expiresAt","fetchImpl","vi","fn","push","method","next","shift","Error","host","logger","debug","info","warn","error","fetch","signal","AbortController","storage","get","key","set","delete","list","prefix","keys","filter","startsWith","secrets","config","oauth","getRedirectUri","saveTokens","getTokens","events","emit","trackFetcher","serve","seedRemainingMs","ms","queueResponse","setFetchImpl","impl","seedConfig","seedSecret","seedStorage","getStorageEntry","storageKeys","seedTokens","getVaultTokens","seedFetchedTrack","stream"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/fake.plugin.host.ts"],"sourcesContent":["import { vi } from 'vitest';\n\nimport type { HostFetchInit, HostFetchMethod, PluginHost, 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 * 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}\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\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\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 // 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 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","createFakePluginHost","calls","queue","customImpl","storageData","Map","configData","secretsData","tokens","remainingMs","fetchedTrack","expiresAt","fetchedPlaylist","fetchImpl","vi","fn","push","method","next","shift","Error","host","logger","debug","info","warn","error","fetch","signal","AbortController","storage","get","key","set","delete","list","prefix","keys","filter","startsWith","secrets","config","oauth","getRedirectUri","saveTokens","getTokens","events","emit","trackFetcher","serve","playlistTracks","seedRemainingMs","ms","queueResponse","setFetchImpl","impl","seedConfig","seedSecret","seedStorage","getStorageEntry","storageKeys","seedTokens","getVaultTokens","seedFetchedTrack","stream","seedFetchedPlaylist","tracks"]}
|
package/package.json
CHANGED