dsh-zen-remote 2.2.9 → 2.3.2

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 (149) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/docs/interface.md +1 -1
  4. package/docs/remote-access.md +20 -0
  5. package/lan-gate.mjs +11 -1
  6. package/lib/adapters/dsh-better-sidebar/index.js +6 -2
  7. package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
  8. package/lib/adapters/dsh-better-sidebar/methods.js +3 -0
  9. package/lib/adapters/dsh-better-sidebar/methods.js.map +1 -1
  10. package/lib/admin-routes.js +33 -2
  11. package/lib/admin-routes.js.map +1 -1
  12. package/lib/client.js +3293 -6
  13. package/lib/config.js +70 -0
  14. package/lib/config.js.map +1 -1
  15. package/lib/index.js +232 -2
  16. package/lib/index.js.map +1 -1
  17. package/lib/intercept.js +43 -3
  18. package/lib/intercept.js.map +1 -1
  19. package/lib/lan-gate-server.cjs +58 -3
  20. package/lib/merge-streams.js +42 -12
  21. package/lib/merge-streams.js.map +1 -1
  22. package/lib/types/adapters/dsh-better-sidebar/index.d.ts +1 -1
  23. package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
  24. package/lib/types/adapters/dsh-better-sidebar/methods.d.ts.map +1 -1
  25. package/lib/types/admin-routes.d.ts +13 -0
  26. package/lib/types/admin-routes.d.ts.map +1 -1
  27. package/lib/types/client/index.d.ts.map +1 -1
  28. package/lib/types/client/locales.d.ts +204 -1
  29. package/lib/types/client/locales.d.ts.map +1 -1
  30. package/lib/types/client/wizard/GatewayWizard.d.ts +29 -0
  31. package/lib/types/client/wizard/GatewayWizard.d.ts.map +1 -0
  32. package/lib/types/client/wizard/WizardCard.d.ts +30 -0
  33. package/lib/types/client/wizard/WizardCard.d.ts.map +1 -0
  34. package/lib/types/client/wizard/cards/MigrateCard.d.ts +15 -0
  35. package/lib/types/client/wizard/cards/MigrateCard.d.ts.map +1 -0
  36. package/lib/types/client/wizard/cards/PortForwardCard.d.ts +15 -0
  37. package/lib/types/client/wizard/cards/PortForwardCard.d.ts.map +1 -0
  38. package/lib/types/client/wizard/cards/ProxyCard.d.ts +19 -0
  39. package/lib/types/client/wizard/cards/ProxyCard.d.ts.map +1 -0
  40. package/lib/types/client/wizard/cards/TokenCard.d.ts +20 -0
  41. package/lib/types/client/wizard/cards/TokenCard.d.ts.map +1 -0
  42. package/lib/types/client/wizard/register-wizard.d.ts +51 -0
  43. package/lib/types/client/wizard/register-wizard.d.ts.map +1 -0
  44. package/lib/types/client/wizard/wizard-css.d.ts +10 -0
  45. package/lib/types/client/wizard/wizard-css.d.ts.map +1 -0
  46. package/lib/types/client-data/qr.d.ts +20 -0
  47. package/lib/types/client-data/qr.d.ts.map +1 -0
  48. package/lib/types/client-data/settings-form.d.ts +7 -2
  49. package/lib/types/client-data/settings-form.d.ts.map +1 -1
  50. package/lib/types/client-data/wizard.d.ts +247 -0
  51. package/lib/types/client-data/wizard.d.ts.map +1 -0
  52. package/lib/types/config.d.ts +27 -0
  53. package/lib/types/config.d.ts.map +1 -1
  54. package/lib/types/index.d.ts.map +1 -1
  55. package/lib/types/intercept.d.ts.map +1 -1
  56. package/lib/types/merge-streams.d.ts.map +1 -1
  57. package/lib/types/wizard/binary-io.d.ts +111 -0
  58. package/lib/types/wizard/binary-io.d.ts.map +1 -0
  59. package/lib/types/wizard/binary.d.ts +118 -0
  60. package/lib/types/wizard/binary.d.ts.map +1 -0
  61. package/lib/types/wizard/caddy-process.d.ts +101 -0
  62. package/lib/types/wizard/caddy-process.d.ts.map +1 -0
  63. package/lib/types/wizard/caddy.d.ts +53 -0
  64. package/lib/types/wizard/caddy.d.ts.map +1 -0
  65. package/lib/types/wizard/cloudflare.d.ts +112 -0
  66. package/lib/types/wizard/cloudflare.d.ts.map +1 -0
  67. package/lib/types/wizard/cloudflared.d.ts +71 -0
  68. package/lib/types/wizard/cloudflared.d.ts.map +1 -0
  69. package/lib/types/wizard/probe.d.ts +66 -0
  70. package/lib/types/wizard/probe.d.ts.map +1 -0
  71. package/lib/types/wizard/proxy-snippets.d.ts +60 -0
  72. package/lib/types/wizard/proxy-snippets.d.ts.map +1 -0
  73. package/lib/types/wizard/routes.d.ts +152 -0
  74. package/lib/types/wizard/routes.d.ts.map +1 -0
  75. package/lib/types/wizard/source-detect.d.ts +53 -0
  76. package/lib/types/wizard/source-detect.d.ts.map +1 -0
  77. package/lib/types/wizard/status.d.ts +115 -0
  78. package/lib/types/wizard/status.d.ts.map +1 -0
  79. package/lib/types/wizard/token-store.d.ts +27 -0
  80. package/lib/types/wizard/token-store.d.ts.map +1 -0
  81. package/lib/types/wizard/tunnel-orchestrator.d.ts +90 -0
  82. package/lib/types/wizard/tunnel-orchestrator.d.ts.map +1 -0
  83. package/lib/types/wizard/verify.d.ts +97 -0
  84. package/lib/types/wizard/verify.d.ts.map +1 -0
  85. package/lib/wizard/binary-io.js +386 -0
  86. package/lib/wizard/binary-io.js.map +1 -0
  87. package/lib/wizard/binary.js +138 -0
  88. package/lib/wizard/binary.js.map +1 -0
  89. package/lib/wizard/caddy-process.js +162 -0
  90. package/lib/wizard/caddy-process.js.map +1 -0
  91. package/lib/wizard/caddy.js +80 -0
  92. package/lib/wizard/caddy.js.map +1 -0
  93. package/lib/wizard/cloudflare.js +334 -0
  94. package/lib/wizard/cloudflare.js.map +1 -0
  95. package/lib/wizard/cloudflared.js +158 -0
  96. package/lib/wizard/cloudflared.js.map +1 -0
  97. package/lib/wizard/probe.js +118 -0
  98. package/lib/wizard/probe.js.map +1 -0
  99. package/lib/wizard/proxy-snippets.js +112 -0
  100. package/lib/wizard/proxy-snippets.js.map +1 -0
  101. package/lib/wizard/routes.js +462 -0
  102. package/lib/wizard/routes.js.map +1 -0
  103. package/lib/wizard/source-detect.js +77 -0
  104. package/lib/wizard/source-detect.js.map +1 -0
  105. package/lib/wizard/status.js +94 -0
  106. package/lib/wizard/status.js.map +1 -0
  107. package/lib/wizard/token-store.js +71 -0
  108. package/lib/wizard/token-store.js.map +1 -0
  109. package/lib/wizard/tunnel-orchestrator.js +155 -0
  110. package/lib/wizard/tunnel-orchestrator.js.map +1 -0
  111. package/lib/wizard/verify.js +171 -0
  112. package/lib/wizard/verify.js.map +1 -0
  113. package/package.json +37 -37
  114. package/scripts/check-settings-form.mjs +5 -4
  115. package/scripts/check-wizard-ui.mjs +690 -0
  116. package/src/adapters/dsh-better-sidebar/index.ts +6 -2
  117. package/src/adapters/dsh-better-sidebar/methods.ts +3 -0
  118. package/src/admin-routes.ts +44 -2
  119. package/src/client/index.tsx +9 -0
  120. package/src/client/locales.ts +410 -2
  121. package/src/client/wizard/GatewayWizard.tsx +2224 -0
  122. package/src/client/wizard/WizardCard.tsx +81 -0
  123. package/src/client/wizard/cards/MigrateCard.tsx +42 -0
  124. package/src/client/wizard/cards/PortForwardCard.tsx +37 -0
  125. package/src/client/wizard/cards/ProxyCard.tsx +56 -0
  126. package/src/client/wizard/cards/TokenCard.tsx +81 -0
  127. package/src/client/wizard/register-wizard.ts +99 -0
  128. package/src/client/wizard/wizard-css.ts +948 -0
  129. package/src/client-data/qr.ts +284 -0
  130. package/src/client-data/settings-form.ts +12 -3
  131. package/src/client-data/wizard.ts +432 -0
  132. package/src/config.ts +87 -0
  133. package/src/index.ts +227 -2
  134. package/src/intercept.ts +43 -3
  135. package/src/merge-streams.ts +41 -12
  136. package/src/wizard/binary-io.ts +449 -0
  137. package/src/wizard/binary.ts +177 -0
  138. package/src/wizard/caddy-process.ts +228 -0
  139. package/src/wizard/caddy.ts +97 -0
  140. package/src/wizard/cloudflare.ts +413 -0
  141. package/src/wizard/cloudflared.ts +196 -0
  142. package/src/wizard/probe.ts +161 -0
  143. package/src/wizard/proxy-snippets.ts +128 -0
  144. package/src/wizard/routes.ts +537 -0
  145. package/src/wizard/source-detect.ts +87 -0
  146. package/src/wizard/status.ts +180 -0
  147. package/src/wizard/token-store.ts +73 -0
  148. package/src/wizard/tunnel-orchestrator.ts +197 -0
  149. package/src/wizard/verify.ts +236 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Minimal QR encoder (GW-10): byte mode, versions 1–5, ECC level L, fixed
3
+ * mask 0 — enough for any sane origin URL (106 bytes at v5) while keeping
4
+ * the whole implementation single-block (no interleave) and reviewable.
5
+ * Returns the module matrix (true = dark) or null when the text does not
6
+ * fit. Pure: the check script verifies it at decoder level (RS syndromes
7
+ * all-zero + data recovery + format-info BCH), which is stronger than any
8
+ * memorized reference vector.
9
+ *
10
+ * Standard QR constants only: sizes 21/25/29/33/37, data codewords
11
+ * 19/34/55/80/108, ECC codewords 7/10/15/20/26, alignment centers
12
+ * [6,18]/[6,22]/[6,26]/[6,30], Galois field 0x11d, format-info generator
13
+ * 0x537 XOR-mask 0x5412.
14
+ */
15
+
16
+ interface QrVersion {
17
+ readonly size: number
18
+ readonly dataCodewords: number
19
+ readonly eccCodewords: number
20
+ /** Alignment-pattern centers; v1 has none. */
21
+ readonly alignment: readonly number[] | null
22
+ /** Byte-mode payload capacity (data codewords minus mode+len header). */
23
+ readonly capacity: number
24
+ }
25
+
26
+ const VERSIONS: readonly QrVersion[] = [
27
+ { size: 21, dataCodewords: 19, eccCodewords: 7, alignment: null, capacity: 17 },
28
+ { size: 25, dataCodewords: 34, eccCodewords: 10, alignment: [6, 18], capacity: 32 },
29
+ { size: 29, dataCodewords: 55, eccCodewords: 15, alignment: [6, 22], capacity: 53 },
30
+ { size: 33, dataCodewords: 80, eccCodewords: 20, alignment: [6, 26], capacity: 78 },
31
+ { size: 37, dataCodewords: 108, eccCodewords: 26, alignment: [6, 30], capacity: 106 },
32
+ ]
33
+
34
+ // --- Galois field GF(256), polynomial 0x11d -----------------------------------
35
+
36
+ const EXP = new Uint8Array(512)
37
+ const LOG = new Uint8Array(256)
38
+ {
39
+ let value = 1
40
+ for (let exponent = 0; exponent < 255; exponent++) {
41
+ EXP[exponent] = value
42
+ LOG[value] = exponent
43
+ value <<= 1
44
+ if (value & 0x100) value ^= 0x11d
45
+ }
46
+ for (let exponent = 255; exponent < 512; exponent++) EXP[exponent] = EXP[exponent - 255] ?? 0
47
+ }
48
+
49
+ function gfMul(a: number, b: number): number {
50
+ if (a === 0 || b === 0) return 0
51
+ const la = LOG[a]
52
+ const lb = LOG[b]
53
+ if (la === undefined || lb === undefined) return 0
54
+ return EXP[la + lb] ?? 0
55
+ }
56
+
57
+ /** Reed–Solomon remainder of `data` under the generator for `eccLen` check
58
+ * codewords (LFSR division). The generator is ∏(x − α^i), i = 0…eccLen−1,
59
+ * coefficients high-order first; g[j]·x lands at index j, g[j]·α^i at
60
+ * index j+1. */
61
+ function rsRemainder(data: Uint8Array, eccLen: number): Uint8Array {
62
+ let generator: number[] = [1]
63
+ for (let i = 0; i < eccLen; i++) {
64
+ const next = new Array<number>(generator.length + 1).fill(0)
65
+ for (let j = 0; j < generator.length; j++) {
66
+ const coefficient = generator[j]
67
+ if (coefficient === undefined) continue
68
+ const low = next[j] ?? 0
69
+ const high = next[j + 1] ?? 0
70
+ next[j] = low ^ coefficient
71
+ next[j + 1] = high ^ gfMul(coefficient, EXP[i] ?? 0)
72
+ }
73
+ generator = next
74
+ }
75
+ const remainder = new Uint8Array(eccLen)
76
+ for (const byte of data) {
77
+ const factor = byte ^ (remainder[0] ?? 0)
78
+ remainder.copyWithin(0, 1)
79
+ remainder[eccLen - 1] = 0
80
+ if (factor !== 0) {
81
+ for (let i = 0; i < eccLen; i++) {
82
+ remainder[i] = (remainder[i] ?? 0) ^ gfMul(generator[i + 1] ?? 0, factor)
83
+ }
84
+ }
85
+ }
86
+ return remainder
87
+ }
88
+
89
+ // --- bit assembly --------------------------------------------------------------
90
+
91
+ class BitBuffer {
92
+ readonly bits: number[] = []
93
+
94
+ push(value: number, length: number): void {
95
+ for (let shift = length - 1; shift >= 0; shift--) this.bits.push((value >>> shift) & 1)
96
+ }
97
+ }
98
+
99
+ function buildCodewords(text: Uint8Array, version: QrVersion): Uint8Array {
100
+ const buffer = new BitBuffer()
101
+ buffer.push(0b0100, 4) // byte mode
102
+ buffer.push(text.length, 8)
103
+ for (const byte of text) buffer.push(byte, 8)
104
+ // Terminator (up to 4 zero bits), then pad to a byte boundary.
105
+ buffer.push(0, Math.min(4, version.dataCodewords * 8 - buffer.bits.length))
106
+ while (buffer.bits.length % 8 !== 0) buffer.push(0, 1)
107
+ const bytes: number[] = []
108
+ for (let i = 0; i < buffer.bits.length; i += 8) {
109
+ let byte = 0
110
+ for (let j = 0; j < 8; j++) byte = (byte << 1) | (buffer.bits[i + j] ?? 0)
111
+ bytes.push(byte)
112
+ }
113
+ // Pad alternately 0xEC / 0x11 to the data capacity.
114
+ for (let pad = 0; bytes.length < version.dataCodewords; pad++) bytes.push(pad % 2 === 0 ? 0xec : 0x11)
115
+ const remainder = rsRemainder(Uint8Array.from(bytes), version.eccCodewords)
116
+ return Uint8Array.from([...bytes, ...remainder])
117
+ }
118
+
119
+ // --- matrix ---------------------------------------------------------------------
120
+
121
+ type Grid = (boolean | null)[][]
122
+
123
+ function blankGrid(size: number): Grid {
124
+ return Array.from({ length: size }, () => new Array<boolean | null>(size).fill(null))
125
+ }
126
+
127
+ function placeFinder(grid: Grid, row: number, col: number): void {
128
+ const n = grid.length
129
+ for (let dr = -1; dr <= 7; dr++) {
130
+ for (let dc = -1; dc <= 7; dc++) {
131
+ const r = row + dr
132
+ const c = col + dc
133
+ if (r < 0 || r >= n || c < 0 || c >= n) continue
134
+ const inRing = dr >= 0 && dr <= 6 && (dc === 0 || dc === 6)
135
+ const inRing2 = dc >= 0 && dc <= 6 && (dr === 0 || dr === 6)
136
+ const inCore = dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4
137
+ const target = grid[r]
138
+ if (target !== undefined) target[c] = inRing || inRing2 || inCore
139
+ }
140
+ }
141
+ }
142
+
143
+ function placeAlignment(grid: Grid, centers: readonly number[]): void {
144
+ for (const row of centers) {
145
+ for (const col of centers) {
146
+ // Skip the centers that sit inside a finder's 7×7 (only possible at
147
+ // the three corners; versions 2–5 have none).
148
+ let overlapsFinder = false
149
+ for (const [fr, fc] of [[0, 0], [0, grid.length - 7], [grid.length - 7, 0]] as const) {
150
+ if (row >= fr - 1 && row <= fr + 7 && col >= fc - 1 && col <= fc + 7) overlapsFinder = true
151
+ }
152
+ if (overlapsFinder) continue
153
+ for (let dr = -2; dr <= 2; dr++) {
154
+ for (let dc = -2; dc <= 2; dc++) {
155
+ const target = grid[row + dr]
156
+ if (target !== undefined) target[col + dc] = Math.max(Math.abs(dr), Math.abs(dc)) !== 1
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ function reserveFormatAreas(grid: Grid): void {
164
+ const n = grid.length
165
+ // `false` marks RESERVED (kept from the data placer, overwritten by the
166
+ // real format bits later); null stays "free for data".
167
+ const put = (row: number, col: number, value: boolean): void => {
168
+ const target = grid[row]
169
+ if (target !== undefined) target[col] = value
170
+ }
171
+ for (let i = 0; i <= 8; i++) {
172
+ if (i !== 6) put(8, i, false)
173
+ if (i !== 6) put(i, 8, false)
174
+ }
175
+ for (let i = 0; i < 8; i++) {
176
+ put(n - 1 - i, 8, false)
177
+ // NOTE: unlike the first copy there is NO skip here — the second copy
178
+ // crosses no timing pattern, and leaving one cell unreserved (the
179
+ // first-copy rule pasted over) would let a data bit land inside it and
180
+ // desynchronize the whole stream (v2+).
181
+ put(8, n - 1 - i, false)
182
+ }
183
+ put(n - 8, 8, true) // the fixed dark module
184
+ }
185
+
186
+ function formatBits(mask: number): number {
187
+ const data = (0b01 << 3) | mask // ECC level L
188
+ let rem = data << 10
189
+ for (let i = 4; i >= 0; i--) {
190
+ if ((rem >>> (i + 10)) & 1) rem ^= 0x537 << i
191
+ }
192
+ return ((data << 10) | rem) ^ 0x5412
193
+ }
194
+
195
+ function placeFormat(grid: Grid, mask: number): void {
196
+ const n = grid.length
197
+ const bits = formatBits(mask)
198
+ // ISO/IEC 18004 Figure 25: bit 14 (MSB) sits at (8, 0) / (n - 1, 8) and
199
+ // bit 0 (LSB) sits at (0, 8) / (8, n - 1). Placing LSB-first reverses the
200
+ // 15-bit BCH codeword and makes every real scanner reject the symbol.
201
+ const bit = (index: number): boolean => ((bits >>> index) & 1) === 1
202
+ const put = (row: number, col: number, value: boolean): void => {
203
+ const target = grid[row]
204
+ if (target !== undefined) target[col] = value
205
+ }
206
+ for (let i = 0; i <= 5; i++) put(8, i, bit(14 - i))
207
+ put(8, 7, bit(8))
208
+ put(8, 8, bit(7))
209
+ put(7, 8, bit(6))
210
+ for (let i = 9; i <= 14; i++) put(14 - i, 8, bit(14 - i))
211
+ for (let i = 0; i <= 6; i++) put(n - 1 - i, 8, bit(14 - i))
212
+ for (let i = 7; i <= 14; i++) put(8, n - 15 + i, bit(14 - i))
213
+ }
214
+
215
+ function placeData(grid: Grid, codewords: Uint8Array): void {
216
+ const n = grid.length
217
+ // Fixed mask 0: (row + col) even flips. Any mask decodes; the penalty
218
+ // optimization is optional and this keeps the implementation small.
219
+ const maskFlips = (row: number, col: number): boolean => (row + col) % 2 === 0
220
+ let bitIndex = 7
221
+ let byteIndex = 0
222
+ let increment = -1
223
+ let row = n - 1
224
+ // The standard two-column zigzag: from the bottom-right corner upward,
225
+ // column PAIR by column PAIR, skipping the timing column pair (6).
226
+ for (let right = n - 1; right >= 0; right -= 2) {
227
+ if (right === 6) right = 5
228
+ // Fill the pair top-to-bottom or bottom-to-top until the edge.
229
+ while (true) {
230
+ const cells = grid[row]
231
+ // `row` is always in range here (the while loop flips before that can
232
+ // change); the guard only keeps a future invariant break non-fatal.
233
+ if (cells === undefined) break
234
+ for (let col = right; col >= right - 1; col--) {
235
+ const current = cells[col]
236
+ if (current !== true && current !== false) {
237
+ let dark = false
238
+ const byte = codewords[byteIndex]
239
+ if (byte !== undefined && byteIndex < codewords.length) dark = ((byte >>> bitIndex) & 1) === 1
240
+ if (maskFlips(row, col)) dark = !dark
241
+ cells[col] = dark
242
+ bitIndex -= 1
243
+ if (bitIndex === -1) {
244
+ byteIndex += 1
245
+ bitIndex = 7
246
+ }
247
+ }
248
+ }
249
+ row += increment
250
+ if (row < 0 || row >= n) {
251
+ row -= increment
252
+ increment = -increment
253
+ break
254
+ }
255
+ }
256
+ }
257
+ }
258
+
259
+ /**
260
+ * The QR matrix for `text` (UTF-8, byte mode), or null when it does not fit
261
+ * into a version-5-L symbol (106 bytes).
262
+ */
263
+ export function qrMatrix(text: string): boolean[][] | null {
264
+ const bytes = new TextEncoder().encode(text)
265
+ const version = VERSIONS.find((candidate) => candidate.capacity >= bytes.length)
266
+ if (version === undefined) return null
267
+ const grid = blankGrid(version.size)
268
+ placeFinder(grid, 0, 0)
269
+ placeFinder(grid, 0, version.size - 7)
270
+ placeFinder(grid, version.size - 7, 0)
271
+ if (version.alignment !== null) placeAlignment(grid, version.alignment)
272
+ // Timing patterns.
273
+ for (let i = 8; i < version.size - 8; i++) {
274
+ const timingRow = grid[6]
275
+ const column = grid[i]
276
+ if (timingRow !== undefined && timingRow[i] === null) timingRow[i] = i % 2 === 0
277
+ if (column !== undefined && column[6] === null) column[6] = i % 2 === 0
278
+ }
279
+ reserveFormatAreas(grid)
280
+ const codewords = buildCodewords(bytes, version)
281
+ placeData(grid, codewords)
282
+ placeFormat(grid, 0)
283
+ return grid.map((row) => row.map((cell) => cell === true))
284
+ }
@@ -403,10 +403,19 @@ export interface ClaimRouteBody {
403
403
  retryAfterMs?: number
404
404
  }
405
405
 
406
- /** What the pairing-code box keeps as its draft: uppercase, no spaces or
407
- * hyphens (the gateway strips every other character at claim time anyway). */
406
+ /** The gateway's 32-character unambiguous pairing-code alphabet (no 0/O/1/I/L)
407
+ * and fixed 8-character length (`lib/lan-gate-server.cjs:330-335`). */
408
+ export const PAIRING_CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'
409
+ export const PAIRING_CODE_LENGTH = 8
410
+
411
+ /** What the pairing-code box keeps as its draft: uppercase, restricted to the
412
+ * 32-character pairing-code alphabet (excluding 0/O/1/I/L and symbols), and
413
+ * capped at 8 characters. */
408
414
  export function normalizePairingCode(input: string): string {
409
- return input.toUpperCase().replace(/[\s-]/g, '')
415
+ return input
416
+ .toUpperCase()
417
+ .replace(/[^ABCDEFGHJKMNPQRSTUVWXYZ23456789]/g, '')
418
+ .slice(0, PAIRING_CODE_LENGTH)
410
419
  }
411
420
 
412
421
  /**