import * as base64Converter from 'base64-arraybuffer' /** * Format produced by dckey's own encodeCombinedKey() (main.js): * "DCKEY1:" + base64(gzip(licenceKey + " " + activationKey)). The prefix is * plain ASCII, not itself compressed/encoded - base64's alphabet never * contains ":", so it can never collide with the start of a legacy licence * key or activation key, making format detection unambiguous. */ export const COMBINED_KEY_PREFIX = 'DCKEY1:' export const isCombinedLicenceKey = (text: string): boolean => text.trim().startsWith(COMBINED_KEY_PREFIX) const streamToArrayBuffer = async ( readable: ReadableStream ): Promise => { const reader = readable.getReader() const chunks: Uint8Array[] = [] let totalLength = 0 while (true) { const { done, value } = await reader.read() if (done) break chunks.push(value) totalLength += value.length } const result = new Uint8Array(totalLength) let offset = 0 for (const chunk of chunks) { result.set(chunk, offset) offset += chunk.length } return result.buffer } const gzipDecompress = async (bytes: ArrayBuffer): Promise => { const decompressionStream = new DecompressionStream('gzip') const writer = decompressionStream.writable.getWriter() // Awaited (unlike a fire-and-forget write) so invalid gzip data rejects // through this function's own returned promise, not as a separate // unhandled rejection racing the readable side below. const result = await Promise.all([ writer.write(new Uint8Array(bytes)).then(() => writer.close()), streamToArrayBuffer(decompressionStream.readable) ]) return result[1] } /** * Splits a combined licence key back into its two parts. Returns null (not * a rejected promise) when text isn't a combined key at all, so callers can * use it as a "try this, then fall back to the legacy two-field input" check * without a try/catch for that common case - a prefixed-but-corrupted * string still rejects, since that's a real error, not a format mismatch. */ export const splitCombinedLicenceKey = async ( text: string ): Promise<{ licenceKey: string; activationKey: string } | null> => { const trimmed = text.trim() if (!isCombinedLicenceKey(trimmed)) return null const compressedBytes = base64Converter.decode( trimmed.slice(COMBINED_KEY_PREFIX.length) ) const decompressedBytes = await gzipDecompress(compressedBytes) const payload = new TextDecoder().decode(decompressedBytes) const separatorIndex = payload.indexOf(' ') if (separatorIndex === -1) { throw new Error('Invalid combined licence key: missing separator') } return { licenceKey: payload.slice(0, separatorIndex), activationKey: payload.slice(separatorIndex + 1) } }