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