1+ // @ts -check
2+
3+ export class CodeCompression {
4+ /**
5+ * Compresses a string using gzip compression and returns base64-encoded result.
6+ * @param {string } text - The text to compress
7+ * @returns {Promise<string> } Base64-encoded compressed string
8+ */
9+ static async compress ( text ) {
10+ const textEncoder = new TextEncoder ( ) ;
11+ const stream = new CompressionStream ( 'gzip' ) ;
12+ const writer = stream . writable . getWriter ( ) ;
13+ const reader = stream . readable . getReader ( ) ;
14+
15+ // Start compression
16+ const writePromise = writer . write ( textEncoder . encode ( text ) ) . then ( ( ) => writer . close ( ) ) ;
17+
18+ // Read compressed chunks
19+ const chunks = [ ] ;
20+ let readResult ;
21+ do {
22+ readResult = await reader . read ( ) ;
23+ if ( readResult . value ) {
24+ chunks . push ( readResult . value ) ;
25+ }
26+ } while ( ! readResult . done ) ;
27+
28+ await writePromise ;
29+
30+ // Combine all chunks into single Uint8Array
31+ const totalLength = chunks . reduce ( ( sum , chunk ) => sum + chunk . length , 0 ) ;
32+ const compressed = new Uint8Array ( totalLength ) ;
33+ let offset = 0 ;
34+ for ( const chunk of chunks ) {
35+ compressed . set ( chunk , offset ) ;
36+ offset += chunk . length ;
37+ }
38+
39+ // Convert to base64 for URL safety
40+ return this . uint8ArrayToBase64 ( compressed ) ;
41+ }
42+
43+ /**
44+ * Decompresses a base64-encoded gzip string back to original text.
45+ * @param {string } compressedBase64 - Base64-encoded compressed string
46+ * @returns {Promise<string> } Original decompressed text
47+ */
48+ static async decompress ( compressedBase64 ) {
49+ const compressed = this . base64ToUint8Array ( compressedBase64 ) ;
50+ const stream = new DecompressionStream ( 'gzip' ) ;
51+ const writer = stream . writable . getWriter ( ) ;
52+ const reader = stream . readable . getReader ( ) ;
53+
54+ // Start decompression
55+ const writePromise = writer . write ( compressed ) . then ( ( ) => writer . close ( ) ) ;
56+
57+ // Read decompressed chunks
58+ const chunks = [ ] ;
59+ let readResult ;
60+ do {
61+ readResult = await reader . read ( ) ;
62+ if ( readResult . value ) {
63+ chunks . push ( readResult . value ) ;
64+ }
65+ } while ( ! readResult . done ) ;
66+
67+ await writePromise ;
68+
69+ // Combine chunks and decode
70+ const totalLength = chunks . reduce ( ( sum , chunk ) => sum + chunk . length , 0 ) ;
71+ const decompressed = new Uint8Array ( totalLength ) ;
72+ let offset = 0 ;
73+ for ( const chunk of chunks ) {
74+ decompressed . set ( chunk , offset ) ;
75+ offset += chunk . length ;
76+ }
77+
78+ const textDecoder = new TextDecoder ( ) ;
79+ return textDecoder . decode ( decompressed ) ;
80+ }
81+
82+ /**
83+ * Converts Uint8Array to base64 string.
84+ * @param {Uint8Array } uint8Array - Array to convert
85+ * @returns {string } Base64 string
86+ */
87+ static uint8ArrayToBase64 ( uint8Array ) {
88+ let binary = '' ;
89+ for ( let i = 0 ; i < uint8Array . byteLength ; i ++ ) {
90+ binary += String . fromCharCode ( uint8Array [ i ] ) ;
91+ }
92+ return btoa ( binary ) ;
93+ }
94+
95+ /**
96+ * Converts base64 string to Uint8Array.
97+ * @param {string } base64 - Base64 string to convert
98+ * @returns {Uint8Array } Converted array
99+ */
100+ static base64ToUint8Array ( base64 ) {
101+ const binary = atob ( base64 ) ;
102+ const bytes = new Uint8Array ( binary . length ) ;
103+ for ( let i = 0 ; i < binary . length ; i ++ ) {
104+ bytes [ i ] = binary . charCodeAt ( i ) ;
105+ }
106+ return bytes ;
107+ }
108+ }
109+
110+ /**
111+ * URL parameter manager for sharing code.
112+ * Handles compression, URL generation, and parameter extraction with encoding type versioning.
113+ */
114+ export class CodeShareManager {
115+ /** @type {string } */
116+ static CURRENT_ENCODING = 'gzip-b64' ;
117+
118+ /**
119+ * Available encoding types for future extensibility.
120+ * @type {Object<string, {compress: function, decompress: function}> }
121+ */
122+ static ENCODERS = {
123+ 'gzip-b64' : {
124+ compress : CodeCompression . compress . bind ( CodeCompression ) ,
125+ decompress : CodeCompression . decompress . bind ( CodeCompression )
126+ }
127+ } ;
128+
129+ /**
130+ * Generates a shareable URL with compressed code and encoding type.
131+ * @param {Object } code - Code object containing swift and dts properties
132+ * @param {string } code.swift - Swift code
133+ * @param {string } code.dts - TypeScript definition code
134+ * @returns {Promise<string> } Shareable URL
135+ */
136+ static async generateShareUrl ( code ) {
137+ const codeData = JSON . stringify ( code ) ;
138+ const encoder = this . ENCODERS [ this . CURRENT_ENCODING ] ;
139+
140+ if ( ! encoder ) {
141+ throw new Error ( `Unsupported encoding type: ${ this . CURRENT_ENCODING } ` ) ;
142+ }
143+
144+ const compressed = await encoder . compress ( codeData ) ;
145+
146+ const url = new URL ( window . location . href ) ;
147+ url . searchParams . set ( 'code' , compressed ) ;
148+ url . searchParams . set ( 'enc' , this . CURRENT_ENCODING ) ;
149+
150+ return url . toString ( ) ;
151+ }
152+
153+ /**
154+ * Extracts code from URL parameters with encoding type detection.
155+ * @param {string } [url] - URL to extract from (defaults to current URL)
156+ * @returns {Promise<Object|null> } Code object or null if no code found
157+ */
158+ static async extractCodeFromUrl ( url ) {
159+ const urlObj = new URL ( url || window . location . href ) ;
160+ const compressedCode = urlObj . searchParams . get ( 'code' ) ;
161+ const encodingType = urlObj . searchParams . get ( 'enc' ) || this . CURRENT_ENCODING ;
162+
163+ if ( ! compressedCode ) {
164+ return null ;
165+ }
166+
167+ const encoder = this . ENCODERS [ encodingType ] ;
168+ if ( ! encoder ) {
169+ console . error ( `Unsupported encoding type: ${ encodingType } ` ) ;
170+ throw new Error ( `Unsupported encoding type: ${ encodingType } . Supported types: ${ Object . keys ( this . ENCODERS ) . join ( ', ' ) } ` ) ;
171+ }
172+
173+ try {
174+ const decompressed = await encoder . decompress ( compressedCode ) ;
175+ return JSON . parse ( decompressed ) ;
176+ } catch ( error ) {
177+ console . error ( 'Failed to extract code from URL:' , error ) ;
178+ throw new Error ( `Failed to decode shared code (encoding: ${ encodingType } ): ${ error . message } ` ) ;
179+ }
180+ }
181+
182+ /**
183+ * Checks if current URL contains shared code.
184+ * @returns {boolean } True if URL contains code parameter
185+ */
186+ static hasSharedCode ( ) {
187+ return new URL ( window . location . href ) . searchParams . has ( 'code' ) ;
188+ }
189+ }
0 commit comments