@open-kingdom/shared-backend-integration-test-doubles 0.0.2-19 → 0.0.2-21

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.
Files changed (81) hide show
  1. package/README.md +39 -0
  2. package/dist/apple-music/apple-music-error-mode.d.ts +17 -0
  3. package/dist/apple-music/apple-music-error-mode.d.ts.map +1 -0
  4. package/dist/apple-music/apple-music-error-mode.js +61 -0
  5. package/dist/apple-music/apple-music-error-mode.js.map +1 -0
  6. package/dist/apple-music/apple-music-twin.config.d.ts +9 -0
  7. package/dist/apple-music/apple-music-twin.config.d.ts.map +1 -0
  8. package/dist/apple-music/apple-music-twin.config.js +25 -0
  9. package/dist/apple-music/apple-music-twin.config.js.map +1 -0
  10. package/dist/apple-music/apple-music-twin.d.ts +17 -0
  11. package/dist/apple-music/apple-music-twin.d.ts.map +1 -0
  12. package/dist/apple-music/apple-music-twin.js +325 -0
  13. package/dist/apple-music/apple-music-twin.js.map +1 -0
  14. package/dist/apple-music/catalog-fixtures.d.ts +19 -0
  15. package/dist/apple-music/catalog-fixtures.d.ts.map +1 -0
  16. package/dist/apple-music/catalog-fixtures.js +94 -0
  17. package/dist/apple-music/catalog-fixtures.js.map +1 -0
  18. package/dist/apple-music/catalog-response.d.ts +59 -0
  19. package/dist/apple-music/catalog-response.d.ts.map +1 -0
  20. package/dist/apple-music/catalog-response.js +63 -0
  21. package/dist/apple-music/catalog-response.js.map +1 -0
  22. package/dist/apple-music/index.d.ts +8 -0
  23. package/dist/apple-music/index.d.ts.map +1 -0
  24. package/dist/apple-music/index.js +6 -0
  25. package/dist/apple-music/index.js.map +1 -0
  26. package/dist/apple-music/msw-handlers.d.ts +12 -0
  27. package/dist/apple-music/msw-handlers.d.ts.map +1 -0
  28. package/dist/apple-music/msw-handlers.js +18 -0
  29. package/dist/apple-music/msw-handlers.js.map +1 -0
  30. package/dist/apple-music/player-shim.d.ts +3 -0
  31. package/dist/apple-music/player-shim.d.ts.map +1 -0
  32. package/dist/apple-music/player-shim.js +310 -0
  33. package/dist/apple-music/player-shim.js.map +1 -0
  34. package/dist/artwork/playlist-001.svg +13 -0
  35. package/dist/artwork/playlist-002.svg +13 -0
  36. package/dist/artwork/track-001.svg +12 -0
  37. package/dist/artwork/track-002.svg +12 -0
  38. package/dist/artwork/track-003.svg +13 -0
  39. package/dist/artwork/track-004.svg +13 -0
  40. package/dist/artwork/track-005.svg +13 -0
  41. package/dist/artwork/track-006.svg +13 -0
  42. package/dist/artwork/track-007.svg +13 -0
  43. package/dist/audio/mock-1.mp3 +0 -0
  44. package/dist/audio/mock-2.mp3 +0 -0
  45. package/dist/audio/mock-3.mp3 +0 -0
  46. package/dist/gcs/gcs-error-mode.js +1 -1
  47. package/dist/gcs/gcs-error-mode.js.map +1 -1
  48. package/dist/gcs/gcs-error-simulation.interceptor.js.map +1 -1
  49. package/dist/gcs/gcs-twin.config.js.map +1 -1
  50. package/dist/gcs/gcs-twin.js +1 -1
  51. package/dist/gcs/gcs-twin.js.map +1 -1
  52. package/dist/gcs/index.js.map +1 -1
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +2 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/interceptor/http-helper.js.map +1 -1
  58. package/dist/interceptor/index.js.map +1 -1
  59. package/dist/interceptor/node-interceptor.js.map +1 -1
  60. package/dist/interceptor/routing-table.config.d.ts.map +1 -1
  61. package/dist/interceptor/routing-table.config.js +10 -0
  62. package/dist/interceptor/routing-table.config.js.map +1 -1
  63. package/dist/interceptor/routing-table.js +1 -1
  64. package/dist/interceptor/routing-table.js.map +1 -1
  65. package/dist/lib/integration-test-doubles.js.map +1 -1
  66. package/dist/shared/activation.js.map +1 -1
  67. package/dist/shared/config.js.map +1 -1
  68. package/dist/shared/constants.d.ts +4 -0
  69. package/dist/shared/constants.d.ts.map +1 -1
  70. package/dist/shared/constants.js +4 -2
  71. package/dist/shared/constants.js.map +1 -1
  72. package/dist/youtube/index.js.map +1 -1
  73. package/dist/youtube/msw-handlers.js.map +1 -1
  74. package/dist/youtube/player-shim.js.map +1 -1
  75. package/dist/youtube/search-fixtures.js.map +1 -1
  76. package/dist/youtube/search-response.js.map +1 -1
  77. package/dist/youtube/youtube-error-mode.js.map +1 -1
  78. package/dist/youtube/youtube-twin.config.js.map +1 -1
  79. package/dist/youtube/youtube-twin.js +5 -5
  80. package/dist/youtube/youtube-twin.js.map +1 -1
  81. package/package.json +23 -3
package/README.md CHANGED
@@ -163,6 +163,45 @@ const handlers = configs.map((config) =>
163
163
  );
164
164
  ```
165
165
 
166
+ ### AppleMusicTwin
167
+
168
+ #### AppleMusicTwin Constructor
169
+
170
+ ```typescript
171
+ constructor(overrides?: Partial<AppleMusicTwinConfig>)
172
+ ```
173
+
174
+ - `overrides.port`: Port to run the twin on (default: 9019, or from `APPLE_MUSIC_TWIN_PORT` env var; must be within 9010-9020)
175
+ - `overrides.externalUrl`: External URL for the twin (default: `http://localhost:{port}`)
176
+
177
+ #### AppleMusicTwin Methods
178
+
179
+ - `start()`: Start the Express server
180
+ - `stop()`: Stop the Express server
181
+ - `reset()`: Reset catalog fixtures, auth state, and clear any active error mode
182
+ - `getEmulatorHost()`: Get the twin URL (e.g., `http://localhost:9019`)
183
+ - `isHealthy()`: Check if the twin is running and responding
184
+
185
+ #### Browser-Side MSW Interception
186
+
187
+ Use `getAppleMusicMswHandlerConfigs(twinBaseUrl)` to export mock route definitions for browser-side testing frameworks like MSW:
188
+
189
+ ```typescript
190
+ import { http, HttpResponse } from 'msw';
191
+ import { getAppleMusicMswHandlerConfigs } from '@open-kingdom/shared-backend-integration-test-doubles';
192
+
193
+ const configs = getAppleMusicMswHandlerConfigs('http://localhost:9019');
194
+ const handlers = configs.map((config) =>
195
+ http.get(config.url, async () => {
196
+ const res = await fetch(config.proxyTo);
197
+ const body = await res.text();
198
+ return new HttpResponse(body, {
199
+ headers: { 'Content-Type': config.contentType },
200
+ });
201
+ })
202
+ );
203
+ ```
204
+
166
205
  ### Exports
167
206
 
168
207
  ```typescript
@@ -0,0 +1,17 @@
1
+ export type AppleMusicErrorModeType = 'unauthorized' | 'expired-token' | 'rate-limited' | 'empty-results' | 'not-found';
2
+ export interface AppleMusicErrorModeConfig {
3
+ type: AppleMusicErrorModeType;
4
+ }
5
+ export interface AppleMusicErrorSimulationResult {
6
+ status: number;
7
+ body: Record<string, unknown>;
8
+ }
9
+ export declare class AppleMusicErrorModeManager {
10
+ private activeMode;
11
+ setMode(config: AppleMusicErrorModeConfig): void;
12
+ clearMode(): void;
13
+ getMode(): AppleMusicErrorModeConfig | null;
14
+ matchRequest(path: string): AppleMusicErrorSimulationResult | null;
15
+ reset(): void;
16
+ }
17
+ //# sourceMappingURL=apple-music-error-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apple-music-error-mode.d.ts","sourceRoot":"","sources":["../../src/apple-music/apple-music-error-mode.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,uBAAuB,GAC/B,cAAc,GACd,eAAe,GACf,cAAc,GACd,eAAe,GACf,WAAW,CAAC;AAEhB,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,uBAAuB,CAAC;CAC/B;AAED,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAwBD,qBAAa,0BAA0B;IACrC,OAAO,CAAC,UAAU,CAA0C;IAE5D,OAAO,CAAC,MAAM,EAAE,yBAAyB,GAAG,IAAI;IAIhD,SAAS,IAAI,IAAI;IAIjB,OAAO,IAAI,yBAAyB,GAAG,IAAI;IAI3C,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,+BAA+B,GAAG,IAAI;IA4ClE,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,61 @@
1
+ function appleMusicError(status, title, detail, code = status.toString()) {
2
+ return {
3
+ status,
4
+ body: {
5
+ errors: [
6
+ {
7
+ id: `mock-error-${Date.now()}`,
8
+ status: status.toString(),
9
+ code,
10
+ title,
11
+ detail
12
+ }
13
+ ]
14
+ }
15
+ };
16
+ }
17
+ export class AppleMusicErrorModeManager {
18
+ setMode(config) {
19
+ this.activeMode = config;
20
+ }
21
+ clearMode() {
22
+ this.activeMode = null;
23
+ }
24
+ getMode() {
25
+ return this.activeMode;
26
+ }
27
+ matchRequest(path) {
28
+ if (!this.activeMode) return null;
29
+ if (path.startsWith('/test/')) return null;
30
+ switch(this.activeMode.type){
31
+ case 'unauthorized':
32
+ return appleMusicError(401, 'Unauthorized', 'The developer token is missing or invalid.');
33
+ case 'expired-token':
34
+ return appleMusicError(403, 'Forbidden', 'The user token has expired or is invalid.');
35
+ case 'rate-limited':
36
+ return appleMusicError(429, 'Too Many Requests', 'The request was rate limited.');
37
+ case 'empty-results':
38
+ if (path.includes('/search')) {
39
+ return {
40
+ status: 200,
41
+ body: {
42
+ results: {}
43
+ }
44
+ };
45
+ }
46
+ return null;
47
+ case 'not-found':
48
+ return appleMusicError(404, 'Not Found', 'The requested resource was not found.');
49
+ default:
50
+ return null;
51
+ }
52
+ }
53
+ reset() {
54
+ this.activeMode = null;
55
+ }
56
+ constructor(){
57
+ this.activeMode = null;
58
+ }
59
+ }
60
+
61
+ //# sourceMappingURL=apple-music-error-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/apple-music/apple-music-error-mode.ts"],"sourcesContent":["export type AppleMusicErrorModeType =\n | 'unauthorized'\n | 'expired-token'\n | 'rate-limited'\n | 'empty-results'\n | 'not-found';\n\nexport interface AppleMusicErrorModeConfig {\n type: AppleMusicErrorModeType;\n}\n\nexport interface AppleMusicErrorSimulationResult {\n status: number;\n body: Record<string, unknown>;\n}\n\nfunction appleMusicError(\n status: number,\n title: string,\n detail: string,\n code = status.toString()\n): AppleMusicErrorSimulationResult {\n return {\n status,\n body: {\n errors: [\n {\n id: `mock-error-${Date.now()}`,\n status: status.toString(),\n code,\n title,\n detail,\n },\n ],\n },\n };\n}\n\nexport class AppleMusicErrorModeManager {\n private activeMode: AppleMusicErrorModeConfig | null = null;\n\n setMode(config: AppleMusicErrorModeConfig): void {\n this.activeMode = config;\n }\n\n clearMode(): void {\n this.activeMode = null;\n }\n\n getMode(): AppleMusicErrorModeConfig | null {\n return this.activeMode;\n }\n\n matchRequest(path: string): AppleMusicErrorSimulationResult | null {\n if (!this.activeMode) return null;\n if (path.startsWith('/test/')) return null;\n\n switch (this.activeMode.type) {\n case 'unauthorized':\n return appleMusicError(\n 401,\n 'Unauthorized',\n 'The developer token is missing or invalid.'\n );\n case 'expired-token':\n return appleMusicError(\n 403,\n 'Forbidden',\n 'The user token has expired or is invalid.'\n );\n case 'rate-limited':\n return appleMusicError(\n 429,\n 'Too Many Requests',\n 'The request was rate limited.'\n );\n case 'empty-results':\n if (path.includes('/search')) {\n return {\n status: 200,\n body: {\n results: {},\n },\n };\n }\n return null;\n case 'not-found':\n return appleMusicError(\n 404,\n 'Not Found',\n 'The requested resource was not found.'\n );\n default:\n return null;\n }\n }\n\n reset(): void {\n this.activeMode = null;\n }\n}\n"],"names":["appleMusicError","status","title","detail","code","toString","body","errors","id","Date","now","AppleMusicErrorModeManager","setMode","config","activeMode","clearMode","getMode","matchRequest","path","startsWith","type","includes","results","reset"],"mappings":"AAgBA,SAASA,gBACPC,MAAc,EACdC,KAAa,EACbC,MAAc,EACdC,OAAOH,OAAOI,QAAQ,EAAE;IAExB,OAAO;QACLJ;QACAK,MAAM;YACJC,QAAQ;gBACN;oBACEC,IAAI,CAAC,WAAW,EAAEC,KAAKC,GAAG,IAAI;oBAC9BT,QAAQA,OAAOI,QAAQ;oBACvBD;oBACAF;oBACAC;gBACF;aACD;QACH;IACF;AACF;AAEA,OAAO,MAAMQ;IAGXC,QAAQC,MAAiC,EAAQ;QAC/C,IAAI,CAACC,UAAU,GAAGD;IACpB;IAEAE,YAAkB;QAChB,IAAI,CAACD,UAAU,GAAG;IACpB;IAEAE,UAA4C;QAC1C,OAAO,IAAI,CAACF,UAAU;IACxB;IAEAG,aAAaC,IAAY,EAA0C;QACjE,IAAI,CAAC,IAAI,CAACJ,UAAU,EAAE,OAAO;QAC7B,IAAII,KAAKC,UAAU,CAAC,WAAW,OAAO;QAEtC,OAAQ,IAAI,CAACL,UAAU,CAACM,IAAI;YAC1B,KAAK;gBACH,OAAOpB,gBACL,KACA,gBACA;YAEJ,KAAK;gBACH,OAAOA,gBACL,KACA,aACA;YAEJ,KAAK;gBACH,OAAOA,gBACL,KACA,qBACA;YAEJ,KAAK;gBACH,IAAIkB,KAAKG,QAAQ,CAAC,YAAY;oBAC5B,OAAO;wBACLpB,QAAQ;wBACRK,MAAM;4BACJgB,SAAS,CAAC;wBACZ;oBACF;gBACF;gBACA,OAAO;YACT,KAAK;gBACH,OAAOtB,gBACL,KACA,aACA;YAEJ;gBACE,OAAO;QACX;IACF;IAEAuB,QAAc;QACZ,IAAI,CAACT,UAAU,GAAG;IACpB;;aA5DQA,aAA+C;;AA6DzD"}
@@ -0,0 +1,9 @@
1
+ export interface AppleMusicTwinConfig {
2
+ /** Port the Apple Music twin Express server listens on */
3
+ port: number;
4
+ /** External URL consumers use to reach the twin */
5
+ externalUrl: string;
6
+ }
7
+ export declare const defaultAppleMusicConfig: AppleMusicTwinConfig;
8
+ export declare function createAppleMusicConfig(overrides?: Partial<AppleMusicTwinConfig>): AppleMusicTwinConfig;
9
+ //# sourceMappingURL=apple-music-twin.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apple-music-twin.config.d.ts","sourceRoot":"","sources":["../../src/apple-music/apple-music-twin.config.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,uBAAuB,EAAE,oBAGrC,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,oBAAoB,CA8BtB"}
@@ -0,0 +1,25 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { ConfigService, nodeEnvAdapter } from '@open-kingdom/shared-poly-util-env-config';
3
+ import { DEFAULT_PORTS, ENV_VARS, PORT_RANGE } from '../shared/constants.js';
4
+ export const defaultAppleMusicConfig = {
5
+ port: DEFAULT_PORTS.APPLE_MUSIC,
6
+ externalUrl: `http://localhost:${DEFAULT_PORTS.APPLE_MUSIC}`
7
+ };
8
+ export function createAppleMusicConfig(overrides = {}) {
9
+ const env = new ConfigService(nodeEnvAdapter);
10
+ const envPortVal = env.get(ENV_VARS.APPLE_MUSIC_TWIN_PORT);
11
+ const envPort = envPortVal ? Number.parseInt(envPortVal, 10) : undefined;
12
+ const baseConfig = _extends({}, defaultAppleMusicConfig, envPort !== undefined && !Number.isNaN(envPort) && {
13
+ port: envPort
14
+ });
15
+ const finalConfig = _extends({}, baseConfig, overrides);
16
+ if (finalConfig.port < PORT_RANGE.min || finalConfig.port > PORT_RANGE.max) {
17
+ throw new Error(`Apple Music twin port ${finalConfig.port} is outside the reserved range ` + `${PORT_RANGE.min}–${PORT_RANGE.max}.`);
18
+ }
19
+ if (!overrides.externalUrl && finalConfig.externalUrl === defaultAppleMusicConfig.externalUrl) {
20
+ finalConfig.externalUrl = `http://localhost:${finalConfig.port}`;
21
+ }
22
+ return finalConfig;
23
+ }
24
+
25
+ //# sourceMappingURL=apple-music-twin.config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/apple-music/apple-music-twin.config.ts"],"sourcesContent":["import {\n ConfigService,\n nodeEnvAdapter,\n} from '@open-kingdom/shared-poly-util-env-config';\nimport { DEFAULT_PORTS, ENV_VARS, PORT_RANGE } from '../shared/constants.js';\n\nexport interface AppleMusicTwinConfig {\n /** Port the Apple Music twin Express server listens on */\n port: number;\n /** External URL consumers use to reach the twin */\n externalUrl: string;\n}\n\nexport const defaultAppleMusicConfig: AppleMusicTwinConfig = {\n port: DEFAULT_PORTS.APPLE_MUSIC,\n externalUrl: `http://localhost:${DEFAULT_PORTS.APPLE_MUSIC}`,\n};\n\nexport function createAppleMusicConfig(\n overrides: Partial<AppleMusicTwinConfig> = {}\n): AppleMusicTwinConfig {\n const env = new ConfigService(nodeEnvAdapter);\n const envPortVal = env.get(ENV_VARS.APPLE_MUSIC_TWIN_PORT);\n const envPort = envPortVal ? Number.parseInt(envPortVal, 10) : undefined;\n\n const baseConfig: AppleMusicTwinConfig = {\n ...defaultAppleMusicConfig,\n ...(envPort !== undefined && !Number.isNaN(envPort) && { port: envPort }),\n };\n\n const finalConfig: AppleMusicTwinConfig = {\n ...baseConfig,\n ...overrides,\n };\n\n if (finalConfig.port < PORT_RANGE.min || finalConfig.port > PORT_RANGE.max) {\n throw new Error(\n `Apple Music twin port ${finalConfig.port} is outside the reserved range ` +\n `${PORT_RANGE.min}–${PORT_RANGE.max}.`\n );\n }\n\n if (\n !overrides.externalUrl &&\n finalConfig.externalUrl === defaultAppleMusicConfig.externalUrl\n ) {\n finalConfig.externalUrl = `http://localhost:${finalConfig.port}`;\n }\n\n return finalConfig;\n}\n"],"names":["ConfigService","nodeEnvAdapter","DEFAULT_PORTS","ENV_VARS","PORT_RANGE","defaultAppleMusicConfig","port","APPLE_MUSIC","externalUrl","createAppleMusicConfig","overrides","env","envPortVal","get","APPLE_MUSIC_TWIN_PORT","envPort","Number","parseInt","undefined","baseConfig","isNaN","finalConfig","min","max","Error"],"mappings":";AAAA,SACEA,aAAa,EACbC,cAAc,QACT,4CAA4C;AACnD,SAASC,aAAa,EAAEC,QAAQ,EAAEC,UAAU,QAAQ,yBAAyB;AAS7E,OAAO,MAAMC,0BAAgD;IAC3DC,MAAMJ,cAAcK,WAAW;IAC/BC,aAAa,CAAC,iBAAiB,EAAEN,cAAcK,WAAW,EAAE;AAC9D,EAAE;AAEF,OAAO,SAASE,uBACdC,YAA2C,CAAC,CAAC;IAE7C,MAAMC,MAAM,IAAIX,cAAcC;IAC9B,MAAMW,aAAaD,IAAIE,GAAG,CAACV,SAASW,qBAAqB;IACzD,MAAMC,UAAUH,aAAaI,OAAOC,QAAQ,CAACL,YAAY,MAAMM;IAE/D,MAAMC,aAAmC,aACpCd,yBACCU,YAAYG,aAAa,CAACF,OAAOI,KAAK,CAACL,YAAY;QAAET,MAAMS;IAAQ;IAGzE,MAAMM,cAAoC,aACrCF,YACAT;IAGL,IAAIW,YAAYf,IAAI,GAAGF,WAAWkB,GAAG,IAAID,YAAYf,IAAI,GAAGF,WAAWmB,GAAG,EAAE;QAC1E,MAAM,IAAIC,MACR,CAAC,sBAAsB,EAAEH,YAAYf,IAAI,CAAC,+BAA+B,CAAC,GACxE,GAAGF,WAAWkB,GAAG,CAAC,CAAC,EAAElB,WAAWmB,GAAG,CAAC,CAAC,CAAC;IAE5C;IAEA,IACE,CAACb,UAAUF,WAAW,IACtBa,YAAYb,WAAW,KAAKH,wBAAwBG,WAAW,EAC/D;QACAa,YAAYb,WAAW,GAAG,CAAC,iBAAiB,EAAEa,YAAYf,IAAI,EAAE;IAClE;IAEA,OAAOe;AACT"}
@@ -0,0 +1,17 @@
1
+ import { AppleMusicTwinConfig } from './apple-music-twin.config.js';
2
+ export declare class AppleMusicTwin {
3
+ private readonly config;
4
+ private server;
5
+ private currentTracks;
6
+ private currentPlaylists;
7
+ private readonly errorModeManager;
8
+ private deviceSession;
9
+ private currentPlayback;
10
+ constructor(overrides?: Partial<AppleMusicTwinConfig>);
11
+ start(): Promise<void>;
12
+ stop(): Promise<void>;
13
+ reset(): Promise<void>;
14
+ isHealthy(): Promise<boolean>;
15
+ getEmulatorHost(): string;
16
+ }
17
+ //# sourceMappingURL=apple-music-twin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apple-music-twin.d.ts","sourceRoot":"","sources":["../../src/apple-music/apple-music-twin.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,oBAAoB,EACrB,MAAM,8BAA8B,CAAC;AAetC,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,gBAAgB,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoC;IAGrE,OAAO,CAAC,aAAa,CAMnB;IAEF,OAAO,CAAC,eAAe,CAAiB;gBAE5B,SAAS,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC;IAM/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoStB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAarB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IActB,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAenC,eAAe,IAAI,MAAM;CAG1B"}
@@ -0,0 +1,325 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import express from 'express';
5
+ import cors from 'cors';
6
+ import { createAppleMusicConfig } from './apple-music-twin.config.js';
7
+ import { trackFixtures, playlistFixtures } from './catalog-fixtures.js';
8
+ import { formatSearchResponse, formatTrackResource, formatPlaylistResource } from './catalog-response.js';
9
+ import { generatePlayerShim } from './player-shim.js';
10
+ import { AppleMusicErrorModeManager } from './apple-music-error-mode.js';
11
+ export class AppleMusicTwin {
12
+ async start() {
13
+ if (this.server) return;
14
+ const app = express();
15
+ app.use(cors());
16
+ app.use(express.json());
17
+ // Middleware to apply simulated errors
18
+ app.use((req, res, next)=>{
19
+ const errorResult = this.errorModeManager.matchRequest(req.path);
20
+ if (errorResult) {
21
+ res.status(errorResult.status).json(errorResult.body);
22
+ return;
23
+ }
24
+ next();
25
+ });
26
+ // Browser-side script shim
27
+ const shimJs = generatePlayerShim(this.config.externalUrl);
28
+ app.get('/musickit.js', (_req, res)=>{
29
+ res.type('application/javascript').send(shimJs);
30
+ });
31
+ // Audio static files
32
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
33
+ let audioPath = path.join(__dirname, 'audio');
34
+ if (!fs.existsSync(audioPath)) {
35
+ audioPath = path.join(__dirname, '../audio');
36
+ }
37
+ app.use('/v1/audio', express.static(audioPath, {
38
+ setHeaders: (res)=>{
39
+ res.setHeader('Access-Control-Allow-Origin', '*');
40
+ res.setHeader('Accept-Ranges', 'bytes');
41
+ }
42
+ }));
43
+ // Artwork static files. Catalog URLs carry Apple-style {w}x{h} placeholders
44
+ // (e.g. /v1/artwork/300x300/track-001.svg) that clients substitute with the
45
+ // size they need; the :size segment is cosmetic, files live flat on disk.
46
+ let artworkPath = path.join(__dirname, 'artwork');
47
+ if (!fs.existsSync(artworkPath)) {
48
+ artworkPath = path.join(__dirname, '../artwork');
49
+ }
50
+ app.get('/v1/artwork/:size/:file', (req, res)=>{
51
+ const file = path.basename(req.params.file);
52
+ res.setHeader('Access-Control-Allow-Origin', '*');
53
+ res.sendFile(path.join(artworkPath, file), (err)=>{
54
+ if (err && !res.headersSent) {
55
+ res.status(404).json({
56
+ errors: [
57
+ {
58
+ title: 'Resource Not Found',
59
+ detail: `Artwork ${file} not found.`
60
+ }
61
+ ]
62
+ });
63
+ }
64
+ });
65
+ });
66
+ // --- Apple Music Catalog API ---
67
+ // Search — empty term returns all tracks/playlists
68
+ app.get('/v1/catalog/:storefront/search', (req, res)=>{
69
+ const { term = '', types = 'songs' } = req.query;
70
+ const requestedTypes = types.split(',');
71
+ const matchesTerm = (val)=>term.trim() === '' || val.toLowerCase().includes(term.toLowerCase());
72
+ const matchedTracks = requestedTypes.includes('songs') ? this.currentTracks.filter((t)=>matchesTerm(t.name) || matchesTerm(t.artistName)) : [];
73
+ const matchedPlaylists = requestedTypes.includes('playlists') ? this.currentPlaylists.filter((p)=>matchesTerm(p.name)) : [];
74
+ const response = formatSearchResponse(matchedTracks, matchedPlaylists, this.currentTracks, this.config.externalUrl);
75
+ res.json(response);
76
+ });
77
+ // Get Single Song
78
+ app.get('/v1/catalog/:storefront/songs/:id', (req, res)=>{
79
+ const { id } = req.params;
80
+ const track = this.currentTracks.find((t)=>t.id === id);
81
+ if (!track) {
82
+ res.status(404).json({
83
+ errors: [
84
+ {
85
+ title: 'Resource Not Found',
86
+ detail: `Song with ID ${id} not found.`
87
+ }
88
+ ]
89
+ });
90
+ return;
91
+ }
92
+ res.json({
93
+ data: [
94
+ formatTrackResource(track, this.config.externalUrl)
95
+ ]
96
+ });
97
+ });
98
+ // Get Single Playlist
99
+ app.get('/v1/catalog/:storefront/playlists/:id', (req, res)=>{
100
+ const { id } = req.params;
101
+ const playlist = this.currentPlaylists.find((p)=>p.id === id);
102
+ if (!playlist) {
103
+ res.status(404).json({
104
+ errors: [
105
+ {
106
+ title: 'Resource Not Found',
107
+ detail: `Playlist with ID ${id} not found.`
108
+ }
109
+ ]
110
+ });
111
+ return;
112
+ }
113
+ res.json({
114
+ data: [
115
+ formatPlaylistResource(playlist, this.currentTracks, this.config.externalUrl)
116
+ ]
117
+ });
118
+ });
119
+ // Get Playlist Tracks
120
+ app.get('/v1/catalog/:storefront/playlists/:id/tracks', (req, res)=>{
121
+ const { id } = req.params;
122
+ const playlist = this.currentPlaylists.find((p)=>p.id === id);
123
+ if (!playlist) {
124
+ res.status(404).json({
125
+ errors: [
126
+ {
127
+ title: 'Resource Not Found',
128
+ detail: `Playlist with ID ${id} not found.`
129
+ }
130
+ ]
131
+ });
132
+ return;
133
+ }
134
+ const playlistTracks = playlist.trackIds.map((tid)=>this.currentTracks.find((t)=>t.id === tid)).filter((t)=>!!t);
135
+ res.json({
136
+ data: playlistTracks.map((t)=>formatTrackResource(t, this.config.externalUrl))
137
+ });
138
+ });
139
+ // --- Custom Auth / Playback endpoints ---
140
+ // Developer Token
141
+ app.get('/v1/developer-token', (_req, res)=>{
142
+ res.json({
143
+ developerToken: this.deviceSession.developerToken
144
+ });
145
+ });
146
+ // Device Session
147
+ app.get('/v1/device-session', (_req, res)=>{
148
+ res.json(this.deviceSession);
149
+ });
150
+ // Connect User Apple Music
151
+ app.post('/v1/connect', (req, res)=>{
152
+ const { musicUserToken, storefront, mutExpires } = req.body || {};
153
+ if (!musicUserToken) {
154
+ res.status(400).json({
155
+ error: 'Missing musicUserToken'
156
+ });
157
+ return;
158
+ }
159
+ this.deviceSession.musicUserToken = musicUserToken;
160
+ this.deviceSession.storefront = storefront || 'us';
161
+ this.deviceSession.mutExpires = mutExpires || '2027-01-01T00:00:00Z';
162
+ this.deviceSession.authStatus = 'authorized';
163
+ res.json({
164
+ success: true
165
+ });
166
+ });
167
+ // Auth Status GET
168
+ app.get('/v1/auth-status', (_req, res)=>{
169
+ res.json({
170
+ status: this.deviceSession.authStatus
171
+ });
172
+ });
173
+ // Auth Status POST
174
+ app.post('/v1/auth-status', (req, res)=>{
175
+ const { status } = req.body || {};
176
+ if (status) {
177
+ this.deviceSession.authStatus = status;
178
+ }
179
+ res.json({
180
+ success: true
181
+ });
182
+ });
183
+ // Playback Telemetry Report
184
+ app.post('/v1/playback', (req, res)=>{
185
+ this.currentPlayback = req.body;
186
+ console.log('[AppleMusicTwin] Playback telemetry received:', this.currentPlayback);
187
+ res.json({
188
+ success: true
189
+ });
190
+ });
191
+ // --- Test Control Endpoints ---
192
+ // Health
193
+ app.get('/test/apple-music/health', (_req, res)=>{
194
+ res.json({
195
+ status: 'ok'
196
+ });
197
+ });
198
+ // Reset
199
+ app.post('/test/apple-music/reset', async (_req, res)=>{
200
+ await this.reset();
201
+ res.json({
202
+ success: true
203
+ });
204
+ });
205
+ // Runtime fixtures override
206
+ app.put('/test/apple-music/fixtures', (req, res)=>{
207
+ const { tracks, playlists } = req.body || {};
208
+ if (tracks) this.currentTracks = tracks;
209
+ if (playlists) this.currentPlaylists = playlists;
210
+ res.json({
211
+ success: true,
212
+ trackCount: this.currentTracks.length,
213
+ playlistCount: this.currentPlaylists.length
214
+ });
215
+ });
216
+ // Error control
217
+ app.post('/test/apple-music/error-mode', (req, res)=>{
218
+ const { mode } = req.body || {};
219
+ if (!mode) {
220
+ res.status(400).json({
221
+ error: 'Missing "mode" field'
222
+ });
223
+ return;
224
+ }
225
+ this.errorModeManager.setMode({
226
+ type: mode
227
+ });
228
+ res.json({
229
+ success: true,
230
+ mode
231
+ });
232
+ });
233
+ app.delete('/test/apple-music/error-mode', (_req, res)=>{
234
+ this.errorModeManager.clearMode();
235
+ res.json({
236
+ success: true,
237
+ mode: null
238
+ });
239
+ });
240
+ app.get('/test/apple-music/error-mode', (_req, res)=>{
241
+ var _ref;
242
+ const mode = this.errorModeManager.getMode();
243
+ res.json({
244
+ active: mode !== null,
245
+ mode: (_ref = mode == null ? void 0 : mode.type) != null ? _ref : null
246
+ });
247
+ });
248
+ return new Promise((resolve, reject)=>{
249
+ this.server = app.listen(this.config.port, ()=>{
250
+ console.log(`[AppleMusicTwin] Express server listening on port ${this.config.port}`);
251
+ resolve();
252
+ });
253
+ this.server.on('error', reject);
254
+ });
255
+ }
256
+ async stop() {
257
+ const server = this.server;
258
+ if (!server) return;
259
+ return new Promise((resolve, reject)=>{
260
+ server.close((err)=>{
261
+ if (err) return reject(err);
262
+ this.server = null;
263
+ console.log('[AppleMusicTwin] Express server stopped');
264
+ resolve();
265
+ });
266
+ });
267
+ }
268
+ async reset() {
269
+ this.errorModeManager.reset();
270
+ this.currentTracks = [
271
+ ...trackFixtures
272
+ ];
273
+ this.currentPlaylists = [
274
+ ...playlistFixtures
275
+ ];
276
+ this.deviceSession = {
277
+ developerToken: 'mock-developer-token-jwt',
278
+ musicUserToken: 'mock-music-user-token',
279
+ storefront: 'us',
280
+ mutExpires: '2027-01-01T00:00:00Z',
281
+ authStatus: 'authorized'
282
+ };
283
+ this.currentPlayback = null;
284
+ }
285
+ async isHealthy() {
286
+ try {
287
+ const res = await fetch(`${this.config.externalUrl}/test/apple-music/health`, {
288
+ headers: {
289
+ Connection: 'close'
290
+ }
291
+ });
292
+ await res.text();
293
+ return res.ok;
294
+ } catch (unused) {
295
+ return false;
296
+ }
297
+ }
298
+ getEmulatorHost() {
299
+ return this.config.externalUrl;
300
+ }
301
+ constructor(overrides){
302
+ this.server = null;
303
+ this.currentTracks = [];
304
+ this.currentPlaylists = [];
305
+ this.errorModeManager = new AppleMusicErrorModeManager();
306
+ // Stateful sessions
307
+ this.deviceSession = {
308
+ developerToken: 'mock-developer-token-jwt',
309
+ musicUserToken: 'mock-music-user-token',
310
+ storefront: 'us',
311
+ mutExpires: '2027-01-01T00:00:00Z',
312
+ authStatus: 'authorized'
313
+ };
314
+ this.currentPlayback = null;
315
+ this.config = createAppleMusicConfig(overrides);
316
+ this.currentTracks = [
317
+ ...trackFixtures
318
+ ];
319
+ this.currentPlaylists = [
320
+ ...playlistFixtures
321
+ ];
322
+ }
323
+ }
324
+
325
+ //# sourceMappingURL=apple-music-twin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/apple-music/apple-music-twin.ts"],"sourcesContent":["import http from 'node:http';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport express, { Request, Response } from 'express';\nimport cors from 'cors';\nimport {\n createAppleMusicConfig,\n AppleMusicTwinConfig,\n} from './apple-music-twin.config.js';\nimport {\n trackFixtures,\n playlistFixtures,\n AppleMusicTrackFixture,\n AppleMusicPlaylistFixture,\n} from './catalog-fixtures.js';\nimport {\n formatSearchResponse,\n formatTrackResource,\n formatPlaylistResource,\n} from './catalog-response.js';\nimport { generatePlayerShim } from './player-shim.js';\nimport { AppleMusicErrorModeManager } from './apple-music-error-mode.js';\n\nexport class AppleMusicTwin {\n private readonly config: AppleMusicTwinConfig;\n private server: http.Server | null = null;\n private currentTracks: AppleMusicTrackFixture[] = [];\n private currentPlaylists: AppleMusicPlaylistFixture[] = [];\n private readonly errorModeManager = new AppleMusicErrorModeManager();\n\n // Stateful sessions\n private deviceSession = {\n developerToken: 'mock-developer-token-jwt',\n musicUserToken: 'mock-music-user-token',\n storefront: 'us',\n mutExpires: '2027-01-01T00:00:00Z',\n authStatus: 'authorized' as 'authorized' | 'expiring-soon' | 'expired',\n };\n\n private currentPlayback: unknown = null;\n\n constructor(overrides?: Partial<AppleMusicTwinConfig>) {\n this.config = createAppleMusicConfig(overrides);\n this.currentTracks = [...trackFixtures];\n this.currentPlaylists = [...playlistFixtures];\n }\n\n async start(): Promise<void> {\n if (this.server) return;\n\n const app = express();\n app.use(cors());\n app.use(express.json());\n\n // Middleware to apply simulated errors\n app.use((req, res, next) => {\n const errorResult = this.errorModeManager.matchRequest(req.path);\n if (errorResult) {\n res.status(errorResult.status).json(errorResult.body);\n return;\n }\n next();\n });\n\n // Browser-side script shim\n const shimJs = generatePlayerShim(this.config.externalUrl);\n app.get('/musickit.js', (_req: Request, res: Response) => {\n res.type('application/javascript').send(shimJs);\n });\n\n // Audio static files\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n let audioPath = path.join(__dirname, 'audio');\n if (!fs.existsSync(audioPath)) {\n audioPath = path.join(__dirname, '../audio');\n }\n app.use(\n '/v1/audio',\n express.static(audioPath, {\n setHeaders: (res) => {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Accept-Ranges', 'bytes');\n },\n })\n );\n\n // Artwork static files. Catalog URLs carry Apple-style {w}x{h} placeholders\n // (e.g. /v1/artwork/300x300/track-001.svg) that clients substitute with the\n // size they need; the :size segment is cosmetic, files live flat on disk.\n let artworkPath = path.join(__dirname, 'artwork');\n if (!fs.existsSync(artworkPath)) {\n artworkPath = path.join(__dirname, '../artwork');\n }\n app.get(\n '/v1/artwork/:size/:file',\n (req: Request<{ size: string; file: string }>, res: Response) => {\n const file = path.basename(req.params.file);\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.sendFile(path.join(artworkPath, file), (err) => {\n if (err && !res.headersSent) {\n res.status(404).json({\n errors: [\n {\n title: 'Resource Not Found',\n detail: `Artwork ${file} not found.`,\n },\n ],\n });\n }\n });\n }\n );\n\n // --- Apple Music Catalog API ---\n\n // Search — empty term returns all tracks/playlists\n app.get('/v1/catalog/:storefront/search', (req: Request, res: Response) => {\n const { term = '', types = 'songs' } = req.query as Record<\n string,\n string\n >;\n const requestedTypes = types.split(',');\n\n const matchesTerm = (val: string) =>\n term.trim() === '' || val.toLowerCase().includes(term.toLowerCase());\n\n const matchedTracks = requestedTypes.includes('songs')\n ? this.currentTracks.filter(\n (t) => matchesTerm(t.name) || matchesTerm(t.artistName)\n )\n : [];\n\n const matchedPlaylists = requestedTypes.includes('playlists')\n ? this.currentPlaylists.filter((p) => matchesTerm(p.name))\n : [];\n\n const response = formatSearchResponse(\n matchedTracks,\n matchedPlaylists,\n this.currentTracks,\n this.config.externalUrl\n );\n res.json(response);\n });\n\n // Get Single Song\n app.get(\n '/v1/catalog/:storefront/songs/:id',\n (req: Request, res: Response) => {\n const { id } = req.params;\n const track = this.currentTracks.find((t) => t.id === id);\n if (!track) {\n res.status(404).json({\n errors: [\n {\n title: 'Resource Not Found',\n detail: `Song with ID ${id} not found.`,\n },\n ],\n });\n return;\n }\n res.json({\n data: [formatTrackResource(track, this.config.externalUrl)],\n });\n }\n );\n\n // Get Single Playlist\n app.get(\n '/v1/catalog/:storefront/playlists/:id',\n (req: Request, res: Response) => {\n const { id } = req.params;\n const playlist = this.currentPlaylists.find((p) => p.id === id);\n if (!playlist) {\n res.status(404).json({\n errors: [\n {\n title: 'Resource Not Found',\n detail: `Playlist with ID ${id} not found.`,\n },\n ],\n });\n return;\n }\n res.json({\n data: [\n formatPlaylistResource(\n playlist,\n this.currentTracks,\n this.config.externalUrl\n ),\n ],\n });\n }\n );\n\n // Get Playlist Tracks\n app.get(\n '/v1/catalog/:storefront/playlists/:id/tracks',\n (req: Request, res: Response) => {\n const { id } = req.params;\n const playlist = this.currentPlaylists.find((p) => p.id === id);\n if (!playlist) {\n res.status(404).json({\n errors: [\n {\n title: 'Resource Not Found',\n detail: `Playlist with ID ${id} not found.`,\n },\n ],\n });\n return;\n }\n const playlistTracks = playlist.trackIds\n .map((tid) => this.currentTracks.find((t) => t.id === tid))\n .filter((t): t is AppleMusicTrackFixture => !!t);\n\n res.json({\n data: playlistTracks.map((t) =>\n formatTrackResource(t, this.config.externalUrl)\n ),\n });\n }\n );\n\n // --- Custom Auth / Playback endpoints ---\n\n // Developer Token\n app.get('/v1/developer-token', (_req: Request, res: Response) => {\n res.json({ developerToken: this.deviceSession.developerToken });\n });\n\n // Device Session\n app.get('/v1/device-session', (_req: Request, res: Response) => {\n res.json(this.deviceSession);\n });\n\n // Connect User Apple Music\n app.post('/v1/connect', (req: Request, res: Response) => {\n const { musicUserToken, storefront, mutExpires } = req.body || {};\n if (!musicUserToken) {\n res.status(400).json({ error: 'Missing musicUserToken' });\n return;\n }\n this.deviceSession.musicUserToken = musicUserToken;\n this.deviceSession.storefront = storefront || 'us';\n this.deviceSession.mutExpires = mutExpires || '2027-01-01T00:00:00Z';\n this.deviceSession.authStatus = 'authorized';\n res.json({ success: true });\n });\n\n // Auth Status GET\n app.get('/v1/auth-status', (_req: Request, res: Response) => {\n res.json({ status: this.deviceSession.authStatus });\n });\n\n // Auth Status POST\n app.post('/v1/auth-status', (req: Request, res: Response) => {\n const { status } = req.body || {};\n if (status) {\n this.deviceSession.authStatus = status;\n }\n res.json({ success: true });\n });\n\n // Playback Telemetry Report\n app.post('/v1/playback', (req: Request, res: Response) => {\n this.currentPlayback = req.body;\n console.log(\n '[AppleMusicTwin] Playback telemetry received:',\n this.currentPlayback\n );\n res.json({ success: true });\n });\n\n // --- Test Control Endpoints ---\n\n // Health\n app.get('/test/apple-music/health', (_req: Request, res: Response) => {\n res.json({ status: 'ok' });\n });\n\n // Reset\n app.post(\n '/test/apple-music/reset',\n async (_req: Request, res: Response) => {\n await this.reset();\n res.json({ success: true });\n }\n );\n\n // Runtime fixtures override\n app.put('/test/apple-music/fixtures', (req: Request, res: Response) => {\n const { tracks, playlists } = req.body || {};\n if (tracks) this.currentTracks = tracks;\n if (playlists) this.currentPlaylists = playlists;\n res.json({\n success: true,\n trackCount: this.currentTracks.length,\n playlistCount: this.currentPlaylists.length,\n });\n });\n\n // Error control\n app.post('/test/apple-music/error-mode', (req: Request, res: Response) => {\n const { mode } = req.body || {};\n if (!mode) {\n res.status(400).json({ error: 'Missing \"mode\" field' });\n return;\n }\n this.errorModeManager.setMode({ type: mode });\n res.json({ success: true, mode });\n });\n\n app.delete(\n '/test/apple-music/error-mode',\n (_req: Request, res: Response) => {\n this.errorModeManager.clearMode();\n res.json({ success: true, mode: null });\n }\n );\n\n app.get('/test/apple-music/error-mode', (_req: Request, res: Response) => {\n const mode = this.errorModeManager.getMode();\n res.json({ active: mode !== null, mode: mode?.type ?? null });\n });\n\n return new Promise<void>((resolve, reject) => {\n this.server = app.listen(this.config.port, () => {\n console.log(\n `[AppleMusicTwin] Express server listening on port ${this.config.port}`\n );\n resolve();\n });\n this.server.on('error', reject);\n });\n }\n\n async stop(): Promise<void> {\n const server = this.server;\n if (!server) return;\n return new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (err) return reject(err);\n this.server = null;\n console.log('[AppleMusicTwin] Express server stopped');\n resolve();\n });\n });\n }\n\n async reset(): Promise<void> {\n this.errorModeManager.reset();\n this.currentTracks = [...trackFixtures];\n this.currentPlaylists = [...playlistFixtures];\n this.deviceSession = {\n developerToken: 'mock-developer-token-jwt',\n musicUserToken: 'mock-music-user-token',\n storefront: 'us',\n mutExpires: '2027-01-01T00:00:00Z',\n authStatus: 'authorized',\n };\n this.currentPlayback = null;\n }\n\n async isHealthy(): Promise<boolean> {\n try {\n const res = await fetch(\n `${this.config.externalUrl}/test/apple-music/health`,\n {\n headers: { Connection: 'close' },\n }\n );\n await res.text();\n return res.ok;\n } catch {\n return false;\n }\n }\n\n getEmulatorHost(): string {\n return this.config.externalUrl;\n }\n}\n"],"names":["path","fs","fileURLToPath","express","cors","createAppleMusicConfig","trackFixtures","playlistFixtures","formatSearchResponse","formatTrackResource","formatPlaylistResource","generatePlayerShim","AppleMusicErrorModeManager","AppleMusicTwin","start","server","app","use","json","req","res","next","errorResult","errorModeManager","matchRequest","status","body","shimJs","config","externalUrl","get","_req","type","send","__dirname","dirname","url","audioPath","join","existsSync","static","setHeaders","setHeader","artworkPath","file","basename","params","sendFile","err","headersSent","errors","title","detail","term","types","query","requestedTypes","split","matchesTerm","val","trim","toLowerCase","includes","matchedTracks","currentTracks","filter","t","name","artistName","matchedPlaylists","currentPlaylists","p","response","id","track","find","data","playlist","playlistTracks","trackIds","map","tid","developerToken","deviceSession","post","musicUserToken","storefront","mutExpires","error","authStatus","success","currentPlayback","console","log","reset","put","tracks","playlists","trackCount","length","playlistCount","mode","setMode","delete","clearMode","getMode","active","Promise","resolve","reject","listen","port","on","stop","close","isHealthy","fetch","headers","Connection","text","ok","getEmulatorHost","overrides"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface AppleMusicTrackFixture {
2
+ id: string;
3
+ name: string;
4
+ artistName: string;
5
+ albumName: string;
6
+ durationMs: number;
7
+ artworkFile: string | null;
8
+ audioFile: string;
9
+ }
10
+ export interface AppleMusicPlaylistFixture {
11
+ id: string;
12
+ name: string;
13
+ description: string | null;
14
+ artworkFile: string | null;
15
+ trackIds: string[];
16
+ }
17
+ export declare const trackFixtures: AppleMusicTrackFixture[];
18
+ export declare const playlistFixtures: AppleMusicPlaylistFixture[];
19
+ //# sourceMappingURL=catalog-fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-fixtures.d.ts","sourceRoot":"","sources":["../../src/apple-music/catalog-fixtures.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAMD,eAAO,MAAM,aAAa,EAAE,sBAAsB,EAgEjD,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,yBAAyB,EAsBvD,CAAC"}