1
+ // Implementation of "Structured Clone" algorithm in MessagPack
2
+ // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
3
+
1
4
import { ExtensionCodec , ExtensionCodecType } from "./ExtensionCodec" ;
2
5
import { encode } from "./encode" ;
3
6
import { decode } from "./decode" ;
4
7
5
8
export const EXT_JAVASCRIPT = 0 ;
6
9
7
- const enum JSData {
8
- Map ,
9
- Set ,
10
- Date ,
11
- RegExp ,
12
- BigInt ,
10
+ const enum JS {
11
+ // defined in "structured clone algorithm"
12
+ // commente-outed ones are TODOs
13
+
14
+ // Boolean = "Boolean",
15
+ // String = "String",
16
+ Date = "Date" ,
17
+ RegExp = "RegExp" ,
18
+ // Blob = "Blob",
19
+ // File = "File",
20
+ // FileList = "FileList",
21
+ ArrayBuffer = "ArrayBuffer" ,
22
+ Int8Array = "Int8Array" ,
23
+ Uint8Array = "Uint8Array" ,
24
+ Uint8ClampedArray = "Uint8ClampedArray" ,
25
+ Int16Array = "Int16Array" ,
26
+ Uint16Array = "Uint16Array" ,
27
+ Int32Array = "Int32Array" ,
28
+ Uint32Array = "Uint32Array" ,
29
+ Float32Array = "Float32Array" ,
30
+ Float64Array = "Float64Array" ,
31
+ BigInt64Array = "BigInt64Array" ,
32
+ BigUint64Array = "BigUint64Array" ,
33
+ DataView = "DataView" ,
34
+ // ImageBitMap = "ImageBitMap",
35
+ // ImageData = "ImageData",
36
+ Map = "Map" ,
37
+ Set = "Set" ,
38
+
39
+ // and more
40
+ BigInt = "BigInt" ,
13
41
}
14
42
15
- export function encodeJavaScriptData ( input : unknown ) : Uint8Array | null {
16
- if ( input instanceof Map ) {
17
- return encode ( [ JSData . Map , [ ...input ] ] ) ;
18
- } else if ( input instanceof Set ) {
19
- return encode ( [ JSData . Set , [ ...input ] ] ) ;
20
- } else if ( input instanceof Date ) {
21
- // Not a MessagePack timestamp because
22
- // it may be overrided by users
23
- return encode ( [ JSData . Date , input . getTime ( ) ] ) ;
24
- } else if ( input instanceof RegExp ) {
25
- return encode ( [ JSData . RegExp , [ input . source , input . flags ] ] ) ;
26
- } else if ( typeof input === "bigint" ) {
27
- return encode ( [ JSData . BigInt , input . toString ( ) ] ) ;
43
+ export function encodeJavaScriptStructure ( input : unknown ) : Uint8Array | null {
44
+ if ( ! ( input instanceof Object ) ) {
45
+ if ( typeof input === "bigint" ) {
46
+ return encode ( [ JS . BigInt , input . toString ( ) ] ) ;
47
+ } else {
48
+ return null ;
49
+ }
50
+ }
51
+ const type = input . constructor . name ;
52
+
53
+ if ( ArrayBuffer . isView ( input ) ) {
54
+ if ( type === JS . Uint8Array ) {
55
+ return null ; // fall through to the default encoder
56
+ } else if ( type === JS . DataView || type === JS . Int8Array || type === JS . Uint8ClampedArray ) {
57
+ // handles them as a byte buffer
58
+ const v = new Uint8Array ( input . buffer , input . byteOffset , input . byteLength )
59
+ return encode ( [ type , v ] ) ;
60
+ } else {
61
+ // handles them as a number array for portability
62
+ return encode ( [ type , ...( input as unknown as Iterable < number > ) ] ) ;
63
+ }
64
+ } else if ( type === JS . ArrayBuffer ) {
65
+ const bufferView = new Uint8Array ( input as ArrayBuffer ) ;
66
+ return encode ( [ type , bufferView ] ) ;
67
+ } else if ( type === JS . Map ) {
68
+ return encode ( [ JS . Map , ...input as Map < unknown , unknown > ] ) ;
69
+ } else if ( type === JS . Set ) {
70
+ return encode ( [ JS . Set , ...input as Set < unknown > ] ) ;
71
+ } else if ( type === JS . Date ) {
72
+ return encode ( [ JS . Date , ( input as Date ) . getTime ( ) ] ) ;
73
+ } else if ( type === JS . RegExp ) {
74
+ return encode ( [ JS . RegExp , ( input as RegExp ) . source , ( input as RegExp ) . flags ] ) ;
28
75
} else {
29
76
return null ;
30
77
}
31
78
}
32
79
33
- export function decodeJavaScriptData ( data : Uint8Array ) {
34
- const [ jsDataType , source ] = decode ( data ) as [ JSData , any ] ;
35
-
36
- switch ( jsDataType ) {
37
- case JSData . Map : {
38
- return new Map < unknown , unknown > ( source ) ;
39
- }
40
- case JSData . Set : {
41
- return new Set < unknown > ( source ) ;
80
+ export function decodeJavaScriptStructure ( data : Uint8Array ) {
81
+ const [ type , ...source ] = decode ( data ) as [ JS , ...any ] ;
82
+ switch ( type ) {
83
+ case JS . BigInt : {
84
+ const [ str ] = source ;
85
+ return BigInt ( str ) ;
42
86
}
43
- case JSData . Date : {
44
- return new Date ( source ) ;
87
+ case JS . Date : {
88
+ const [ millis ] = source ;
89
+ return new Date ( millis ) ;
45
90
}
46
- case JSData . RegExp : {
91
+ case JS . RegExp : {
47
92
const [ pattern , flags ] = source ;
48
93
return new RegExp ( pattern , flags ) ;
49
94
}
50
- case JSData . BigInt : {
51
- return BigInt ( source ) ;
95
+ case JS . ArrayBuffer : {
96
+ const [ buffer ] = source as [ Uint8Array ] ;
97
+ return buffer . slice ( 0 ) . buffer ;
98
+ }
99
+ case JS . Int8Array : {
100
+ const [ v ] = source as [ Uint8Array ] ;
101
+ return new Int8Array ( v . buffer , v . byteOffset , v . byteLength ) ;
102
+ }
103
+ case JS . Uint8Array : {
104
+ // unlikely because it is handled by the default decoder,
105
+ // but technically possible with no conflict.
106
+ const [ v ] = source as [ Uint8Array ] ;
107
+ return new Uint8Array ( v . buffer , v . byteOffset , v . byteLength ) ;
108
+ }
109
+ case JS . Uint8ClampedArray : {
110
+ const [ v ] = source as [ Uint8Array ] ;
111
+ return new Uint8ClampedArray ( v . buffer , v . byteOffset , v . byteLength ) ;
112
+ }
113
+ case JS . Int16Array : {
114
+ return Int16Array . from ( source as ReadonlyArray < number > ) ;
115
+ }
116
+ case JS . Uint16Array : {
117
+ return Uint16Array . from ( source as ReadonlyArray < number > ) ;
118
+ }
119
+ case JS . Int32Array : {
120
+ return Int32Array . from ( source as ReadonlyArray < number > ) ;
121
+ }
122
+ case JS . Uint32Array : {
123
+ return Uint32Array . from ( source as ReadonlyArray < number > ) ;
124
+ }
125
+ case JS . Float32Array : {
126
+ return Float32Array . from ( source as ReadonlyArray < number > ) ;
127
+ }
128
+ case JS . Float64Array : {
129
+ return Float64Array . from ( source as ReadonlyArray < number > ) ;
130
+ }
131
+ case JS . BigInt64Array : {
132
+ return BigInt64Array . from ( source as ReadonlyArray < bigint > ) ;
133
+ }
134
+ case JS . BigUint64Array : {
135
+ return BigUint64Array . from ( source as ReadonlyArray < bigint > ) ;
136
+ }
137
+ case JS . DataView : {
138
+ const [ v ] = source as [ Uint8Array ] ;
139
+ return new DataView ( v . buffer , v . byteOffset , v . byteLength ) ;
140
+ }
141
+ case JS . Map : {
142
+ return new Map ( source ) ;
143
+ }
144
+ case JS . Set : {
145
+ return new Set ( source ) ;
52
146
}
53
147
default : {
54
- throw new Error ( `Unknown data type: ${ jsDataType } ` ) ;
148
+ throw new Error ( `Unknown data type: ${ type } ` ) ;
55
149
}
56
150
}
57
151
}
@@ -61,8 +155,8 @@ export const JavaScriptCodec: ExtensionCodecType<undefined> = (() => {
61
155
62
156
ext . register ( {
63
157
type : EXT_JAVASCRIPT ,
64
- encode : encodeJavaScriptData ,
65
- decode : decodeJavaScriptData ,
158
+ encode : encodeJavaScriptStructure ,
159
+ decode : decodeJavaScriptStructure ,
66
160
} ) ;
67
161
68
162
return ext ;
0 commit comments