@scalebun-release/cli 2.5.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 (210) hide show
  1. package/bin/scalebun.js +2 -0
  2. package/lib/api/client.d.ts +52 -0
  3. package/lib/api/client.d.ts.map +1 -0
  4. package/lib/api/client.js +335 -0
  5. package/lib/api/client.js.map +1 -0
  6. package/lib/commands/ci-init.d.ts +3 -0
  7. package/lib/commands/ci-init.d.ts.map +1 -0
  8. package/lib/commands/ci-init.js +348 -0
  9. package/lib/commands/ci-init.js.map +1 -0
  10. package/lib/commands/doctor.d.ts +31 -0
  11. package/lib/commands/doctor.d.ts.map +1 -0
  12. package/lib/commands/doctor.js +559 -0
  13. package/lib/commands/doctor.js.map +1 -0
  14. package/lib/commands/launch.d.ts +3 -0
  15. package/lib/commands/launch.d.ts.map +1 -0
  16. package/lib/commands/launch.js +23 -0
  17. package/lib/commands/launch.js.map +1 -0
  18. package/lib/commands/link.d.ts +16 -0
  19. package/lib/commands/link.d.ts.map +1 -0
  20. package/lib/commands/link.js +164 -0
  21. package/lib/commands/link.js.map +1 -0
  22. package/lib/commands/login.d.ts +12 -0
  23. package/lib/commands/login.d.ts.map +1 -0
  24. package/lib/commands/login.js +124 -0
  25. package/lib/commands/login.js.map +1 -0
  26. package/lib/commands/migrate-codepush.d.ts +13 -0
  27. package/lib/commands/migrate-codepush.d.ts.map +1 -0
  28. package/lib/commands/migrate-codepush.js +98 -0
  29. package/lib/commands/migrate-codepush.js.map +1 -0
  30. package/lib/commands/ota-bundle.d.ts +22 -0
  31. package/lib/commands/ota-bundle.d.ts.map +1 -0
  32. package/lib/commands/ota-bundle.js +253 -0
  33. package/lib/commands/ota-bundle.js.map +1 -0
  34. package/lib/commands/ota-channels.d.ts +3 -0
  35. package/lib/commands/ota-channels.d.ts.map +1 -0
  36. package/lib/commands/ota-channels.js +231 -0
  37. package/lib/commands/ota-channels.js.map +1 -0
  38. package/lib/commands/ota-delete-bundle.d.ts +16 -0
  39. package/lib/commands/ota-delete-bundle.d.ts.map +1 -0
  40. package/lib/commands/ota-delete-bundle.js +96 -0
  41. package/lib/commands/ota-delete-bundle.js.map +1 -0
  42. package/lib/commands/ota-inspect.d.ts +3 -0
  43. package/lib/commands/ota-inspect.d.ts.map +1 -0
  44. package/lib/commands/ota-inspect.js +258 -0
  45. package/lib/commands/ota-inspect.js.map +1 -0
  46. package/lib/commands/ota-keys.d.ts +91 -0
  47. package/lib/commands/ota-keys.d.ts.map +1 -0
  48. package/lib/commands/ota-keys.js +765 -0
  49. package/lib/commands/ota-keys.js.map +1 -0
  50. package/lib/commands/ota-promote.d.ts +3 -0
  51. package/lib/commands/ota-promote.d.ts.map +1 -0
  52. package/lib/commands/ota-promote.js +194 -0
  53. package/lib/commands/ota-promote.js.map +1 -0
  54. package/lib/commands/ota-publish.d.ts +26 -0
  55. package/lib/commands/ota-publish.d.ts.map +1 -0
  56. package/lib/commands/ota-publish.js +892 -0
  57. package/lib/commands/ota-publish.js.map +1 -0
  58. package/lib/commands/ota-rollout.d.ts +3 -0
  59. package/lib/commands/ota-rollout.d.ts.map +1 -0
  60. package/lib/commands/ota-rollout.js +265 -0
  61. package/lib/commands/ota-rollout.js.map +1 -0
  62. package/lib/commands/ota-sign.d.ts +19 -0
  63. package/lib/commands/ota-sign.d.ts.map +1 -0
  64. package/lib/commands/ota-sign.js +245 -0
  65. package/lib/commands/ota-sign.js.map +1 -0
  66. package/lib/commands/push-apns.d.ts +3 -0
  67. package/lib/commands/push-apns.d.ts.map +1 -0
  68. package/lib/commands/push-apns.js +166 -0
  69. package/lib/commands/push-apns.js.map +1 -0
  70. package/lib/commands/quickstart.d.ts +4 -0
  71. package/lib/commands/quickstart.d.ts.map +1 -0
  72. package/lib/commands/quickstart.js +672 -0
  73. package/lib/commands/quickstart.js.map +1 -0
  74. package/lib/commands/shell.d.ts +28 -0
  75. package/lib/commands/shell.d.ts.map +1 -0
  76. package/lib/commands/shell.js +512 -0
  77. package/lib/commands/shell.js.map +1 -0
  78. package/lib/commands/tls-pins.d.ts +27 -0
  79. package/lib/commands/tls-pins.d.ts.map +1 -0
  80. package/lib/commands/tls-pins.js +209 -0
  81. package/lib/commands/tls-pins.js.map +1 -0
  82. package/lib/index.d.ts +2 -0
  83. package/lib/index.d.ts.map +1 -0
  84. package/lib/index.js +233 -0
  85. package/lib/index.js.map +1 -0
  86. package/lib/ui/banner.d.ts +23 -0
  87. package/lib/ui/banner.d.ts.map +1 -0
  88. package/lib/ui/banner.js +162 -0
  89. package/lib/ui/banner.js.map +1 -0
  90. package/lib/ui/graphics.d.ts +18 -0
  91. package/lib/ui/graphics.d.ts.map +1 -0
  92. package/lib/ui/graphics.js +134 -0
  93. package/lib/ui/graphics.js.map +1 -0
  94. package/lib/ui/mark.d.ts +4 -0
  95. package/lib/ui/mark.d.ts.map +1 -0
  96. package/lib/ui/mark.js +21 -0
  97. package/lib/ui/mark.js.map +1 -0
  98. package/lib/ui/menu.d.ts +109 -0
  99. package/lib/ui/menu.d.ts.map +1 -0
  100. package/lib/ui/menu.js +314 -0
  101. package/lib/ui/menu.js.map +1 -0
  102. package/lib/ui/sixel.d.ts +6 -0
  103. package/lib/ui/sixel.d.ts.map +1 -0
  104. package/lib/ui/sixel.js +9 -0
  105. package/lib/ui/sixel.js.map +1 -0
  106. package/lib/utils/apps.d.ts +29 -0
  107. package/lib/utils/apps.d.ts.map +1 -0
  108. package/lib/utils/apps.js +19 -0
  109. package/lib/utils/apps.js.map +1 -0
  110. package/lib/utils/browser.d.ts +9 -0
  111. package/lib/utils/browser.d.ts.map +1 -0
  112. package/lib/utils/browser.js +39 -0
  113. package/lib/utils/browser.js.map +1 -0
  114. package/lib/utils/bundle.d.ts +153 -0
  115. package/lib/utils/bundle.d.ts.map +1 -0
  116. package/lib/utils/bundle.js +459 -0
  117. package/lib/utils/bundle.js.map +1 -0
  118. package/lib/utils/bundleStore.d.ts +128 -0
  119. package/lib/utils/bundleStore.d.ts.map +1 -0
  120. package/lib/utils/bundleStore.js +250 -0
  121. package/lib/utils/bundleStore.js.map +1 -0
  122. package/lib/utils/context.d.ts +88 -0
  123. package/lib/utils/context.d.ts.map +1 -0
  124. package/lib/utils/context.js +247 -0
  125. package/lib/utils/context.js.map +1 -0
  126. package/lib/utils/credentials.d.ts +59 -0
  127. package/lib/utils/credentials.d.ts.map +1 -0
  128. package/lib/utils/credentials.js +147 -0
  129. package/lib/utils/credentials.js.map +1 -0
  130. package/lib/utils/guide.d.ts +63 -0
  131. package/lib/utils/guide.d.ts.map +1 -0
  132. package/lib/utils/guide.js +191 -0
  133. package/lib/utils/guide.js.map +1 -0
  134. package/lib/utils/hash.d.ts +3 -0
  135. package/lib/utils/hash.d.ts.map +1 -0
  136. package/lib/utils/hash.js +44 -0
  137. package/lib/utils/hash.js.map +1 -0
  138. package/lib/utils/native-id.d.ts +17 -0
  139. package/lib/utils/native-id.d.ts.map +1 -0
  140. package/lib/utils/native-id.js +105 -0
  141. package/lib/utils/native-id.js.map +1 -0
  142. package/lib/utils/output.d.ts +96 -0
  143. package/lib/utils/output.d.ts.map +1 -0
  144. package/lib/utils/output.js +135 -0
  145. package/lib/utils/output.js.map +1 -0
  146. package/lib/utils/prompt.d.ts +106 -0
  147. package/lib/utils/prompt.d.ts.map +1 -0
  148. package/lib/utils/prompt.js +457 -0
  149. package/lib/utils/prompt.js.map +1 -0
  150. package/lib/utils/provenance.d.ts +30 -0
  151. package/lib/utils/provenance.d.ts.map +1 -0
  152. package/lib/utils/provenance.js +157 -0
  153. package/lib/utils/provenance.js.map +1 -0
  154. package/lib/utils/resolve.d.ts +81 -0
  155. package/lib/utils/resolve.d.ts.map +1 -0
  156. package/lib/utils/resolve.js +139 -0
  157. package/lib/utils/resolve.js.map +1 -0
  158. package/lib/utils/sdkBin.d.ts +18 -0
  159. package/lib/utils/sdkBin.d.ts.map +1 -0
  160. package/lib/utils/sdkBin.js +76 -0
  161. package/lib/utils/sdkBin.js.map +1 -0
  162. package/lib/utils/signer.d.ts +94 -0
  163. package/lib/utils/signer.d.ts.map +1 -0
  164. package/lib/utils/signer.js +230 -0
  165. package/lib/utils/signer.js.map +1 -0
  166. package/lib/utils/signing.d.ts +102 -0
  167. package/lib/utils/signing.d.ts.map +1 -0
  168. package/lib/utils/signing.js +229 -0
  169. package/lib/utils/signing.js.map +1 -0
  170. package/lib/utils/transportSecurity.d.ts +36 -0
  171. package/lib/utils/transportSecurity.d.ts.map +1 -0
  172. package/lib/utils/transportSecurity.js +74 -0
  173. package/lib/utils/transportSecurity.js.map +1 -0
  174. package/lib/utils/trustAnchor.d.ts +109 -0
  175. package/lib/utils/trustAnchor.d.ts.map +1 -0
  176. package/lib/utils/trustAnchor.js +169 -0
  177. package/lib/utils/trustAnchor.js.map +1 -0
  178. package/lib/utils/ui.d.ts +12 -0
  179. package/lib/utils/ui.d.ts.map +1 -0
  180. package/lib/utils/ui.js +60 -0
  181. package/lib/utils/ui.js.map +1 -0
  182. package/lib/utils/validate.d.ts +62 -0
  183. package/lib/utils/validate.d.ts.map +1 -0
  184. package/lib/utils/validate.js +189 -0
  185. package/lib/utils/validate.js.map +1 -0
  186. package/lib/vendor/ota-protocol/base64.d.ts +11 -0
  187. package/lib/vendor/ota-protocol/base64.d.ts.map +1 -0
  188. package/lib/vendor/ota-protocol/base64.js +39 -0
  189. package/lib/vendor/ota-protocol/base64.js.map +1 -0
  190. package/lib/vendor/ota-protocol/codec.d.ts +26 -0
  191. package/lib/vendor/ota-protocol/codec.d.ts.map +1 -0
  192. package/lib/vendor/ota-protocol/codec.js +349 -0
  193. package/lib/vendor/ota-protocol/codec.js.map +1 -0
  194. package/lib/vendor/ota-protocol/hex.d.ts +8 -0
  195. package/lib/vendor/ota-protocol/hex.d.ts.map +1 -0
  196. package/lib/vendor/ota-protocol/hex.js +31 -0
  197. package/lib/vendor/ota-protocol/hex.js.map +1 -0
  198. package/lib/vendor/ota-protocol/index.d.ts +17 -0
  199. package/lib/vendor/ota-protocol/index.d.ts.map +1 -0
  200. package/lib/vendor/ota-protocol/index.js +41 -0
  201. package/lib/vendor/ota-protocol/index.js.map +1 -0
  202. package/lib/vendor/ota-protocol/limits.d.ts +39 -0
  203. package/lib/vendor/ota-protocol/limits.d.ts.map +1 -0
  204. package/lib/vendor/ota-protocol/limits.js +42 -0
  205. package/lib/vendor/ota-protocol/limits.js.map +1 -0
  206. package/lib/vendor/ota-protocol/types.d.ts +73 -0
  207. package/lib/vendor/ota-protocol/types.d.ts.map +1 -0
  208. package/lib/vendor/ota-protocol/types.js +24 -0
  209. package/lib/vendor/ota-protocol/types.js.map +1 -0
  210. package/package.json +37 -0
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nearest = nearest;
4
+ exports.checkedEnum = checkedEnum;
5
+ exports.checkedInt = checkedInt;
6
+ exports.checkedPercent = checkedPercent;
7
+ exports.checkedId = checkedId;
8
+ exports.checkedVersionRange = checkedVersionRange;
9
+ exports.checkedText = checkedText;
10
+ const output_1 = require("./output");
11
+ /**
12
+ * Checked parsers for everything the user can type.
13
+ *
14
+ * ── Why this exists ─────────────────────────────────────────────────────────
15
+ *
16
+ * Bad input was being handled three different ways, and two of them were
17
+ * silent:
18
+ *
19
+ * 1. Cast and hope. `opts.platform as 'android' | 'ios'` — `--platform
20
+ * windows` sails through to the bundler.
21
+ * 2. Coerce to a default. `Math.max(1, Number(opts.limit) || 20)` — a typo'd
22
+ * `--limit abc` quietly means 20, and `--limit -5`
23
+ * quietly means 1.
24
+ * 3. Compare and shrug. `if (opts.failOn === 'act')` — `--fail-on acts`
25
+ * matches nothing, so the CI gate the user asked for
26
+ * silently does not exist and the pipeline goes green.
27
+ *
28
+ * The third is the dangerous one and the pattern is general: an unrecognised
29
+ * value that falls through a comparison disables the behaviour it was meant to
30
+ * request, and disabled checks look exactly like passing checks. Every parser
31
+ * here refuses instead, names the flag, shows what was received, and — for
32
+ * enums — suggests the intended value when it is one edit away.
33
+ *
34
+ * All of them exit with ExitCode.Usage, because "you typed the command wrong"
35
+ * and "your project is not ready" (Gate/3) want opposite responses in CI.
36
+ */
37
+ /**
38
+ * Edit distance, capped — only used to decide whether to offer a suggestion,
39
+ * so the exact number past 3 never matters.
40
+ */
41
+ function distance(a, b) {
42
+ const m = a.length;
43
+ const n = b.length;
44
+ if (Math.abs(m - n) > 3)
45
+ return 99;
46
+ let prev = Array.from({ length: n + 1 }, (_, i) => i);
47
+ for (let i = 1; i <= m; i++) {
48
+ const curr = [i];
49
+ for (let j = 1; j <= n; j++) {
50
+ curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
51
+ }
52
+ prev = curr;
53
+ }
54
+ return prev[n];
55
+ }
56
+ /** The allowed value closest to `raw`, when one is close enough to mean it. */
57
+ function nearest(raw, allowed) {
58
+ let best = null;
59
+ let bestScore = 99;
60
+ const lower = raw.toLowerCase();
61
+ for (const option of allowed) {
62
+ const d = distance(lower, option.toLowerCase());
63
+ if (d < bestScore) {
64
+ bestScore = d;
65
+ best = option;
66
+ }
67
+ }
68
+ // Two edits on a short word is already a different word; scale the tolerance
69
+ // so `acts`→`act` is offered but `x`→`act` is not.
70
+ return best !== null && bestScore <= Math.max(1, Math.floor(best.length / 3)) ? best : null;
71
+ }
72
+ /**
73
+ * One of `allowed`, or exit.
74
+ *
75
+ * Replaces `if (opts.x === 'known')` chains, where an unrecognised value is
76
+ * indistinguishable from the flag not being passed at all.
77
+ */
78
+ function checkedEnum(flag, raw, allowed, opts = {}) {
79
+ if (opts.caseInsensitive) {
80
+ const hit = allowed.find((a) => a.toLowerCase() === raw.toLowerCase());
81
+ if (hit)
82
+ return hit;
83
+ }
84
+ else if (allowed.includes(raw)) {
85
+ return raw;
86
+ }
87
+ const guess = nearest(raw, allowed);
88
+ (0, output_1.fail)(`${flag} must be one of ${allowed.join(', ')} — got "${raw}".`, [...(guess ? [`Did you mean ${flag} ${guess}?`] : []), ...(opts.details ?? [])], output_1.ExitCode.Usage);
89
+ }
90
+ /**
91
+ * A whole number in range, or exit.
92
+ *
93
+ * `Number('12abc')` is NaN and `Number('')` is 0 — both of which a `|| default`
94
+ * turns into a plausible-looking value the user never asked for. Everything
95
+ * that is not cleanly an integer is refused here instead.
96
+ */
97
+ function checkedInt(flag, raw, opts = {}) {
98
+ if (raw === undefined || raw === null || raw === '') {
99
+ if (opts.fallback !== undefined)
100
+ return opts.fallback;
101
+ (0, output_1.fail)(`${flag} is required.`, opts.details ?? [], output_1.ExitCode.Usage);
102
+ }
103
+ const text = String(raw).trim();
104
+ const range = opts.min !== undefined && opts.max !== undefined
105
+ ? ` from ${opts.min} to ${opts.max}`
106
+ : opts.min !== undefined
107
+ ? ` of at least ${opts.min}`
108
+ : '';
109
+ // Deliberately strict: Number() accepts '0x10', '1e3', ' 12 ' and Infinity,
110
+ // none of which anyone means to type into a CLI flag.
111
+ if (!/^[+-]?\d+$/.test(text)) {
112
+ (0, output_1.fail)(`${flag} must be a whole number${range} — got "${text}".`, [
113
+ ...(/^[+-]?\d*\.\d+$/.test(text) ? ['Fractions are not accepted here.'] : []),
114
+ ...(opts.details ?? []),
115
+ ], output_1.ExitCode.Usage);
116
+ }
117
+ const n = Number(text);
118
+ if ((opts.min !== undefined && n < opts.min) || (opts.max !== undefined && n > opts.max)) {
119
+ (0, output_1.fail)(`${flag} must be a whole number${range} — got ${n}.`, opts.details ?? [], output_1.ExitCode.Usage);
120
+ }
121
+ return n;
122
+ }
123
+ /**
124
+ * A rollout percentage, 0-100.
125
+ *
126
+ * A trailing `%` is accepted because it is the obvious thing to type for a
127
+ * value the help text calls a percentage, and refusing it teaches nothing.
128
+ */
129
+ function checkedPercent(flag, raw, fallback) {
130
+ const text = raw === undefined || raw === null ? '' : String(raw).trim().replace(/%$/, '');
131
+ return checkedInt(flag, text, {
132
+ min: 0,
133
+ max: 100,
134
+ fallback,
135
+ details: ['0 stages the release without serving it to anyone; 100 serves everyone.'],
136
+ });
137
+ }
138
+ /**
139
+ * An identifier, or exit.
140
+ *
141
+ * Catches the two ways ids arrive broken: an empty value from an unquoted
142
+ * shell variable that did not expand (`--app-id $APP` with APP unset becomes
143
+ * `--app-id` swallowing the NEXT flag), and a value with whitespace or a
144
+ * leading dash from a mis-quoted paste. Both otherwise reach the API and come
145
+ * back as a confusing 404.
146
+ */
147
+ function checkedId(flag, raw) {
148
+ const text = String(raw ?? '').trim();
149
+ if (!text) {
150
+ (0, output_1.fail)(`${flag} was given an empty value.`, ['Check the variable it came from is set.'], output_1.ExitCode.Usage);
151
+ }
152
+ if (text.startsWith('-')) {
153
+ (0, output_1.fail)(`${flag} looks like it swallowed the next flag — got "${text}".`, ['An id never starts with "-". Check for a missing value earlier in the command.'], output_1.ExitCode.Usage);
154
+ }
155
+ if (/\s/.test(text)) {
156
+ (0, output_1.fail)(`${flag} must not contain spaces — got "${text}".`, ['Quote the value if it came from a variable.'], output_1.ExitCode.Usage);
157
+ }
158
+ return text;
159
+ }
160
+ /**
161
+ * A semver range the server will accept, or exit.
162
+ *
163
+ * Deliberately loose. Ranges legitimately look like `1.0.0`, `^1.0.0`,
164
+ * `>=1.0.0`, `1.x`, `>=1.0.0 <2.0.0` and `*`, and a strict grammar here would
165
+ * reject valid input the API would have taken. The check is only for the
166
+ * shapes that are certainly wrong — empty, or containing no version at all —
167
+ * because those come back as a 400 describing a field the user never typed.
168
+ */
169
+ function checkedVersionRange(flag, raw) {
170
+ const text = String(raw ?? '').trim();
171
+ if (!text) {
172
+ (0, output_1.fail)(`${flag} was given an empty value.`, ['Use a range like ">=1.0.0", "^1.2.0" or "1.x".'], output_1.ExitCode.Usage);
173
+ }
174
+ if (text !== '*' && !/\d/.test(text)) {
175
+ (0, output_1.fail)(`${flag} does not look like a version range — got "${text}".`, ['Use a range like ">=1.0.0", "^1.2.0", "1.x", or "*" for any version.'], output_1.ExitCode.Usage);
176
+ }
177
+ return text;
178
+ }
179
+ /** Non-empty free text, or exit. Trims, so a whitespace-only value is empty. */
180
+ function checkedText(flag, raw, opts = {}) {
181
+ const text = String(raw ?? '').trim();
182
+ if (!text)
183
+ (0, output_1.fail)(`${flag} was given an empty value.`, [], output_1.ExitCode.Usage);
184
+ if (opts.max && text.length > opts.max) {
185
+ (0, output_1.fail)(`${flag} must be ${opts.max} characters or fewer — got ${text.length}.`, [], output_1.ExitCode.Usage);
186
+ }
187
+ return text;
188
+ }
189
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Standard Base64 for the transport envelope (docs §4).
3
+ *
4
+ * Decoding is STRICT and canonical: only the standard alphabet, correct padding,
5
+ * length a multiple of 4, and — crucially — the decode→re-encode round trip must
6
+ * reproduce the input exactly. That last check rejects "alternative encodings"
7
+ * (non-zero unused trailing bits), which is a protocol requirement.
8
+ */
9
+ export declare function encodeBase64(bytes: Uint8Array): string;
10
+ export declare function decodeBase64Strict(value: string, maxBytes: number, what?: string): Uint8Array;
11
+ //# sourceMappingURL=base64.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../../../src/vendor/ota-protocol/base64.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,SAAW,GACd,UAAU,CAoBZ"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /**
3
+ * Standard Base64 for the transport envelope (docs §4).
4
+ *
5
+ * Decoding is STRICT and canonical: only the standard alphabet, correct padding,
6
+ * length a multiple of 4, and — crucially — the decode→re-encode round trip must
7
+ * reproduce the input exactly. That last check rejects "alternative encodings"
8
+ * (non-zero unused trailing bits), which is a protocol requirement.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.encodeBase64 = encodeBase64;
12
+ exports.decodeBase64Strict = decodeBase64Strict;
13
+ const types_1 = require("./types");
14
+ const STD_B64 = /^[A-Za-z0-9+/]*={0,2}$/;
15
+ function encodeBase64(bytes) {
16
+ return Buffer.from(bytes).toString('base64');
17
+ }
18
+ function decodeBase64Strict(value, maxBytes, what = 'base64') {
19
+ if (typeof value !== 'string' || value.length === 0) {
20
+ throw new types_1.OtaProtocolError('INVALID_BASE64', `${what}: empty or not a string`);
21
+ }
22
+ // Reject oversized inputs before allocating (a base64 char is ~0.75 bytes).
23
+ if (value.length > Math.ceil((maxBytes * 4) / 3) + 4) {
24
+ throw new types_1.OtaProtocolError('INVALID_BASE64', `${what}: exceeds size limit`);
25
+ }
26
+ if (value.length % 4 !== 0 || !STD_B64.test(value)) {
27
+ throw new types_1.OtaProtocolError('INVALID_BASE64', `${what}: not canonical standard base64`);
28
+ }
29
+ const decoded = Buffer.from(value, 'base64');
30
+ // Buffer.from is lenient; enforce canonical form by re-encoding.
31
+ if (decoded.toString('base64') !== value) {
32
+ throw new types_1.OtaProtocolError('INVALID_BASE64', `${what}: non-canonical base64`);
33
+ }
34
+ if (decoded.length > maxBytes) {
35
+ throw new types_1.OtaProtocolError('INVALID_BASE64', `${what}: decoded size exceeds limit`);
36
+ }
37
+ return new Uint8Array(decoded);
38
+ }
39
+ //# sourceMappingURL=base64.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.js","sourceRoot":"","sources":["../../../src/vendor/ota-protocol/base64.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAMH,oCAEC;AAED,gDAwBC;AAhCD,mCAA2C;AAE3C,MAAM,OAAO,GAAG,wBAAwB,CAAC;AAEzC,SAAgB,YAAY,CAAC,KAAiB;IAC5C,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC/C,CAAC;AAED,SAAgB,kBAAkB,CAChC,KAAa,EACb,QAAgB,EAChB,IAAI,GAAG,QAAQ;IAEf,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,wBAAgB,CAAC,gBAAgB,EAAE,GAAG,IAAI,yBAAyB,CAAC,CAAC;IACjF,CAAC;IACD,4EAA4E;IAC5E,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,wBAAgB,CAAC,gBAAgB,EAAE,GAAG,IAAI,sBAAsB,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,wBAAgB,CAAC,gBAAgB,EAAE,GAAG,IAAI,iCAAiC,CAAC,CAAC;IACzF,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC7C,iEAAiE;IACjE,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC;QACzC,MAAM,IAAI,wBAAgB,CAAC,gBAAgB,EAAE,GAAG,IAAI,wBAAwB,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,wBAAgB,CAAC,gBAAgB,EAAE,GAAG,IAAI,8BAA8B,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Byte-exact serializer + bounds-checked parser for the ScaleBunSignedReleaseV1
3
+ * binary payload. This is the TypeScript REFERENCE implementation; the Kotlin and
4
+ * Swift verifiers and the backend must match it byte-for-byte against the
5
+ * committed fixtures. See docs/ota/SB-OTA-RSA-SHA256-V1.md.
6
+ *
7
+ * The signature is computed/verified over the exact bytes produced by
8
+ * {@link serializeSignedRelease}; verifiers must NOT re-serialize a parsed struct.
9
+ */
10
+ import { ScaleBunSignedReleaseV1 } from './types';
11
+ /** Serialize a signed release, validating every field and the overall limits. */
12
+ export declare function serializeSignedRelease(p: ScaleBunSignedReleaseV1): Uint8Array;
13
+ /**
14
+ * Parse + fully validate a signed payload. Throws {@link OtaProtocolError} with a
15
+ * stable code on any structural, enum, range, or consistency violation, on
16
+ * invalid UTF-8, on an oversized length prefix, and on trailing bytes.
17
+ */
18
+ export declare function parseSignedRelease(bytes: Uint8Array): ScaleBunSignedReleaseV1;
19
+ /**
20
+ * Parse ONLY enough of the payload to extract `keyId` (docs §5 step 4/5), so a
21
+ * verifier can resolve the trusted key before spending an RSA verify. Still fully
22
+ * bounds-checked. The returned keyId is NOT authenticated until the signature is
23
+ * verified over the whole payload.
24
+ */
25
+ export declare function peekKeyId(bytes: Uint8Array): string;
26
+ //# sourceMappingURL=codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../../../src/vendor/ota-protocol/codec.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,OAAO,EAAoB,uBAAuB,EAAgC,MAAM,SAAS,CAAC;AAqElG,iFAAiF;AACjF,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,uBAAuB,GAAG,UAAU,CAwE7E;AAgED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,uBAAuB,CAkH7E;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAmBnD"}
@@ -0,0 +1,349 @@
1
+ "use strict";
2
+ /**
3
+ * Byte-exact serializer + bounds-checked parser for the ScaleBunSignedReleaseV1
4
+ * binary payload. This is the TypeScript REFERENCE implementation; the Kotlin and
5
+ * Swift verifiers and the backend must match it byte-for-byte against the
6
+ * committed fixtures. See docs/ota/SB-OTA-RSA-SHA256-V1.md.
7
+ *
8
+ * The signature is computed/verified over the exact bytes produced by
9
+ * {@link serializeSignedRelease}; verifiers must NOT re-serialize a parsed struct.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.serializeSignedRelease = serializeSignedRelease;
13
+ exports.parseSignedRelease = parseSignedRelease;
14
+ exports.peekKeyId = peekKeyId;
15
+ const limits_1 = require("./limits");
16
+ const types_1 = require("./types");
17
+ const hex_1 = require("./hex");
18
+ const utf8Encoder = new TextEncoder();
19
+ const utf8DecoderFatal = new TextDecoder('utf-8', { fatal: true });
20
+ // ─── serialize ──────────────────────────────────────────────────────────────
21
+ class ByteWriter {
22
+ constructor() {
23
+ this.out = [];
24
+ }
25
+ u16(n) {
26
+ this.out.push((n >>> 8) & 0xff, n & 0xff);
27
+ }
28
+ u32(n) {
29
+ this.out.push((n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff);
30
+ }
31
+ u64(n) {
32
+ let big = BigInt(n);
33
+ const bytes = new Array(8);
34
+ for (let i = 7; i >= 0; i--) {
35
+ bytes[i] = Number(big & 0xffn);
36
+ big >>= 8n;
37
+ }
38
+ this.out.push(...bytes);
39
+ }
40
+ bytes(b) {
41
+ for (let i = 0; i < b.length; i++)
42
+ this.out.push(b[i]);
43
+ }
44
+ lenBytes(b) {
45
+ this.u32(b.length);
46
+ this.bytes(b);
47
+ }
48
+ string(s) {
49
+ this.lenBytes(utf8Encoder.encode(s));
50
+ }
51
+ finish() {
52
+ return Uint8Array.from(this.out);
53
+ }
54
+ }
55
+ function requireEnum(value, allowed, field) {
56
+ if (!allowed.includes(value)) {
57
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `${field}: invalid value ${JSON.stringify(value)}`);
58
+ }
59
+ return value;
60
+ }
61
+ function requireString(value, field, minLen) {
62
+ if (typeof value !== 'string') {
63
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `${field}: not a string`);
64
+ }
65
+ const byteLen = utf8Encoder.encode(value).length;
66
+ if (byteLen < minLen) {
67
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `${field}: too short`);
68
+ }
69
+ if (byteLen > limits_1.MAX_STRING_FIELD_BYTES) {
70
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `${field}: exceeds ${limits_1.MAX_STRING_FIELD_BYTES} bytes`);
71
+ }
72
+ return value;
73
+ }
74
+ function requireUint(value, field, min = 0) {
75
+ if (!Number.isInteger(value) || value < min || value > limits_1.MAX_SAFE_UINT64) {
76
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `${field}: out of range`);
77
+ }
78
+ return value;
79
+ }
80
+ /** Serialize a signed release, validating every field and the overall limits. */
81
+ function serializeSignedRelease(p) {
82
+ if (p.signatureAlgorithm !== limits_1.SIGNATURE_ALGORITHM) {
83
+ throw new types_1.OtaProtocolError('UNSUPPORTED_SIGNATURE_ALGORITHM', p.signatureAlgorithm);
84
+ }
85
+ if (p.hashAlgorithm !== limits_1.HASH_ALGORITHM) {
86
+ throw new types_1.OtaProtocolError('UNSUPPORTED_HASH_ALGORITHM', p.hashAlgorithm);
87
+ }
88
+ const platform = requireEnum(p.platform, limits_1.PLATFORMS, 'platform');
89
+ const artifactType = requireEnum(p.artifactType, limits_1.ARTIFACT_TYPES, 'artifactType');
90
+ requireString(p.keyId, 'keyId', 1);
91
+ requireString(p.projectId, 'projectId', 1);
92
+ requireString(p.applicationId, 'applicationId', 1);
93
+ requireString(p.releaseId, 'releaseId', 1);
94
+ requireString(p.bundleId, 'bundleId', 1);
95
+ requireString(p.targetAppVersion, 'targetAppVersion', 0);
96
+ requireString(p.targetRuntimeVersion, 'targetRuntimeVersion', 0);
97
+ requireString(p.minimumSdkVersion, 'minimumSdkVersion', 0);
98
+ requireUint(p.releaseSequence, 'releaseSequence');
99
+ requireUint(p.issuedAtEpochSeconds, 'issuedAtEpochSeconds', 1);
100
+ requireUint(p.expiresAtEpochSeconds, 'expiresAtEpochSeconds', 0);
101
+ requireUint(p.artifactSize, 'artifactSize', 1);
102
+ if (p.expiresAtEpochSeconds !== 0 && p.expiresAtEpochSeconds < p.issuedAtEpochSeconds) {
103
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'expiresAt < issuedAt');
104
+ }
105
+ const artifactSha = (0, hex_1.hexToBytes)(p.artifactSha256, limits_1.SHA256_LEN);
106
+ const resultSha = (0, hex_1.hexToBytes)(p.resultArtifactSha256, limits_1.SHA256_LEN);
107
+ // full/patch consistency (docs §3.3).
108
+ let baseBundleId = '';
109
+ let baseSha = new Uint8Array(0);
110
+ if (artifactType === 'patch') {
111
+ baseBundleId = requireString(p.baseBundleId, 'baseBundleId', 1);
112
+ baseSha = (0, hex_1.hexToBytes)(p.baseArtifactSha256, limits_1.SHA256_LEN);
113
+ }
114
+ else {
115
+ if (p.baseBundleId !== '' || p.baseArtifactSha256 !== '') {
116
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'full artifact must not carry base fields');
117
+ }
118
+ }
119
+ const w = new ByteWriter();
120
+ w.bytes(limits_1.MAGIC_BYTES);
121
+ w.u16(limits_1.SCHEMA_VERSION);
122
+ w.string(limits_1.SIGNATURE_ALGORITHM);
123
+ w.string(limits_1.HASH_ALGORITHM);
124
+ w.string(p.keyId);
125
+ w.string(p.projectId);
126
+ w.string(p.applicationId);
127
+ w.string(p.releaseId);
128
+ w.string(p.bundleId);
129
+ w.string(platform);
130
+ w.string(artifactType);
131
+ w.string(p.targetAppVersion);
132
+ w.string(p.targetRuntimeVersion);
133
+ w.string(p.minimumSdkVersion);
134
+ w.u64(p.releaseSequence);
135
+ w.u64(p.issuedAtEpochSeconds);
136
+ w.u64(p.expiresAtEpochSeconds);
137
+ w.u64(p.artifactSize);
138
+ w.bytes(artifactSha);
139
+ w.string(baseBundleId);
140
+ w.lenBytes(baseSha);
141
+ w.bytes(resultSha);
142
+ const bytes = w.finish();
143
+ if (bytes.length > limits_1.MAX_SIGNED_PAYLOAD_BYTES) {
144
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `payload exceeds ${limits_1.MAX_SIGNED_PAYLOAD_BYTES} bytes`);
145
+ }
146
+ return bytes;
147
+ }
148
+ // ─── parse ──────────────────────────────────────────────────────────────────
149
+ class ByteReader {
150
+ constructor(buf) {
151
+ this.buf = buf;
152
+ this.pos = 0;
153
+ }
154
+ ensure(n) {
155
+ if (n < 0 || this.pos + n > this.buf.length) {
156
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'truncated payload');
157
+ }
158
+ }
159
+ fixed(n) {
160
+ this.ensure(n);
161
+ const slice = this.buf.subarray(this.pos, this.pos + n);
162
+ this.pos += n;
163
+ return slice;
164
+ }
165
+ u16() {
166
+ const b = this.fixed(2);
167
+ return (b[0] << 8) | b[1];
168
+ }
169
+ u32() {
170
+ const b = this.fixed(4);
171
+ // multiplication avoids the sign bit that << would set for values ≥ 2^31.
172
+ return b[0] * 0x1000000 + (b[1] << 16) + (b[2] << 8) + b[3];
173
+ }
174
+ u64() {
175
+ const b = this.fixed(8);
176
+ let big = 0n;
177
+ for (let i = 0; i < 8; i++)
178
+ big = (big << 8n) | BigInt(b[i]);
179
+ if (big > BigInt(limits_1.MAX_SAFE_UINT64)) {
180
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'uint64 exceeds safe range');
181
+ }
182
+ return Number(big);
183
+ }
184
+ lenBytes(max, field) {
185
+ const len = this.u32();
186
+ if (len > max) {
187
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `${field}: length ${len} exceeds ${max}`);
188
+ }
189
+ return this.fixed(len);
190
+ }
191
+ string(max, field) {
192
+ const bytes = this.lenBytes(max, field);
193
+ try {
194
+ return utf8DecoderFatal.decode(bytes);
195
+ }
196
+ catch {
197
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `${field}: invalid UTF-8`);
198
+ }
199
+ }
200
+ remaining() {
201
+ return this.buf.length - this.pos;
202
+ }
203
+ }
204
+ function equalBytes(a, b) {
205
+ if (a.length !== b.length)
206
+ return false;
207
+ let diff = 0;
208
+ for (let i = 0; i < a.length; i++)
209
+ diff |= a[i] ^ b[i];
210
+ return diff === 0;
211
+ }
212
+ /**
213
+ * Parse + fully validate a signed payload. Throws {@link OtaProtocolError} with a
214
+ * stable code on any structural, enum, range, or consistency violation, on
215
+ * invalid UTF-8, on an oversized length prefix, and on trailing bytes.
216
+ */
217
+ function parseSignedRelease(bytes) {
218
+ if (!(bytes instanceof Uint8Array)) {
219
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'input is not bytes');
220
+ }
221
+ if (bytes.length > limits_1.MAX_SIGNED_PAYLOAD_BYTES) {
222
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'payload exceeds size limit');
223
+ }
224
+ const r = new ByteReader(bytes);
225
+ const magic = r.fixed(limits_1.MAGIC_BYTES.length);
226
+ if (!equalBytes(magic, limits_1.MAGIC_BYTES)) {
227
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'bad magic');
228
+ }
229
+ const schemaVersion = r.u16();
230
+ if (schemaVersion !== limits_1.SCHEMA_VERSION) {
231
+ throw new types_1.OtaProtocolError('UNSUPPORTED_SCHEMA', `schemaVersion ${schemaVersion}`);
232
+ }
233
+ const signatureAlgorithm = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'signatureAlgorithm');
234
+ if (signatureAlgorithm !== limits_1.SIGNATURE_ALGORITHM) {
235
+ throw new types_1.OtaProtocolError('UNSUPPORTED_SIGNATURE_ALGORITHM', signatureAlgorithm);
236
+ }
237
+ const hashAlgorithm = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'hashAlgorithm');
238
+ if (hashAlgorithm !== limits_1.HASH_ALGORITHM) {
239
+ throw new types_1.OtaProtocolError('UNSUPPORTED_HASH_ALGORITHM', hashAlgorithm);
240
+ }
241
+ const keyId = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'keyId');
242
+ const projectId = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'projectId');
243
+ const applicationId = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'applicationId');
244
+ const releaseId = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'releaseId');
245
+ const bundleId = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'bundleId');
246
+ for (const [name, v] of [
247
+ ['keyId', keyId],
248
+ ['projectId', projectId],
249
+ ['applicationId', applicationId],
250
+ ['releaseId', releaseId],
251
+ ['bundleId', bundleId],
252
+ ]) {
253
+ if (v.length === 0)
254
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `${name}: empty`);
255
+ }
256
+ const platformStr = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'platform');
257
+ if (!limits_1.PLATFORMS.includes(platformStr)) {
258
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `platform: ${platformStr}`);
259
+ }
260
+ const artifactTypeStr = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'artifactType');
261
+ if (!limits_1.ARTIFACT_TYPES.includes(artifactTypeStr)) {
262
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', `artifactType: ${artifactTypeStr}`);
263
+ }
264
+ const platform = platformStr;
265
+ const artifactType = artifactTypeStr;
266
+ const targetAppVersion = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'targetAppVersion');
267
+ const targetRuntimeVersion = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'targetRuntimeVersion');
268
+ const minimumSdkVersion = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'minimumSdkVersion');
269
+ const releaseSequence = r.u64();
270
+ const issuedAtEpochSeconds = r.u64();
271
+ const expiresAtEpochSeconds = r.u64();
272
+ const artifactSize = r.u64();
273
+ if (issuedAtEpochSeconds === 0) {
274
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'issuedAt is zero');
275
+ }
276
+ if (artifactSize === 0) {
277
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'artifactSize is zero');
278
+ }
279
+ if (expiresAtEpochSeconds !== 0 && expiresAtEpochSeconds < issuedAtEpochSeconds) {
280
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'expiresAt < issuedAt');
281
+ }
282
+ const artifactSha256 = (0, hex_1.bytesToHex)(r.fixed(limits_1.SHA256_LEN));
283
+ const baseBundleId = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'baseBundleId');
284
+ const baseShaBytes = r.lenBytes(limits_1.SHA256_LEN, 'baseArtifactSha256');
285
+ const resultArtifactSha256 = (0, hex_1.bytesToHex)(r.fixed(limits_1.SHA256_LEN));
286
+ // full/patch consistency (docs §3.3).
287
+ if (artifactType === 'full') {
288
+ if (baseBundleId.length !== 0 || baseShaBytes.length !== 0) {
289
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'full artifact carries base fields');
290
+ }
291
+ }
292
+ else {
293
+ if (baseBundleId.length === 0 || baseShaBytes.length !== limits_1.SHA256_LEN) {
294
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'patch artifact missing base fields');
295
+ }
296
+ }
297
+ if (r.remaining() !== 0) {
298
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'trailing bytes after payload');
299
+ }
300
+ return {
301
+ signatureAlgorithm,
302
+ hashAlgorithm,
303
+ keyId,
304
+ projectId,
305
+ applicationId,
306
+ releaseId,
307
+ bundleId,
308
+ platform,
309
+ artifactType,
310
+ targetAppVersion,
311
+ targetRuntimeVersion,
312
+ minimumSdkVersion,
313
+ releaseSequence,
314
+ issuedAtEpochSeconds,
315
+ expiresAtEpochSeconds,
316
+ artifactSize,
317
+ artifactSha256,
318
+ baseBundleId,
319
+ baseArtifactSha256: baseShaBytes.length === limits_1.SHA256_LEN ? (0, hex_1.bytesToHex)(baseShaBytes) : '',
320
+ resultArtifactSha256,
321
+ };
322
+ }
323
+ /**
324
+ * Parse ONLY enough of the payload to extract `keyId` (docs §5 step 4/5), so a
325
+ * verifier can resolve the trusted key before spending an RSA verify. Still fully
326
+ * bounds-checked. The returned keyId is NOT authenticated until the signature is
327
+ * verified over the whole payload.
328
+ */
329
+ function peekKeyId(bytes) {
330
+ if (bytes.length > limits_1.MAX_SIGNED_PAYLOAD_BYTES) {
331
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'payload exceeds size limit');
332
+ }
333
+ const r = new ByteReader(bytes);
334
+ const magic = r.fixed(limits_1.MAGIC_BYTES.length);
335
+ if (!equalBytes(magic, limits_1.MAGIC_BYTES)) {
336
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'bad magic');
337
+ }
338
+ if (r.u16() !== limits_1.SCHEMA_VERSION) {
339
+ throw new types_1.OtaProtocolError('UNSUPPORTED_SCHEMA', 'schemaVersion');
340
+ }
341
+ r.string(limits_1.MAX_STRING_FIELD_BYTES, 'signatureAlgorithm');
342
+ r.string(limits_1.MAX_STRING_FIELD_BYTES, 'hashAlgorithm');
343
+ const keyId = r.string(limits_1.MAX_STRING_FIELD_BYTES, 'keyId');
344
+ if (keyId.length === 0) {
345
+ throw new types_1.OtaProtocolError('INVALID_SIGNED_PAYLOAD', 'keyId: empty');
346
+ }
347
+ return keyId;
348
+ }
349
+ //# sourceMappingURL=codec.js.map