romm-uploader 0.1.1-beta.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.
Files changed (120) hide show
  1. package/.env.example +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +228 -0
  4. package/dist/cache/checksum-cache.d.ts +20 -0
  5. package/dist/cache/checksum-cache.d.ts.map +1 -0
  6. package/dist/cache/checksum-cache.js +134 -0
  7. package/dist/cache/checksum-cache.js.map +1 -0
  8. package/dist/checksum/compute-checksum.d.ts +16 -0
  9. package/dist/checksum/compute-checksum.d.ts.map +1 -0
  10. package/dist/checksum/compute-checksum.js +71 -0
  11. package/dist/checksum/compute-checksum.js.map +1 -0
  12. package/dist/cli.d.ts +7 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +165 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/config.d.ts +34 -0
  17. package/dist/config.d.ts.map +1 -0
  18. package/dist/config.js +95 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/files/discover-files.d.ts +9 -0
  21. package/dist/files/discover-files.d.ts.map +1 -0
  22. package/dist/files/discover-files.js +34 -0
  23. package/dist/files/discover-files.js.map +1 -0
  24. package/dist/files/discover-sources.d.ts +6 -0
  25. package/dist/files/discover-sources.d.ts.map +1 -0
  26. package/dist/files/discover-sources.js +69 -0
  27. package/dist/files/discover-sources.js.map +1 -0
  28. package/dist/files/discover-types.d.ts +8 -0
  29. package/dist/files/discover-types.d.ts.map +1 -0
  30. package/dist/files/discover-types.js +2 -0
  31. package/dist/files/discover-types.js.map +1 -0
  32. package/dist/files/file-info.d.ts +6 -0
  33. package/dist/files/file-info.d.ts.map +1 -0
  34. package/dist/files/file-info.js +44 -0
  35. package/dist/files/file-info.js.map +1 -0
  36. package/dist/output/planner.d.ts +6 -0
  37. package/dist/output/planner.d.ts.map +1 -0
  38. package/dist/output/planner.js +77 -0
  39. package/dist/output/planner.js.map +1 -0
  40. package/dist/output/records.d.ts +16 -0
  41. package/dist/output/records.d.ts.map +1 -0
  42. package/dist/output/records.js +31 -0
  43. package/dist/output/records.js.map +1 -0
  44. package/dist/output/reporter.d.ts +38 -0
  45. package/dist/output/reporter.d.ts.map +1 -0
  46. package/dist/output/reporter.js +66 -0
  47. package/dist/output/reporter.js.map +1 -0
  48. package/dist/romm/auth-cache.d.ts +27 -0
  49. package/dist/romm/auth-cache.d.ts.map +1 -0
  50. package/dist/romm/auth-cache.js +115 -0
  51. package/dist/romm/auth-cache.js.map +1 -0
  52. package/dist/romm/client-http.d.ts +5 -0
  53. package/dist/romm/client-http.d.ts.map +1 -0
  54. package/dist/romm/client-http.js +44 -0
  55. package/dist/romm/client-http.js.map +1 -0
  56. package/dist/romm/client-mappers.d.ts +18 -0
  57. package/dist/romm/client-mappers.d.ts.map +1 -0
  58. package/dist/romm/client-mappers.js +66 -0
  59. package/dist/romm/client-mappers.js.map +1 -0
  60. package/dist/romm/client-session.d.ts +23 -0
  61. package/dist/romm/client-session.d.ts.map +1 -0
  62. package/dist/romm/client-session.js +116 -0
  63. package/dist/romm/client-session.js.map +1 -0
  64. package/dist/romm/client-types.d.ts +29 -0
  65. package/dist/romm/client-types.d.ts.map +1 -0
  66. package/dist/romm/client-types.js +2 -0
  67. package/dist/romm/client-types.js.map +1 -0
  68. package/dist/romm/client.d.ts +86 -0
  69. package/dist/romm/client.d.ts.map +1 -0
  70. package/dist/romm/client.js +168 -0
  71. package/dist/romm/client.js.map +1 -0
  72. package/dist/romm/upload-duplicate-check.d.ts +16 -0
  73. package/dist/romm/upload-duplicate-check.d.ts.map +1 -0
  74. package/dist/romm/upload-duplicate-check.js +16 -0
  75. package/dist/romm/upload-duplicate-check.js.map +1 -0
  76. package/dist/romm/upload-utils.d.ts +9 -0
  77. package/dist/romm/upload-utils.d.ts.map +1 -0
  78. package/dist/romm/upload-utils.js +31 -0
  79. package/dist/romm/upload-utils.js.map +1 -0
  80. package/dist/types.d.ts +98 -0
  81. package/dist/types.d.ts.map +1 -0
  82. package/dist/types.js +5 -0
  83. package/dist/types.js.map +1 -0
  84. package/dist/workflow/create-client.d.ts +7 -0
  85. package/dist/workflow/create-client.d.ts.map +1 -0
  86. package/dist/workflow/create-client.js +19 -0
  87. package/dist/workflow/create-client.js.map +1 -0
  88. package/dist/workflow/discover-input.d.ts +6 -0
  89. package/dist/workflow/discover-input.d.ts.map +1 -0
  90. package/dist/workflow/discover-input.js +14 -0
  91. package/dist/workflow/discover-input.js.map +1 -0
  92. package/dist/workflow/io.d.ts +6 -0
  93. package/dist/workflow/io.d.ts.map +1 -0
  94. package/dist/workflow/io.js +13 -0
  95. package/dist/workflow/io.js.map +1 -0
  96. package/dist/workflow/process-dry-file.d.ts +26 -0
  97. package/dist/workflow/process-dry-file.d.ts.map +1 -0
  98. package/dist/workflow/process-dry-file.js +51 -0
  99. package/dist/workflow/process-dry-file.js.map +1 -0
  100. package/dist/workflow/process-roms.d.ts +15 -0
  101. package/dist/workflow/process-roms.d.ts.map +1 -0
  102. package/dist/workflow/process-roms.js +26 -0
  103. package/dist/workflow/process-roms.js.map +1 -0
  104. package/dist/workflow/process-upload-file.d.ts +22 -0
  105. package/dist/workflow/process-upload-file.d.ts.map +1 -0
  106. package/dist/workflow/process-upload-file.js +55 -0
  107. package/dist/workflow/process-upload-file.js.map +1 -0
  108. package/dist/workflow/resolve-checksums.d.ts +10 -0
  109. package/dist/workflow/resolve-checksums.d.ts.map +1 -0
  110. package/dist/workflow/resolve-checksums.js +28 -0
  111. package/dist/workflow/resolve-checksums.js.map +1 -0
  112. package/dist/workflow/run-dry.d.ts +6 -0
  113. package/dist/workflow/run-dry.d.ts.map +1 -0
  114. package/dist/workflow/run-dry.js +96 -0
  115. package/dist/workflow/run-dry.js.map +1 -0
  116. package/dist/workflow/run-upload.d.ts +6 -0
  117. package/dist/workflow/run-upload.d.ts.map +1 -0
  118. package/dist/workflow/run-upload.js +131 -0
  119. package/dist/workflow/run-upload.js.map +1 -0
  120. package/package.json +68 -0
@@ -0,0 +1,16 @@
1
+ import { ChecksumData, FileInfo } from '../types.js';
2
+ export declare function formatDryRecord(options: {
3
+ file: FileInfo;
4
+ checksums: ChecksumData;
5
+ cached: boolean;
6
+ exists?: boolean;
7
+ romName?: string;
8
+ }): string;
9
+ export declare function formatUploadRecord(options: {
10
+ status: 'uploaded' | 'skipped' | 'failed';
11
+ file: FileInfo;
12
+ reason?: string;
13
+ romName?: string;
14
+ error?: string;
15
+ }): string;
16
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/output/records.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AASrD,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,YAAY,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAaT;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC1C,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAaT"}
@@ -0,0 +1,31 @@
1
+ function toKeyValuePairs(fields) {
2
+ return fields
3
+ .filter(([, value]) => value !== undefined)
4
+ .map(([key, value]) => `${key}=${value}`)
5
+ .join('\t');
6
+ }
7
+ export function formatDryRecord(options) {
8
+ const { file, checksums, cached, exists, romName } = options;
9
+ const status = exists === true ? 'existing' : exists === false ? 'new' : 'unknown';
10
+ const kv = toKeyValuePairs([
11
+ ['source', cached ? 'cache' : 'computed'],
12
+ ['crc32', checksums.crc32],
13
+ ['md5', checksums.md5],
14
+ ['sha1', checksums.sha1],
15
+ ['rom', romName],
16
+ ]);
17
+ return `${status}\t${file.absolutePath}\t${kv}`;
18
+ }
19
+ export function formatUploadRecord(options) {
20
+ const { status, file, reason, romName, error } = options;
21
+ const kv = toKeyValuePairs([
22
+ ['reason', reason],
23
+ ['rom', romName],
24
+ ['error', error],
25
+ ]);
26
+ if (!kv) {
27
+ return `${status}\t${file.absolutePath}`;
28
+ }
29
+ return `${status}\t${file.absolutePath}\t${kv}`;
30
+ }
31
+ //# sourceMappingURL=records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/output/records.ts"],"names":[],"mappings":"AAEA,SAAS,eAAe,CAAC,MAA2C;IAClE,OAAO,MAAM;SACV,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;SAC1C,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;SACxC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAM/B;IACC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAEnF,MAAM,EAAE,GAAG,eAAe,CAAC;QACzB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;QACzC,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC;QAC1B,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC;QACtB,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC;QACxB,CAAC,KAAK,EAAE,OAAO,CAAC;KACjB,CAAC,CAAC;IAEH,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,YAAY,KAAK,EAAE,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAMlC;IACC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACzD,MAAM,EAAE,GAAG,eAAe,CAAC;QACzB,CAAC,QAAQ,EAAE,MAAM,CAAC;QAClB,CAAC,KAAK,EAAE,OAAO,CAAC;QAChB,CAAC,OAAO,EAAE,KAAK,CAAC;KACjB,CAAC,CAAC;IAEH,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;IAC3C,CAAC;IAED,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,YAAY,KAAK,EAAE,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Output reporter module
3
+ * Plain text and JSON output formatting
4
+ */
5
+ import { FileResult, RunSummary } from '../types.js';
6
+ export interface ReporterOptions {
7
+ json: boolean;
8
+ verbose: boolean;
9
+ }
10
+ export declare class Reporter {
11
+ private options;
12
+ constructor(options: ReporterOptions);
13
+ /**
14
+ * Report file discovery results
15
+ */
16
+ reportDiscovery(fileCount: number): void;
17
+ /**
18
+ * Report checksum computation
19
+ */
20
+ reportChecksum(filePath: string, algorithm: string): void;
21
+ /**
22
+ * Report file result
23
+ */
24
+ reportFileResult(result: FileResult): void;
25
+ /**
26
+ * Report final summary
27
+ */
28
+ reportSummary(summary: RunSummary): void;
29
+ /**
30
+ * Report platforms list
31
+ */
32
+ reportPlatforms(platforms: Array<{
33
+ id: number;
34
+ slug: string;
35
+ name: string;
36
+ }>): void;
37
+ }
38
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/output/reporter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAkB;gBAErB,OAAO,EAAE,eAAe;IAIpC;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKxC;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKzD;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAU1C;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAcxC;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI;CAWpF"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Output reporter module
3
+ * Plain text and JSON output formatting
4
+ */
5
+ export class Reporter {
6
+ options;
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ /**
11
+ * Report file discovery results
12
+ */
13
+ reportDiscovery(fileCount) {
14
+ if (this.options.json)
15
+ return;
16
+ console.log(`discovered ${fileCount} files`);
17
+ }
18
+ /**
19
+ * Report checksum computation
20
+ */
21
+ reportChecksum(filePath, algorithm) {
22
+ if (!this.options.verbose)
23
+ return;
24
+ console.log(`checksum ${algorithm} ${filePath}`);
25
+ }
26
+ /**
27
+ * Report file result
28
+ */
29
+ reportFileResult(result) {
30
+ if (this.options.json)
31
+ return;
32
+ console.log(`${result.status}\t${result.path}`);
33
+ if (result.error) {
34
+ console.log(` Error: ${result.error}`);
35
+ }
36
+ }
37
+ /**
38
+ * Report final summary
39
+ */
40
+ reportSummary(summary) {
41
+ if (this.options.json) {
42
+ console.log(JSON.stringify({ summary }, null, 2));
43
+ return;
44
+ }
45
+ console.log('summary');
46
+ console.log(`total_scanned=${summary.totalScanned}`);
47
+ console.log(`already_existing=${summary.alreadyExisting}`);
48
+ console.log(`uploaded=${summary.uploaded}`);
49
+ console.log(`failed=${summary.failed}`);
50
+ console.log(`elapsed_seconds=${(summary.elapsedMs / 1000).toFixed(2)}`);
51
+ }
52
+ /**
53
+ * Report platforms list
54
+ */
55
+ reportPlatforms(platforms) {
56
+ if (this.options.json) {
57
+ console.log(JSON.stringify({ platforms }, null, 2));
58
+ return;
59
+ }
60
+ console.log('id\tslug\tname');
61
+ platforms.forEach((p) => {
62
+ console.log(`${p.id}\t${p.slug}\t${p.name}`);
63
+ });
64
+ }
65
+ }
66
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../src/output/reporter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,MAAM,OAAO,QAAQ;IACX,OAAO,CAAkB;IAEjC,YAAY,OAAwB;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,eAAe,CAAC,SAAiB;QAC/B,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI;YAAE,OAAO;QAC9B,OAAO,CAAC,GAAG,CAAC,cAAc,SAAS,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,cAAc,CAAC,QAAgB,EAAE,SAAiB;QAChD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE,OAAO;QAClC,OAAO,CAAC,GAAG,CAAC,YAAY,SAAS,IAAI,QAAQ,EAAE,CAAC,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,MAAkB;QACjC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI;YAAE,OAAO;QAE9B,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAEhD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,aAAa,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,OAAmB;QAC/B,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,iBAAiB,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,oBAAoB,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,UAAU,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED;;OAEG;IACH,eAAe,CAAC,SAA4D;QAC1E,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACpD,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9B,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACtB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Authentication token cache
3
+ * Persists tokens to disk for reuse across runs
4
+ */
5
+ import { AuthTokenCache } from '../types.js';
6
+ export interface AuthCacheOptions {
7
+ enabled: boolean;
8
+ cachePath?: string;
9
+ reauthSkewSeconds: number;
10
+ }
11
+ /**
12
+ * Get default auth cache path based on XDG spec
13
+ */
14
+ export declare function getDefaultAuthCachePath(): string;
15
+ /**
16
+ * Get cached token if valid
17
+ */
18
+ export declare function getCachedToken(baseUrl: string, username: string, options: AuthCacheOptions): Promise<AuthTokenCache | null>;
19
+ /**
20
+ * Store token in cache
21
+ */
22
+ export declare function setCachedToken(baseUrl: string, username: string, token: AuthTokenCache, options: AuthCacheOptions): Promise<void>;
23
+ /**
24
+ * Clear cached token
25
+ */
26
+ export declare function clearCachedToken(baseUrl: string, username: string, options: AuthCacheOptions): Promise<void>;
27
+ //# sourceMappingURL=auth-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-cache.d.ts","sourceRoot":"","sources":["../../src/romm/auth-cache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAK7C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAMhD;AA2DD;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAyBhC;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CASf"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Authentication token cache
3
+ * Persists tokens to disk for reuse across runs
4
+ */
5
+ import { readFile, writeFile, mkdir } from 'fs/promises';
6
+ import { existsSync } from 'fs';
7
+ import { dirname } from 'path';
8
+ /**
9
+ * Get default auth cache path based on XDG spec
10
+ */
11
+ export function getDefaultAuthCachePath() {
12
+ const xdgCacheHome = process.env.XDG_CACHE_HOME;
13
+ if (xdgCacheHome) {
14
+ return `${xdgCacheHome}/romm-uploader/auth-token-cache.json`;
15
+ }
16
+ return `${process.env.HOME ?? '~'}/.cache/romm-uploader/auth-token-cache.json`;
17
+ }
18
+ /**
19
+ * Generate cache key from base URL and username
20
+ */
21
+ function getCacheKey(baseUrl, username) {
22
+ return `${baseUrl}:${username}`;
23
+ }
24
+ /**
25
+ * Load cache from disk
26
+ */
27
+ async function loadCache(cachePath) {
28
+ try {
29
+ if (!existsSync(cachePath)) {
30
+ return {};
31
+ }
32
+ const data = await readFile(cachePath, 'utf-8');
33
+ return JSON.parse(data);
34
+ }
35
+ catch {
36
+ // If cache is corrupt, start fresh
37
+ return {};
38
+ }
39
+ }
40
+ /**
41
+ * Save cache to disk atomically
42
+ */
43
+ async function saveCache(cachePath, data) {
44
+ try {
45
+ const dir = dirname(cachePath);
46
+ if (!existsSync(dir)) {
47
+ await mkdir(dir, { recursive: true, mode: 0o700 });
48
+ }
49
+ // Write to temp file then rename for atomic operation
50
+ const tempPath = `${cachePath}.tmp`;
51
+ await writeFile(tempPath, JSON.stringify(data, null, 2), { mode: 0o600 });
52
+ // Rename is atomic on POSIX systems
53
+ const { rename } = await import('fs/promises');
54
+ await rename(tempPath, cachePath);
55
+ }
56
+ catch (error) {
57
+ console.warn(`⚠️ Warning: Failed to save auth cache: ${error instanceof Error ? error.message : 'Unknown error'}`);
58
+ }
59
+ }
60
+ /**
61
+ * Get cached token if valid
62
+ */
63
+ export async function getCachedToken(baseUrl, username, options) {
64
+ if (!options.enabled)
65
+ return null;
66
+ const cachePath = options.cachePath ?? getDefaultAuthCachePath();
67
+ const cache = await loadCache(cachePath);
68
+ const key = getCacheKey(baseUrl, username);
69
+ const entry = cache[key];
70
+ if (!entry)
71
+ return null;
72
+ // Check if token is still valid (with skew)
73
+ const now = Date.now();
74
+ const validUntil = entry.expiresAtEpochMs - (options.reauthSkewSeconds * 1000);
75
+ if (now >= validUntil) {
76
+ // Token expired or will expire soon
77
+ return null;
78
+ }
79
+ return {
80
+ accessToken: entry.accessToken,
81
+ tokenType: entry.tokenType,
82
+ expiresAtEpochMs: entry.expiresAtEpochMs,
83
+ issuedAtEpochMs: entry.issuedAtEpochMs,
84
+ };
85
+ }
86
+ /**
87
+ * Store token in cache
88
+ */
89
+ export async function setCachedToken(baseUrl, username, token, options) {
90
+ if (!options.enabled)
91
+ return;
92
+ const cachePath = options.cachePath ?? getDefaultAuthCachePath();
93
+ const cache = await loadCache(cachePath);
94
+ const key = getCacheKey(baseUrl, username);
95
+ cache[key] = {
96
+ accessToken: token.accessToken,
97
+ tokenType: token.tokenType,
98
+ expiresAtEpochMs: token.expiresAtEpochMs,
99
+ issuedAtEpochMs: token.issuedAtEpochMs,
100
+ };
101
+ await saveCache(cachePath, cache);
102
+ }
103
+ /**
104
+ * Clear cached token
105
+ */
106
+ export async function clearCachedToken(baseUrl, username, options) {
107
+ if (!options.enabled)
108
+ return;
109
+ const cachePath = options.cachePath ?? getDefaultAuthCachePath();
110
+ const cache = await loadCache(cachePath);
111
+ const key = getCacheKey(baseUrl, username);
112
+ delete cache[key];
113
+ await saveCache(cachePath, cache);
114
+ }
115
+ //# sourceMappingURL=auth-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-cache.js","sourceRoot":"","sources":["../../src/romm/auth-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared HTTP retry behavior for RomM client operations.
3
+ */
4
+ export declare function fetchWithRetry(url: string, options: RequestInit, requestTimeoutSeconds: number, retryCount: number, attempt?: number): Promise<Response>;
5
+ //# sourceMappingURL=client-http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-http.d.ts","sourceRoot":"","sources":["../../src/romm/client-http.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,WAAW,EACpB,qBAAqB,EAAE,MAAM,EAC7B,UAAU,EAAE,MAAM,EAClB,OAAO,SAAI,GACV,OAAO,CAAC,QAAQ,CAAC,CAqCnB"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Shared HTTP retry behavior for RomM client operations.
3
+ */
4
+ const RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503, 504];
5
+ function calculateBackoff(attempt) {
6
+ const baseDelay = 500;
7
+ const maxDelay = 5000;
8
+ const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
9
+ const jitter = delay * 0.1 * (Math.random() * 2 - 1);
10
+ return delay + jitter;
11
+ }
12
+ export async function fetchWithRetry(url, options, requestTimeoutSeconds, retryCount, attempt = 0) {
13
+ const controller = new AbortController();
14
+ const timeout = setTimeout(() => controller.abort(), requestTimeoutSeconds * 1000);
15
+ try {
16
+ const response = await fetch(url, {
17
+ ...options,
18
+ signal: controller.signal,
19
+ });
20
+ if (retryCount > 0 &&
21
+ attempt < retryCount &&
22
+ (RETRYABLE_STATUS_CODES.includes(response.status) || !response.ok)) {
23
+ const delay = calculateBackoff(attempt);
24
+ await new Promise((resolve) => setTimeout(resolve, delay));
25
+ return fetchWithRetry(url, options, requestTimeoutSeconds, retryCount, attempt + 1);
26
+ }
27
+ return response;
28
+ }
29
+ catch (error) {
30
+ if (retryCount > 0 &&
31
+ attempt < retryCount &&
32
+ error instanceof Error &&
33
+ (error.name === 'TypeError' || error.name === 'FetchError')) {
34
+ const delay = calculateBackoff(attempt);
35
+ await new Promise((resolve) => setTimeout(resolve, delay));
36
+ return fetchWithRetry(url, options, requestTimeoutSeconds, retryCount, attempt + 1);
37
+ }
38
+ throw error;
39
+ }
40
+ finally {
41
+ clearTimeout(timeout);
42
+ }
43
+ }
44
+ //# sourceMappingURL=client-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-http.js","sourceRoot":"","sources":["../../src/romm/client-http.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAE9D,SAAS,gBAAgB,CAAC,OAAe;IACvC,MAAM,SAAS,GAAG,GAAG,CAAC;IACtB,MAAM,QAAQ,GAAG,IAAI,CAAC;IACtB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,KAAK,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,OAAO,KAAK,GAAG,MAAM,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAW,EACX,OAAoB,EACpB,qBAA6B,EAC7B,UAAkB,EAClB,OAAO,GAAG,CAAC;IAEX,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,qBAAqB,GAAG,IAAI,CAAC,CAAC;IAEnF,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,GAAG,OAAO;YACV,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QAEH,IACE,UAAU,GAAG,CAAC;YACd,OAAO,GAAG,UAAU;YACpB,CAAC,sBAAsB,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAClE,CAAC;YACD,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;YACxC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;YAC3D,OAAO,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;QACtF,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IACE,UAAU,GAAG,CAAC;YACd,OAAO,GAAG,UAAU;YACpB,KAAK,YAAY,KAAK;YACtB,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC,EAC3D,CAAC;YACD,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;YACxC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;YAC3D,OAAO,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;QACtF,CAAC;QAED,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { Platform, RomLookupResult } from '../types.js';
2
+ import { RommFilesResponse, RommPlatformResponse, RommRomResponse } from './client-types.js';
3
+ export declare function mapPlatform(response: RommPlatformResponse): Platform;
4
+ export declare function mapPlatforms(responses: RommPlatformResponse[]): Platform[];
5
+ export declare function mapLookupResult(response: RommRomResponse): RomLookupResult;
6
+ export declare function mapFilesByFilename(response: RommFilesResponse): Array<{
7
+ id: number;
8
+ name: string;
9
+ platformId: number;
10
+ platformSlug: string;
11
+ fileName: string;
12
+ fileSize: number;
13
+ crcHash?: string;
14
+ md5Hash?: string;
15
+ sha1Hash?: string;
16
+ }>;
17
+ export declare function resolvePlatformId(platforms: Platform[], platformValue: string): number;
18
+ //# sourceMappingURL=client-mappers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-mappers.d.ts","sourceRoot":"","sources":["../../src/romm/client-mappers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE7F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,oBAAoB,GAAG,QAAQ,CAQpE;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,oBAAoB,EAAE,GAAG,QAAQ,EAAE,CAE1E;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CAgB1E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,GAC9D,KAAK,CAAC;IACJ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CAYD;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CA+BtF"}
@@ -0,0 +1,66 @@
1
+ export function mapPlatform(response) {
2
+ return {
3
+ id: response.id,
4
+ slug: response.slug,
5
+ fsSlug: response.fs_slug,
6
+ name: response.name,
7
+ customName: response.custom_name,
8
+ };
9
+ }
10
+ export function mapPlatforms(responses) {
11
+ return responses.map(mapPlatform);
12
+ }
13
+ export function mapLookupResult(response) {
14
+ return {
15
+ exists: true,
16
+ rom: {
17
+ id: response.id,
18
+ name: response.name,
19
+ platformId: response.platform_id,
20
+ platformSlug: response.platform_slug,
21
+ fileName: response.file_name,
22
+ fileSize: response.file_size,
23
+ crcHash: response.crc_hash,
24
+ md5Hash: response.md5_hash,
25
+ sha1Hash: response.sha1_hash,
26
+ raHash: response.ra_hash,
27
+ },
28
+ };
29
+ }
30
+ export function mapFilesByFilename(response) {
31
+ return response.items.map((item) => ({
32
+ id: item.id,
33
+ name: item.name,
34
+ platformId: item.platform_id,
35
+ platformSlug: item.platform_slug,
36
+ fileName: item.file_name,
37
+ fileSize: item.file_size,
38
+ crcHash: item.crc_hash,
39
+ md5Hash: item.md5_hash,
40
+ sha1Hash: item.sha1_hash,
41
+ }));
42
+ }
43
+ export function resolvePlatformId(platforms, platformValue) {
44
+ const numericId = Number.parseInt(platformValue, 10);
45
+ if (!Number.isNaN(numericId)) {
46
+ const platform = platforms.find((p) => p.id === numericId);
47
+ if (platform)
48
+ return platform.id;
49
+ throw new Error(`Platform ID ${platformValue} not found`);
50
+ }
51
+ const slugMatch = platforms.find((p) => p.slug === platformValue);
52
+ if (slugMatch)
53
+ return slugMatch.id;
54
+ const nameMatches = platforms.filter((p) => p.name.toLowerCase() === platformValue.toLowerCase());
55
+ if (nameMatches.length === 1) {
56
+ return nameMatches[0].id;
57
+ }
58
+ if (nameMatches.length > 1) {
59
+ throw new Error(`Platform name "${platformValue}" is ambiguous. ` +
60
+ `Multiple matches: ${nameMatches.map((p) => `${p.name} (ID: ${p.id})`).join(', ')}. ` +
61
+ `Please use the platform ID or slug.`);
62
+ }
63
+ throw new Error(`Platform "${platformValue}" not found. ` +
64
+ `Use --list-platforms to see available platforms.`);
65
+ }
66
+ //# sourceMappingURL=client-mappers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-mappers.js","sourceRoot":"","sources":["../../src/romm/client-mappers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { RommClientOptions } from './client.js';
2
+ interface RommSessionDeps {
3
+ fetchWithRetry: (url: string, options: RequestInit) => Promise<Response>;
4
+ getUrl: (path: string) => string;
5
+ }
6
+ /**
7
+ * Manages auth/session state for the RomM API client.
8
+ */
9
+ export declare class RommSession {
10
+ private _options;
11
+ private _deps;
12
+ private _tokenCache?;
13
+ private _accessToken?;
14
+ private _authAttempted;
15
+ constructor(options: RommClientOptions, deps: RommSessionDeps);
16
+ get accessToken(): string | undefined;
17
+ initialize(): Promise<void>;
18
+ ensureAuthenticated(): Promise<string>;
19
+ authenticate(): Promise<void>;
20
+ fetch(path: string, options?: RequestInit): Promise<Response>;
21
+ }
22
+ export {};
23
+ //# sourceMappingURL=client-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-session.d.ts","sourceRoot":"","sources":["../../src/romm/client-session.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,UAAU,eAAe;IACvB,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAClC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,WAAW,CAAC,CAAiB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,cAAc,CAAS;gBAEnB,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,eAAe;IAM7D,IAAI,WAAW,IAAI,MAAM,GAAG,SAAS,CAEpC;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAe3B,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAQtC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAuD7B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;CAmCxE"}
@@ -0,0 +1,116 @@
1
+ import { clearCachedToken, getCachedToken, setCachedToken } from './auth-cache.js';
2
+ /**
3
+ * Manages auth/session state for the RomM API client.
4
+ */
5
+ export class RommSession {
6
+ _options;
7
+ _deps;
8
+ _tokenCache;
9
+ _accessToken;
10
+ _authAttempted = false;
11
+ constructor(options, deps) {
12
+ this._options = options;
13
+ this._deps = deps;
14
+ this._tokenCache = undefined;
15
+ }
16
+ get accessToken() {
17
+ return this._accessToken;
18
+ }
19
+ async initialize() {
20
+ if (!this._options.authCacheEnabled)
21
+ return;
22
+ const cached = await getCachedToken(this._options.baseUrl, this._options.username, {
23
+ enabled: this._options.authCacheEnabled,
24
+ cachePath: this._options.authCachePath,
25
+ reauthSkewSeconds: this._options.authReauthSkewSeconds,
26
+ });
27
+ if (cached) {
28
+ this._tokenCache = cached;
29
+ this._accessToken = cached.accessToken;
30
+ }
31
+ }
32
+ async ensureAuthenticated() {
33
+ if (!this._accessToken) {
34
+ await this.authenticate();
35
+ }
36
+ return this._accessToken;
37
+ }
38
+ async authenticate() {
39
+ if (!this._options.password) {
40
+ throw new Error('Password required for authentication');
41
+ }
42
+ const url = this._deps.getUrl('/token');
43
+ const body = new URLSearchParams({
44
+ grant_type: 'password',
45
+ username: this._options.username,
46
+ password: this._options.password,
47
+ scope: 'roms.read roms.write platforms.read',
48
+ });
49
+ try {
50
+ const response = await this._deps.fetchWithRetry(url, {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/x-www-form-urlencoded',
54
+ },
55
+ body,
56
+ });
57
+ if (!response.ok) {
58
+ const error = await response.text();
59
+ throw new Error(`Authentication failed: ${response.status} ${error}`);
60
+ }
61
+ const data = (await response.json());
62
+ this._accessToken = data.access_token;
63
+ if (this._options.authCacheEnabled) {
64
+ this._tokenCache = {
65
+ accessToken: data.access_token,
66
+ tokenType: data.token_type,
67
+ expiresAtEpochMs: Date.now() + data.expires * 1000,
68
+ issuedAtEpochMs: Date.now(),
69
+ };
70
+ await setCachedToken(this._options.baseUrl, this._options.username, this._tokenCache, {
71
+ enabled: this._options.authCacheEnabled,
72
+ cachePath: this._options.authCachePath,
73
+ reauthSkewSeconds: this._options.authReauthSkewSeconds,
74
+ });
75
+ }
76
+ }
77
+ catch (error) {
78
+ if (error instanceof Error) {
79
+ if (error.name === 'TypeError' && error.message.includes('fetch failed')) {
80
+ throw new Error(`Cannot connect to ${this._options.baseUrl}. Is the RomM server running?`);
81
+ }
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+ async fetch(path, options = {}) {
87
+ const accessToken = await this.ensureAuthenticated();
88
+ const url = this._deps.getUrl(path);
89
+ const headers = {
90
+ ...options.headers,
91
+ Authorization: `Bearer ${accessToken}`,
92
+ };
93
+ const response = await this._deps.fetchWithRetry(url, { ...options, headers });
94
+ if (response.status === 401 && !this._authAttempted) {
95
+ this._authAttempted = true;
96
+ this._accessToken = undefined;
97
+ await clearCachedToken(this._options.baseUrl, this._options.username, {
98
+ enabled: this._options.authCacheEnabled,
99
+ cachePath: this._options.authCachePath,
100
+ reauthSkewSeconds: this._options.authReauthSkewSeconds,
101
+ });
102
+ await this.authenticate();
103
+ const retryAccessToken = await this.ensureAuthenticated();
104
+ return this._deps.fetchWithRetry(url, {
105
+ ...options,
106
+ headers: {
107
+ ...options.headers,
108
+ Authorization: `Bearer ${retryAccessToken}`,
109
+ },
110
+ });
111
+ }
112
+ this._authAttempted = false;
113
+ return response;
114
+ }
115
+ }
116
+ //# sourceMappingURL=client-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-session.js","sourceRoot":"","sources":["../../src/romm/client-session.ts"],"names":[],"mappings":"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"}