@browserless.io/browserless 2.5.0 → 2.6.0

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 (191) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/README.md +55 -36
  3. package/build/config.d.ts +2 -2
  4. package/build/config.js +3 -3
  5. package/build/data/classes.json +1 -1
  6. package/build/data/selectors.json +1 -1
  7. package/build/http.d.ts +1 -0
  8. package/build/http.js +1 -0
  9. package/build/routes/chrome/http/content.post.body.json +8 -8
  10. package/build/routes/chrome/http/pdf.post.body.json +8 -8
  11. package/build/routes/chrome/http/scrape.post.body.json +8 -8
  12. package/build/routes/chrome/http/screenshot.post.body.json +9 -9
  13. package/build/routes/chromium/http/content.post.body.json +8 -8
  14. package/build/routes/chromium/http/pdf.post.body.json +8 -8
  15. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  16. package/build/routes/chromium/http/screenshot.post.body.json +9 -9
  17. package/build/routes/management/http/active.get.d.ts +16 -0
  18. package/build/routes/management/http/active.get.js +17 -0
  19. package/build/routes/management/tests/management.spec.js +7 -0
  20. package/build/server.js +1 -1
  21. package/build/types.d.ts +6 -4
  22. package/build/types.js +1 -0
  23. package/build/utils.d.ts +1 -1
  24. package/docker/base/Dockerfile +1 -1
  25. package/extensions/ublock/1p-filters.html +4 -3
  26. package/extensions/ublock/3p-filters.html +3 -3
  27. package/extensions/ublock/_locales/ar/messages.json +16 -4
  28. package/extensions/ublock/_locales/az/messages.json +16 -4
  29. package/extensions/ublock/_locales/be/messages.json +19 -7
  30. package/extensions/ublock/_locales/bg/messages.json +16 -4
  31. package/extensions/ublock/_locales/bn/messages.json +33 -21
  32. package/extensions/ublock/_locales/br_FR/messages.json +33 -21
  33. package/extensions/ublock/_locales/bs/messages.json +16 -4
  34. package/extensions/ublock/_locales/ca/messages.json +16 -4
  35. package/extensions/ublock/_locales/cs/messages.json +16 -4
  36. package/extensions/ublock/_locales/cv/messages.json +16 -4
  37. package/extensions/ublock/_locales/cy/messages.json +16 -4
  38. package/extensions/ublock/_locales/da/messages.json +21 -9
  39. package/extensions/ublock/_locales/de/messages.json +17 -5
  40. package/extensions/ublock/_locales/el/messages.json +16 -4
  41. package/extensions/ublock/_locales/en/messages.json +16 -4
  42. package/extensions/ublock/_locales/en_GB/messages.json +16 -4
  43. package/extensions/ublock/_locales/eo/messages.json +17 -5
  44. package/extensions/ublock/_locales/es/messages.json +16 -4
  45. package/extensions/ublock/_locales/et/messages.json +16 -4
  46. package/extensions/ublock/_locales/eu/messages.json +16 -4
  47. package/extensions/ublock/_locales/fa/messages.json +24 -12
  48. package/extensions/ublock/_locales/fi/messages.json +16 -4
  49. package/extensions/ublock/_locales/fil/messages.json +16 -4
  50. package/extensions/ublock/_locales/fr/messages.json +16 -4
  51. package/extensions/ublock/_locales/fy/messages.json +16 -4
  52. package/extensions/ublock/_locales/gl/messages.json +16 -4
  53. package/extensions/ublock/_locales/gu/messages.json +16 -4
  54. package/extensions/ublock/_locales/he/messages.json +25 -13
  55. package/extensions/ublock/_locales/hi/messages.json +31 -19
  56. package/extensions/ublock/_locales/hr/messages.json +16 -4
  57. package/extensions/ublock/_locales/hu/messages.json +16 -4
  58. package/extensions/ublock/_locales/hy/messages.json +17 -5
  59. package/extensions/ublock/_locales/id/messages.json +16 -4
  60. package/extensions/ublock/_locales/it/messages.json +17 -5
  61. package/extensions/ublock/_locales/ja/messages.json +16 -4
  62. package/extensions/ublock/_locales/ka/messages.json +16 -4
  63. package/extensions/ublock/_locales/kk/messages.json +16 -4
  64. package/extensions/ublock/_locales/kn/messages.json +74 -62
  65. package/extensions/ublock/_locales/ko/messages.json +16 -4
  66. package/extensions/ublock/_locales/lt/messages.json +23 -11
  67. package/extensions/ublock/_locales/lv/messages.json +16 -4
  68. package/extensions/ublock/_locales/mk/messages.json +16 -4
  69. package/extensions/ublock/_locales/ml/messages.json +19 -7
  70. package/extensions/ublock/_locales/mr/messages.json +16 -4
  71. package/extensions/ublock/_locales/ms/messages.json +16 -4
  72. package/extensions/ublock/_locales/nb/messages.json +16 -4
  73. package/extensions/ublock/_locales/nl/messages.json +16 -4
  74. package/extensions/ublock/_locales/no/messages.json +16 -4
  75. package/extensions/ublock/_locales/oc/messages.json +16 -4
  76. package/extensions/ublock/_locales/pa/messages.json +16 -4
  77. package/extensions/ublock/_locales/pl/messages.json +17 -5
  78. package/extensions/ublock/_locales/pt_BR/messages.json +16 -4
  79. package/extensions/ublock/_locales/pt_PT/messages.json +16 -4
  80. package/extensions/ublock/_locales/ro/messages.json +17 -5
  81. package/extensions/ublock/_locales/ru/messages.json +16 -4
  82. package/extensions/ublock/_locales/si/messages.json +16 -4
  83. package/extensions/ublock/_locales/sk/messages.json +16 -4
  84. package/extensions/ublock/_locales/sl/messages.json +16 -4
  85. package/extensions/ublock/_locales/so/messages.json +16 -4
  86. package/extensions/ublock/_locales/sq/messages.json +16 -4
  87. package/extensions/ublock/_locales/sr/messages.json +16 -4
  88. package/extensions/ublock/_locales/sv/messages.json +20 -8
  89. package/extensions/ublock/_locales/sw/messages.json +16 -4
  90. package/extensions/ublock/_locales/ta/messages.json +16 -4
  91. package/extensions/ublock/_locales/te/messages.json +16 -4
  92. package/extensions/ublock/_locales/th/messages.json +42 -30
  93. package/extensions/ublock/_locales/tr/messages.json +19 -7
  94. package/extensions/ublock/_locales/uk/messages.json +16 -4
  95. package/extensions/ublock/_locales/ur/messages.json +16 -4
  96. package/extensions/ublock/_locales/vi/messages.json +16 -4
  97. package/extensions/ublock/_locales/zh_CN/messages.json +16 -4
  98. package/extensions/ublock/_locales/zh_TW/messages.json +42 -30
  99. package/extensions/ublock/assets/assets.json +95 -78
  100. package/extensions/ublock/assets/resources/scriptlets.js +70 -24
  101. package/extensions/ublock/assets/thirdparties/easylist/easylist.txt +6258 -3453
  102. package/extensions/ublock/assets/thirdparties/easylist/easyprivacy.txt +277 -40
  103. package/extensions/ublock/assets/thirdparties/pgl.yoyo.org/as/serverlist +8 -32
  104. package/extensions/ublock/assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat +107 -12
  105. package/extensions/ublock/assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt +1160 -954
  106. package/extensions/ublock/assets/ublock/badlists.txt +1 -2
  107. package/extensions/ublock/assets/ublock/badware.min.txt +395 -270
  108. package/extensions/ublock/assets/ublock/filters.min.txt +1176 -1238
  109. package/extensions/ublock/assets/ublock/privacy.min.txt +32 -31
  110. package/extensions/ublock/assets/ublock/quick-fixes.min.txt +120 -110
  111. package/extensions/ublock/assets/ublock/unbreak.min.txt +75 -36
  112. package/extensions/ublock/css/1p-filters.css +2 -1
  113. package/extensions/ublock/css/3p-filters.css +1 -16
  114. package/extensions/ublock/css/advanced-settings.css +1 -0
  115. package/extensions/ublock/css/asset-viewer.css +1 -0
  116. package/extensions/ublock/css/code-viewer.css +1 -0
  117. package/extensions/ublock/css/codemirror.css +37 -10
  118. package/extensions/ublock/css/common.css +36 -2
  119. package/extensions/ublock/css/dashboard.css +9 -3
  120. package/extensions/ublock/css/devtools.css +1 -0
  121. package/extensions/ublock/css/document-blocked.css +3 -3
  122. package/extensions/ublock/css/dom-inspector.css +1 -0
  123. package/extensions/ublock/css/dyna-rules.css +1 -0
  124. package/extensions/ublock/css/epicker-ui.css +76 -66
  125. package/extensions/ublock/css/fa-icons.css +1 -0
  126. package/extensions/ublock/css/logger-ui.css +2 -0
  127. package/extensions/ublock/css/popup-fenix.css +1 -0
  128. package/extensions/ublock/css/whitelist.css +1 -0
  129. package/extensions/ublock/dashboard.html +20 -13
  130. package/extensions/ublock/devtools.html +2 -0
  131. package/extensions/ublock/dyna-rules.html +2 -2
  132. package/extensions/ublock/img/flags-of-the-world/np.png +0 -0
  133. package/extensions/ublock/img/fontawesome/fontawesome-defs.svg +1 -0
  134. package/extensions/ublock/js/1p-filters.js +72 -23
  135. package/extensions/ublock/js/3p-filters.js +71 -25
  136. package/extensions/ublock/js/asset-viewer.js +1 -0
  137. package/extensions/ublock/js/assets.js +83 -89
  138. package/extensions/ublock/js/background.js +20 -27
  139. package/extensions/ublock/js/base64-custom.js +1 -102
  140. package/extensions/ublock/js/benchmarks.js +36 -21
  141. package/extensions/ublock/js/biditrie.js +8 -23
  142. package/extensions/ublock/js/broadcast.js +2 -4
  143. package/extensions/ublock/js/cachestorage.js +594 -396
  144. package/extensions/ublock/js/codemirror/search.js +49 -37
  145. package/extensions/ublock/js/codemirror/ubo-static-filtering.js +233 -215
  146. package/extensions/ublock/js/contentscript-extra.js +31 -1
  147. package/extensions/ublock/js/cosmetic-filtering.js +35 -33
  148. package/extensions/ublock/js/dashboard.js +11 -7
  149. package/extensions/ublock/js/devtools.js +22 -0
  150. package/extensions/ublock/js/dom.js +2 -2
  151. package/extensions/ublock/js/dyna-rules.js +17 -16
  152. package/extensions/ublock/js/epicker-ui.js +41 -16
  153. package/extensions/ublock/js/fa-icons.js +1 -0
  154. package/extensions/ublock/js/hntrie.js +10 -25
  155. package/extensions/ublock/js/i18n.js +15 -15
  156. package/extensions/ublock/js/logger-ui.js +9 -6
  157. package/extensions/ublock/js/messaging.js +51 -26
  158. package/extensions/ublock/js/pagestore.js +21 -23
  159. package/extensions/ublock/js/popup-fenix.js +35 -22
  160. package/extensions/ublock/js/redirect-engine.js +15 -30
  161. package/extensions/ublock/js/reverselookup.js +1 -1
  162. package/extensions/ublock/js/s14e-serializer.js +1405 -0
  163. package/extensions/ublock/js/scriptlet-filtering-core.js +1 -1
  164. package/extensions/ublock/js/scriptlets/epicker.js +27 -18
  165. package/extensions/ublock/js/settings.js +32 -21
  166. package/extensions/ublock/js/start.js +121 -62
  167. package/extensions/ublock/js/static-ext-filtering-db.js +6 -6
  168. package/extensions/ublock/js/static-ext-filtering.js +17 -28
  169. package/extensions/ublock/js/static-filtering-parser.js +26 -4
  170. package/extensions/ublock/js/static-net-filtering.js +69 -168
  171. package/extensions/ublock/js/storage.js +178 -155
  172. package/extensions/ublock/js/traffic.js +11 -7
  173. package/extensions/ublock/js/vapi-background.js +49 -62
  174. package/extensions/ublock/js/vapi-client.js +13 -16
  175. package/extensions/ublock/js/webext.js +10 -2
  176. package/extensions/ublock/js/whitelist.js +27 -25
  177. package/extensions/ublock/lib/publicsuffixlist/publicsuffixlist.js +3 -7
  178. package/extensions/ublock/manifest.json +2 -1
  179. package/extensions/ublock/web_accessible_resources/epicker-ui.html +5 -8
  180. package/extensions/ublock/whitelist.html +3 -4
  181. package/package.json +12 -12
  182. package/src/config.ts +3 -4
  183. package/src/http.ts +1 -0
  184. package/src/routes/management/http/active.get.ts +30 -0
  185. package/src/routes/management/tests/management.spec.ts +13 -0
  186. package/src/server.ts +1 -1
  187. package/src/types.ts +2 -1
  188. package/static/docs/swagger.json +57 -11
  189. package/static/docs/swagger.min.json +56 -10
  190. package/static/function/client.js +4155 -3350
  191. package/extensions/ublock/_locales/ku/messages.json +0 -1294
@@ -0,0 +1,1405 @@
1
+ /*******************************************************************************
2
+
3
+ uBlock Origin - a browser extension to block requests.
4
+ Copyright (C) 2024-present Raymond Hill
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
18
+
19
+ Home: https://github.com/gorhill/uBlock
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ /*******************************************************************************
25
+ *
26
+ * Structured-Cloneable to Unicode-Only SERIALIZER
27
+ *
28
+ * Purpose:
29
+ *
30
+ * Serialize/deserialize arbitrary JS data to/from well-formed Unicode strings.
31
+ *
32
+ * The browser does not expose an API to serialize structured-cloneable types
33
+ * into a single string. JSON.stringify() does not support complex JavaScript
34
+ * objects, and does not support references to composite types. Unless the
35
+ * data to serialize is only JS strings, it is difficult to easily switch
36
+ * from one type of storage to another.
37
+ *
38
+ * Serializing to a well-formed Unicode string allows to store structured-
39
+ * cloneable data to any storage. Not all storages support storing binary data,
40
+ * but all storages support storing Unicode strings.
41
+ *
42
+ * Structured-cloneable types:
43
+ * https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types
44
+ *
45
+ * ----------------+------------------+------------------+----------------------
46
+ * Data types | String | JSONable | structured-cloneable
47
+ * ================+============================================================
48
+ * document.cookie | Yes | No | No
49
+ * ----------------+------------------+------------------+----------------------
50
+ * localStorage | Yes | No | No
51
+ * ----------------+------------------+------------------+----------------------
52
+ * IndexedDB | Yes | Yes | Yes
53
+ * ----------------+------------------+------------------+----------------------
54
+ * browser.storage | Yes | Yes | No
55
+ * ----------------+------------------+------------------+----------------------
56
+ * Cache API | Yes | No | No
57
+ * ----------------+------------------+------------------+----------------------
58
+ *
59
+ * The above table shows that only JS strings can be persisted natively to all
60
+ * types of storage. The purpose of this library is to convert
61
+ * structure-cloneable data (which is a superset of JSONable data) into a
62
+ * single JS string. The resulting string is meant to be as small as possible.
63
+ * As a result, it is not human-readable, though it contains only printable
64
+ * ASCII characters -- and possibly Unicode characters beyond ASCII.
65
+ *
66
+ * The resulting JS string will not contain characters which require escaping
67
+ * should it be converted to a JSON value. However it may contain characters
68
+ * which require escaping should it be converted to a URI component.
69
+ *
70
+ * Characteristics:
71
+ *
72
+ * - Serializes/deserializes data to/from a single well-formed Unicode string
73
+ * - Strings do not require escaping, i.e. they are stored as-is
74
+ * - Supports multiple references to same object
75
+ * - Supports reference cycles
76
+ * - Supports synchronous and asynchronous API
77
+ * - Supports usage of Worker
78
+ * - Optionally supports LZ4 compression
79
+ *
80
+ * TODO:
81
+ *
82
+ * - Harden against unexpected conditions, such as corrupted string during
83
+ * deserialization.
84
+ * - Evaluate supporting checksum.
85
+ *
86
+ * */
87
+
88
+ const VERSION = 1;
89
+ const SEPARATORCHAR = ' ';
90
+ const SEPARATORCHARCODE = SEPARATORCHAR.charCodeAt(0);
91
+ const SENTINELCHAR = '!';
92
+ const SENTINELCHARCODE = SENTINELCHAR.charCodeAt(0);
93
+ const MAGICPREFIX = `UOSC_${VERSION}${SEPARATORCHAR}`;
94
+ const MAGICLZ4PREFIX = `UOSC/lz4_${VERSION}${SEPARATORCHAR}`;
95
+ const FAILMARK = Number.MAX_SAFE_INTEGER;
96
+ // Avoid characters which require escaping when serialized to JSON:
97
+ const SAFECHARS = "&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~";
98
+ const NUMSAFECHARS = SAFECHARS.length;
99
+ const BITS_PER_SAFECHARS = Math.log2(NUMSAFECHARS);
100
+
101
+ const { intToChar, intToCharCode, charCodeToInt } = (( ) => {
102
+ const intToChar = [];
103
+ const intToCharCode = [];
104
+ const charCodeToInt = [];
105
+ for ( let i = 0; i < NUMSAFECHARS; i++ ) {
106
+ intToChar[i] = SAFECHARS.charAt(i);
107
+ intToCharCode[i] = SAFECHARS.charCodeAt(i);
108
+ charCodeToInt[i] = 0;
109
+ }
110
+ for ( let i = NUMSAFECHARS; i < 128; i++ ) {
111
+ intToChar[i] = '';
112
+ intToCharCode[i] = 0;
113
+ charCodeToInt[i] = 0;
114
+ }
115
+ for ( let i = 0; i < SAFECHARS.length; i++ ) {
116
+ charCodeToInt[SAFECHARS.charCodeAt(i)] = i;
117
+ }
118
+ return { intToChar, intToCharCode, charCodeToInt };
119
+ })();
120
+
121
+ let iota = 1;
122
+ const I_STRING_SMALL = iota++;
123
+ const I_STRING_LARGE = iota++;
124
+ const I_ZERO = iota++;
125
+ const I_INTEGER_SMALL_POS = iota++;
126
+ const I_INTEGER_SMALL_NEG = iota++;
127
+ const I_INTEGER_LARGE_POS = iota++;
128
+ const I_INTEGER_LARGE_NEG = iota++;
129
+ const I_BOOL_FALSE = iota++;
130
+ const I_BOOL_TRUE = iota++;
131
+ const I_NULL = iota++;
132
+ const I_UNDEFINED = iota++;
133
+ const I_FLOAT = iota++;
134
+ const I_REGEXP = iota++;
135
+ const I_DATE = iota++;
136
+ const I_REFERENCE = iota++;
137
+ const I_OBJECT_SMALL = iota++;
138
+ const I_OBJECT_LARGE = iota++;
139
+ const I_ARRAY_SMALL = iota++;
140
+ const I_ARRAY_LARGE = iota++;
141
+ const I_SET_SMALL = iota++;
142
+ const I_SET_LARGE = iota++;
143
+ const I_MAP_SMALL = iota++;
144
+ const I_MAP_LARGE = iota++;
145
+ const I_ARRAYBUFFER = iota++;
146
+ const I_INT8ARRAY = iota++;
147
+ const I_UINT8ARRAY = iota++;
148
+ const I_UINT8CLAMPEDARRAY = iota++;
149
+ const I_INT16ARRAY = iota++;
150
+ const I_UINT16ARRAY = iota++;
151
+ const I_INT32ARRAY = iota++;
152
+ const I_UINT32ARRAY = iota++;
153
+ const I_FLOAT32ARRAY = iota++;
154
+ const I_FLOAT64ARRAY = iota++;
155
+ const I_DATAVIEW = iota++;
156
+
157
+ const C_STRING_SMALL = intToChar[I_STRING_SMALL];
158
+ const C_STRING_LARGE = intToChar[I_STRING_LARGE];
159
+ const C_ZERO = intToChar[I_ZERO];
160
+ const C_INTEGER_SMALL_POS = intToChar[I_INTEGER_SMALL_POS];
161
+ const C_INTEGER_SMALL_NEG = intToChar[I_INTEGER_SMALL_NEG];
162
+ const C_INTEGER_LARGE_POS = intToChar[I_INTEGER_LARGE_POS];
163
+ const C_INTEGER_LARGE_NEG = intToChar[I_INTEGER_LARGE_NEG];
164
+ const C_BOOL_FALSE = intToChar[I_BOOL_FALSE];
165
+ const C_BOOL_TRUE = intToChar[I_BOOL_TRUE];
166
+ const C_NULL = intToChar[I_NULL];
167
+ const C_UNDEFINED = intToChar[I_UNDEFINED];
168
+ const C_FLOAT = intToChar[I_FLOAT];
169
+ const C_REGEXP = intToChar[I_REGEXP];
170
+ const C_DATE = intToChar[I_DATE];
171
+ const C_REFERENCE = intToChar[I_REFERENCE];
172
+ const C_OBJECT_SMALL = intToChar[I_OBJECT_SMALL];
173
+ const C_OBJECT_LARGE = intToChar[I_OBJECT_LARGE];
174
+ const C_ARRAY_SMALL = intToChar[I_ARRAY_SMALL];
175
+ const C_ARRAY_LARGE = intToChar[I_ARRAY_LARGE];
176
+ const C_SET_SMALL = intToChar[I_SET_SMALL];
177
+ const C_SET_LARGE = intToChar[I_SET_LARGE];
178
+ const C_MAP_SMALL = intToChar[I_MAP_SMALL];
179
+ const C_MAP_LARGE = intToChar[I_MAP_LARGE];
180
+ const C_ARRAYBUFFER = intToChar[I_ARRAYBUFFER];
181
+ const C_INT8ARRAY = intToChar[I_INT8ARRAY];
182
+ const C_UINT8ARRAY = intToChar[I_UINT8ARRAY];
183
+ const C_UINT8CLAMPEDARRAY = intToChar[I_UINT8CLAMPEDARRAY];
184
+ const C_INT16ARRAY = intToChar[I_INT16ARRAY];
185
+ const C_UINT16ARRAY = intToChar[I_UINT16ARRAY];
186
+ const C_INT32ARRAY = intToChar[I_INT32ARRAY];
187
+ const C_UINT32ARRAY = intToChar[I_UINT32ARRAY];
188
+ const C_FLOAT32ARRAY = intToChar[I_FLOAT32ARRAY];
189
+ const C_FLOAT64ARRAY = intToChar[I_FLOAT64ARRAY];
190
+ const C_DATAVIEW = intToChar[I_DATAVIEW];
191
+
192
+ // Just reuse already defined constants, we just need distinct values
193
+ const I_STRING = I_STRING_SMALL;
194
+ const I_NUMBER = I_FLOAT;
195
+ const I_BOOL = I_BOOL_FALSE;
196
+ const I_OBJECT = I_OBJECT_SMALL;
197
+ const I_ARRAY = I_ARRAY_SMALL;
198
+ const I_SET = I_SET_SMALL;
199
+ const I_MAP = I_MAP_SMALL;
200
+
201
+ const typeToSerializedInt = {
202
+ 'string': I_STRING,
203
+ 'number': I_NUMBER,
204
+ 'boolean': I_BOOL,
205
+ 'object': I_OBJECT,
206
+ };
207
+
208
+ const xtypeToSerializedInt = {
209
+ '[object RegExp]': I_REGEXP,
210
+ '[object Date]': I_DATE,
211
+ '[object Array]': I_ARRAY,
212
+ '[object Set]': I_SET,
213
+ '[object Map]': I_MAP,
214
+ '[object ArrayBuffer]': I_ARRAYBUFFER,
215
+ '[object Int8Array]': I_INT8ARRAY,
216
+ '[object Uint8Array]': I_UINT8ARRAY,
217
+ '[object Uint8ClampedArray]': I_UINT8CLAMPEDARRAY,
218
+ '[object Int16Array]': I_INT16ARRAY,
219
+ '[object Uint16Array]': I_UINT16ARRAY,
220
+ '[object Int32Array]': I_INT32ARRAY,
221
+ '[object Uint32Array]': I_UINT32ARRAY,
222
+ '[object Float32Array]': I_FLOAT32ARRAY,
223
+ '[object Float64Array]': I_FLOAT64ARRAY,
224
+ '[object DataView]': I_DATAVIEW,
225
+ };
226
+
227
+ const xtypeToSerializedChar = {
228
+ '[object Int8Array]': C_INT8ARRAY,
229
+ '[object Uint8Array]': C_UINT8ARRAY,
230
+ '[object Uint8ClampedArray]': C_UINT8CLAMPEDARRAY,
231
+ '[object Int16Array]': C_INT16ARRAY,
232
+ '[object Uint16Array]': C_UINT16ARRAY,
233
+ '[object Int32Array]': C_INT32ARRAY,
234
+ '[object Uint32Array]': C_UINT32ARRAY,
235
+ '[object Float32Array]': C_FLOAT32ARRAY,
236
+ '[object Float64Array]': C_FLOAT64ARRAY,
237
+ };
238
+
239
+ const toArrayBufferViewConstructor = {
240
+ [`${I_INT8ARRAY}`]: Int8Array,
241
+ [`${I_UINT8ARRAY}`]: Uint8Array,
242
+ [`${I_UINT8CLAMPEDARRAY}`]: Uint8ClampedArray,
243
+ [`${I_INT16ARRAY}`]: Int16Array,
244
+ [`${I_UINT16ARRAY}`]: Uint16Array,
245
+ [`${I_INT32ARRAY}`]: Int32Array,
246
+ [`${I_UINT32ARRAY}`]: Uint32Array,
247
+ [`${I_FLOAT32ARRAY}`]: Float32Array,
248
+ [`${I_FLOAT64ARRAY}`]: Float64Array,
249
+ [`${I_DATAVIEW}`]: DataView,
250
+ };
251
+
252
+ /******************************************************************************/
253
+
254
+ const textDecoder = new TextDecoder();
255
+ const textEncoder = new TextEncoder();
256
+ const isInteger = Number.isInteger;
257
+
258
+ const writeRefs = new Map();
259
+ const writeBuffer = [];
260
+
261
+ const readRefs = new Map();
262
+ let readStr = '';
263
+ let readPtr = 0;
264
+ let readEnd = 0;
265
+
266
+ let refCounter = 1;
267
+
268
+ let uint8Input = null;
269
+
270
+ const uint8InputFromAsciiStr = s => {
271
+ if ( uint8Input === null || uint8Input.length < s.length ) {
272
+ uint8Input = new Uint8Array(s.length + 0x03FF & ~0x03FF);
273
+ }
274
+ textEncoder.encodeInto(s, uint8Input);
275
+ return uint8Input;
276
+ };
277
+
278
+ const isInstanceOf = (o, s) => {
279
+ return typeof o === 'object' && o !== null && (
280
+ s === 'Object' || Object.prototype.toString.call(o) === `[object ${s}]`
281
+ );
282
+ };
283
+
284
+ const shouldCompress = (s, options) =>
285
+ options.compress === true && (
286
+ options.compressThreshold === undefined ||
287
+ options.compressThreshold <= s.length
288
+ );
289
+
290
+ /*******************************************************************************
291
+ *
292
+ * A large Uint is always a positive integer (can be zero), assumed to be
293
+ * large, i.e. > NUMSAFECHARS -- but not necessarily. The serialized value has
294
+ * always at least one digit, and is always followed by a separator.
295
+ *
296
+ * */
297
+
298
+ const strFromLargeUint = i => {
299
+ let r = 0, s = '';
300
+ for (;;) {
301
+ r = i % NUMSAFECHARS;
302
+ s += intToChar[r];
303
+ i -= r;
304
+ if ( i === 0 ) { break; }
305
+ i /= NUMSAFECHARS;
306
+ }
307
+ return s + SEPARATORCHAR;
308
+ };
309
+
310
+ const deserializeLargeUint = ( ) => {
311
+ let c = readStr.charCodeAt(readPtr++);
312
+ let n = charCodeToInt[c];
313
+ let m = 1;
314
+ while ( (c = readStr.charCodeAt(readPtr++)) !== SEPARATORCHARCODE ) {
315
+ m *= NUMSAFECHARS;
316
+ n += m * charCodeToInt[c];
317
+ }
318
+ return n;
319
+ };
320
+
321
+ /*******************************************************************************
322
+ *
323
+ * Methods specific to ArrayBuffer objects to serialize optimally according to
324
+ * the content of the buffer.
325
+ *
326
+ * In sparse mode, number of output bytes per input int32 (4-byte) value:
327
+ * [v === zero]: 1 byte (separator)
328
+ * [v !== zero]: n digits + 1 byte (separator)
329
+ *
330
+ * */
331
+
332
+ const sparseValueLen = v => v !== 0
333
+ ? (Math.log2(v) / BITS_PER_SAFECHARS | 0) + 2
334
+ : 1;
335
+
336
+ const analyzeArrayBuffer = arrbuf => {
337
+ const byteLength = arrbuf.byteLength;
338
+ const uint32len = byteLength >>> 2;
339
+ const uint32arr = new Uint32Array(arrbuf, 0, uint32len);
340
+ let notzeroCount = 0;
341
+ for ( let i = uint32len-1; i >= 0; i-- ) {
342
+ if ( uint32arr[i] === 0 ) { continue; }
343
+ notzeroCount = i + 1;
344
+ break;
345
+ }
346
+ const end = notzeroCount + 1 <= uint32len ? notzeroCount << 2 : byteLength;
347
+ const endUint32 = end >>> 2;
348
+ const remUint8 = end & 0b11;
349
+ const denseSize = endUint32 * 5 + (remUint8 ? remUint8 + 1 : 0);
350
+ let sparseSize = 0;
351
+ for ( let i = 0; i < endUint32; i++ ) {
352
+ sparseSize += sparseValueLen(uint32arr[i]);
353
+ if ( sparseSize > denseSize ) {
354
+ return { end, dense: true, denseSize };
355
+ }
356
+ }
357
+ if ( remUint8 !== 0 ) {
358
+ sparseSize += 1; // sentinel
359
+ const uint8arr = new Uint8Array(arrbuf, endUint32 << 2);
360
+ for ( let i = 0; i < remUint8; i++ ) {
361
+ sparseSize += sparseValueLen(uint8arr[i]);
362
+ }
363
+ }
364
+ return { end, dense: false, sparseSize };
365
+ };
366
+
367
+ const denseArrayBufferToStr = (arrbuf, details) => {
368
+ const end = details.end;
369
+ const m = end % 4;
370
+ const n = end - m;
371
+ const uin32len = n >>> 2;
372
+ const uint32arr = new Uint32Array(arrbuf, 0, uin32len);
373
+ const output = new Uint8Array(details.denseSize);
374
+ let j = 0, v = 0;
375
+ for ( let i = 0; i < uin32len; i++ ) {
376
+ v = uint32arr[i];
377
+ output[j+0] = intToCharCode[v % NUMSAFECHARS];
378
+ v = v / NUMSAFECHARS | 0;
379
+ output[j+1] = intToCharCode[v % NUMSAFECHARS];
380
+ v = v / NUMSAFECHARS | 0;
381
+ output[j+2] = intToCharCode[v % NUMSAFECHARS];
382
+ v = v / NUMSAFECHARS | 0;
383
+ output[j+3] = intToCharCode[v % NUMSAFECHARS];
384
+ v = v / NUMSAFECHARS | 0;
385
+ output[j+4] = intToCharCode[v];
386
+ j += 5;
387
+ }
388
+ if ( m !== 0 ) {
389
+ const uint8arr = new Uint8Array(arrbuf, n);
390
+ v = uint8arr[0];
391
+ if ( m > 1 ) {
392
+ v += uint8arr[1] << 8;
393
+ if ( m > 2 ) {
394
+ v += uint8arr[2] << 16;
395
+ }
396
+ }
397
+ output[j+0] = intToCharCode[v % NUMSAFECHARS];
398
+ v = v / NUMSAFECHARS | 0;
399
+ output[j+1] = intToCharCode[v % NUMSAFECHARS];
400
+ if ( m > 1 ) {
401
+ v = v / NUMSAFECHARS | 0;
402
+ output[j+2] = intToCharCode[v % NUMSAFECHARS];
403
+ if ( m > 2 ) {
404
+ v = v / NUMSAFECHARS | 0;
405
+ output[j+3] = intToCharCode[v % NUMSAFECHARS];
406
+ }
407
+ }
408
+ }
409
+ return textDecoder.decode(output);
410
+ };
411
+
412
+ const BASE88_POW1 = NUMSAFECHARS;
413
+ const BASE88_POW2 = NUMSAFECHARS * BASE88_POW1;
414
+ const BASE88_POW3 = NUMSAFECHARS * BASE88_POW2;
415
+ const BASE88_POW4 = NUMSAFECHARS * BASE88_POW3;
416
+
417
+ const denseArrayBufferFromStr = (denseStr, arrbuf) => {
418
+ const input = uint8InputFromAsciiStr(denseStr);
419
+ const end = denseStr.length;
420
+ const m = end % 5;
421
+ const n = end - m;
422
+ const uin32len = n / 5 * 4 >>> 2;
423
+ const uint32arr = new Uint32Array(arrbuf, 0, uin32len);
424
+ let j = 0, v = 0;
425
+ for ( let i = 0; i < n; i += 5 ) {
426
+ v = charCodeToInt[input[i+0]];
427
+ v += charCodeToInt[input[i+1]] * BASE88_POW1;
428
+ v += charCodeToInt[input[i+2]] * BASE88_POW2;
429
+ v += charCodeToInt[input[i+3]] * BASE88_POW3;
430
+ v += charCodeToInt[input[i+4]] * BASE88_POW4;
431
+ uint32arr[j++] = v;
432
+ }
433
+ if ( m === 0 ) { return; }
434
+ v = charCodeToInt[input[n+0]] +
435
+ charCodeToInt[input[n+1]] * BASE88_POW1;
436
+ if ( m > 2 ) {
437
+ v += charCodeToInt[input[n+2]] * BASE88_POW2;
438
+ if ( m > 3 ) {
439
+ v += charCodeToInt[input[n+3]] * BASE88_POW3;
440
+ }
441
+ }
442
+ const uint8arr = new Uint8Array(arrbuf, j << 2);
443
+ uint8arr[0] = v & 255;
444
+ if ( v !== 0 ) {
445
+ v >>>= 8;
446
+ uint8arr[1] = v & 255;
447
+ if ( v !== 0 ) {
448
+ v >>>= 8;
449
+ uint8arr[2] = v & 255;
450
+ }
451
+ }
452
+ };
453
+
454
+ const sparseArrayBufferToStr = (arrbuf, details) => {
455
+ const end = details.end;
456
+ const uint8out = new Uint8Array(details.sparseSize);
457
+ const uint32len = end >>> 2;
458
+ const uint32arr = new Uint32Array(arrbuf, 0, uint32len);
459
+ let j = 0, n = 0, r = 0;
460
+ for ( let i = 0; i < uint32len; i++ ) {
461
+ n = uint32arr[i];
462
+ if ( n !== 0 ) {
463
+ for (;;) {
464
+ r = n % NUMSAFECHARS;
465
+ uint8out[j++] = intToCharCode[r];
466
+ n -= r;
467
+ if ( n === 0 ) { break; }
468
+ n /= NUMSAFECHARS;
469
+ }
470
+ }
471
+ uint8out[j++] = SEPARATORCHARCODE;
472
+ }
473
+ const uint8rem = end & 0b11;
474
+ if ( uint8rem !== 0 ) {
475
+ uint8out[j++] = SENTINELCHARCODE;
476
+ const uint8arr = new Uint8Array(arrbuf, end - uint8rem, uint8rem);
477
+ for ( let i = 0; i < uint8rem; i++ ) {
478
+ n = uint8arr[i];
479
+ if ( n !== 0 ) {
480
+ for (;;) {
481
+ r = n % NUMSAFECHARS;
482
+ uint8out[j++] = intToCharCode[r];
483
+ n -= r;
484
+ if ( n === 0 ) { break; }
485
+ n /= NUMSAFECHARS;
486
+ }
487
+ }
488
+ uint8out[j++] = SEPARATORCHARCODE;
489
+ }
490
+ }
491
+ return textDecoder.decode(uint8out);
492
+ };
493
+
494
+ const sparseArrayBufferFromStr = (sparseStr, arrbuf) => {
495
+ const sparseLen = sparseStr.length;
496
+ const input = uint8InputFromAsciiStr(sparseStr);
497
+ const end = arrbuf.byteLength;
498
+ const uint32len = end >>> 2;
499
+ const uint32arr = new Uint32Array(arrbuf, 0, uint32len);
500
+ let i = 0, j = 0, c = 0, n = 0, m = 0;
501
+ for ( ; j < sparseLen; i++ ) {
502
+ c = input[j++];
503
+ if ( c === SEPARATORCHARCODE ) { continue; }
504
+ if ( c === SENTINELCHARCODE ) { break; }
505
+ n = charCodeToInt[c];
506
+ m = 1;
507
+ for (;;) {
508
+ c = input[j++];
509
+ if ( c === SEPARATORCHARCODE ) { break; }
510
+ m *= NUMSAFECHARS;
511
+ n += m * charCodeToInt[c];
512
+ }
513
+ uint32arr[i] = n;
514
+ }
515
+ if ( c === SENTINELCHARCODE ) {
516
+ i <<= 2;
517
+ const uint8arr = new Uint8Array(arrbuf, i);
518
+ for ( ; j < sparseLen; i++ ) {
519
+ c = input[j++];
520
+ if ( c === SEPARATORCHARCODE ) { continue; }
521
+ n = charCodeToInt[c];
522
+ m = 1;
523
+ for (;;) {
524
+ c = input[j++];
525
+ if ( c === SEPARATORCHARCODE ) { break; }
526
+ m *= NUMSAFECHARS;
527
+ n += m * charCodeToInt[c];
528
+ }
529
+ uint8arr[i] = n;
530
+ }
531
+ }
532
+ };
533
+
534
+ /******************************************************************************/
535
+
536
+ const _serialize = data => {
537
+ // Primitive types
538
+ if ( data === 0 ) {
539
+ writeBuffer.push(C_ZERO);
540
+ return;
541
+ }
542
+ if ( data === null ) {
543
+ writeBuffer.push(C_NULL);
544
+ return;
545
+ }
546
+ if ( data === undefined ) {
547
+ writeBuffer.push(C_UNDEFINED);
548
+ return;
549
+ }
550
+ // Type name
551
+ switch ( typeToSerializedInt[typeof data] ) {
552
+ case I_STRING: {
553
+ const length = data.length;
554
+ if ( length < NUMSAFECHARS ) {
555
+ writeBuffer.push(C_STRING_SMALL + intToChar[length], data);
556
+ } else {
557
+ writeBuffer.push(C_STRING_LARGE + strFromLargeUint(length), data);
558
+ }
559
+ return;
560
+ }
561
+ case I_NUMBER:
562
+ if ( isInteger(data) ) {
563
+ if ( data >= NUMSAFECHARS ) {
564
+ writeBuffer.push(C_INTEGER_LARGE_POS + strFromLargeUint(data));
565
+ } else if ( data > 0 ) {
566
+ writeBuffer.push(C_INTEGER_SMALL_POS + intToChar[data]);
567
+ } else if ( data > -NUMSAFECHARS ) {
568
+ writeBuffer.push(C_INTEGER_SMALL_NEG + intToChar[-data]);
569
+ } else {
570
+ writeBuffer.push(C_INTEGER_LARGE_NEG + strFromLargeUint(-data));
571
+ }
572
+ } else {
573
+ const s = `${data}`;
574
+ writeBuffer.push(C_FLOAT + strFromLargeUint(s.length) + s);
575
+ }
576
+ return;
577
+ case I_BOOL:
578
+ writeBuffer.push(data ? C_BOOL_TRUE : C_BOOL_FALSE);
579
+ return;
580
+ case I_OBJECT:
581
+ break;
582
+ default:
583
+ return;
584
+ }
585
+ const xtypeName = Object.prototype.toString.call(data);
586
+ const xtypeInt = xtypeToSerializedInt[xtypeName];
587
+ if ( xtypeInt === I_REGEXP ) {
588
+ writeBuffer.push(C_REGEXP);
589
+ _serialize(data.source);
590
+ _serialize(data.flags);
591
+ return;
592
+ }
593
+ if ( xtypeInt === I_DATE ) {
594
+ writeBuffer.push(C_DATE + _serialize(data.getTime()));
595
+ return;
596
+ }
597
+ // Reference to composite types
598
+ const ref = writeRefs.get(data);
599
+ if ( ref !== undefined ) {
600
+ writeBuffer.push(C_REFERENCE + strFromLargeUint(ref));
601
+ return;
602
+ }
603
+ // Remember reference
604
+ writeRefs.set(data, refCounter++);
605
+ // Extended type name
606
+ switch ( xtypeInt ) {
607
+ case I_ARRAY: {
608
+ const size = data.length;
609
+ if ( size < NUMSAFECHARS ) {
610
+ writeBuffer.push(C_ARRAY_SMALL + intToChar[size]);
611
+ } else {
612
+ writeBuffer.push(C_ARRAY_LARGE + strFromLargeUint(size));
613
+ }
614
+ for ( const v of data ) {
615
+ _serialize(v);
616
+ }
617
+ return;
618
+ }
619
+ case I_SET: {
620
+ const size = data.size;
621
+ if ( size < NUMSAFECHARS ) {
622
+ writeBuffer.push(C_SET_SMALL + intToChar[size]);
623
+ } else {
624
+ writeBuffer.push(C_SET_LARGE + strFromLargeUint(size));
625
+ }
626
+ for ( const v of data ) {
627
+ _serialize(v);
628
+ }
629
+ return;
630
+ }
631
+ case I_MAP: {
632
+ const size = data.size;
633
+ if ( size < NUMSAFECHARS ) {
634
+ writeBuffer.push(C_MAP_SMALL + intToChar[size]);
635
+ } else {
636
+ writeBuffer.push(C_MAP_LARGE + strFromLargeUint(size));
637
+ }
638
+ for ( const [ k, v ] of data ) {
639
+ _serialize(k);
640
+ _serialize(v);
641
+ }
642
+ return;
643
+ }
644
+ case I_ARRAYBUFFER: {
645
+ const byteLength = data.byteLength;
646
+ writeBuffer.push(C_ARRAYBUFFER + strFromLargeUint(byteLength));
647
+ _serialize(data.maxByteLength);
648
+ const arrbuffDetails = analyzeArrayBuffer(data);
649
+ _serialize(arrbuffDetails.dense);
650
+ const str = arrbuffDetails.dense
651
+ ? denseArrayBufferToStr(data, arrbuffDetails)
652
+ : sparseArrayBufferToStr(data, arrbuffDetails);
653
+ _serialize(str);
654
+ //console.log(`arrbuf size=${byteLength} content size=${arrbuffDetails.end} dense=${arrbuffDetails.dense} array size=${arrbuffDetails.dense ? arrbuffDetails.denseSize : arrbuffDetails.sparseSize} serialized size=${str.length}`);
655
+ return;
656
+ }
657
+ case I_INT8ARRAY:
658
+ case I_UINT8ARRAY:
659
+ case I_UINT8CLAMPEDARRAY:
660
+ case I_INT16ARRAY:
661
+ case I_UINT16ARRAY:
662
+ case I_INT32ARRAY:
663
+ case I_UINT32ARRAY:
664
+ case I_FLOAT32ARRAY:
665
+ case I_FLOAT64ARRAY:
666
+ writeBuffer.push(
667
+ xtypeToSerializedChar[xtypeName],
668
+ strFromLargeUint(data.byteOffset),
669
+ strFromLargeUint(data.length)
670
+ );
671
+ _serialize(data.buffer);
672
+ return;
673
+ case I_DATAVIEW:
674
+ writeBuffer.push(C_DATAVIEW, strFromLargeUint(data.byteOffset), strFromLargeUint(data.byteLength));
675
+ _serialize(data.buffer);
676
+ return;
677
+ default: {
678
+ const keys = Object.keys(data);
679
+ const size = keys.length;
680
+ if ( size < NUMSAFECHARS ) {
681
+ writeBuffer.push(C_OBJECT_SMALL + intToChar[size]);
682
+ } else {
683
+ writeBuffer.push(C_OBJECT_LARGE + strFromLargeUint(size));
684
+ }
685
+ for ( const key of keys ) {
686
+ _serialize(key);
687
+ _serialize(data[key]);
688
+ }
689
+ break;
690
+ }
691
+ }
692
+ };
693
+
694
+ /******************************************************************************/
695
+
696
+ const _deserialize = ( ) => {
697
+ if ( readPtr >= readEnd ) { return; }
698
+ const type = charCodeToInt[readStr.charCodeAt(readPtr++)];
699
+ switch ( type ) {
700
+ // Primitive types
701
+ case I_STRING_SMALL:
702
+ case I_STRING_LARGE: {
703
+ const size = type === I_STRING_SMALL
704
+ ? charCodeToInt[readStr.charCodeAt(readPtr++)]
705
+ : deserializeLargeUint();
706
+ const beg = readPtr;
707
+ readPtr += size;
708
+ return readStr.slice(beg, readPtr);
709
+ }
710
+ case I_ZERO:
711
+ return 0;
712
+ case I_INTEGER_SMALL_POS:
713
+ return charCodeToInt[readStr.charCodeAt(readPtr++)];
714
+ case I_INTEGER_SMALL_NEG:
715
+ return -charCodeToInt[readStr.charCodeAt(readPtr++)];
716
+ case I_INTEGER_LARGE_POS:
717
+ return deserializeLargeUint();
718
+ case I_INTEGER_LARGE_NEG:
719
+ return -deserializeLargeUint();
720
+ case I_BOOL_FALSE:
721
+ return false;
722
+ case I_BOOL_TRUE:
723
+ return true;
724
+ case I_NULL:
725
+ return null;
726
+ case I_UNDEFINED:
727
+ return;
728
+ case I_FLOAT: {
729
+ const size = deserializeLargeUint();
730
+ const beg = readPtr;
731
+ readPtr += size;
732
+ return parseFloat(readStr.slice(beg, readPtr));
733
+ }
734
+ case I_REGEXP: {
735
+ const source = _deserialize();
736
+ const flags = _deserialize();
737
+ return new RegExp(source, flags);
738
+ }
739
+ case I_DATE: {
740
+ const time = _deserialize();
741
+ return new Date(time);
742
+ }
743
+ case I_REFERENCE: {
744
+ const ref = deserializeLargeUint();
745
+ return readRefs.get(ref);
746
+ }
747
+ case I_OBJECT_SMALL:
748
+ case I_OBJECT_LARGE: {
749
+ const entries = [];
750
+ const size = type === I_OBJECT_SMALL
751
+ ? charCodeToInt[readStr.charCodeAt(readPtr++)]
752
+ : deserializeLargeUint();
753
+ for ( let i = 0; i < size; i++ ) {
754
+ const k = _deserialize();
755
+ const v = _deserialize();
756
+ entries.push([ k, v ]);
757
+ }
758
+ const out = Object.fromEntries(entries);
759
+ readRefs.set(refCounter++, out);
760
+ return out;
761
+ }
762
+ case I_ARRAY_SMALL:
763
+ case I_ARRAY_LARGE: {
764
+ const out = [];
765
+ const size = type === I_ARRAY_SMALL
766
+ ? charCodeToInt[readStr.charCodeAt(readPtr++)]
767
+ : deserializeLargeUint();
768
+ for ( let i = 0; i < size; i++ ) {
769
+ out.push(_deserialize());
770
+ }
771
+ readRefs.set(refCounter++, out);
772
+ return out;
773
+ }
774
+ case I_SET_SMALL:
775
+ case I_SET_LARGE: {
776
+ const entries = [];
777
+ const size = type === I_SET_SMALL
778
+ ? charCodeToInt[readStr.charCodeAt(readPtr++)]
779
+ : deserializeLargeUint();
780
+ for ( let i = 0; i < size; i++ ) {
781
+ entries.push(_deserialize());
782
+ }
783
+ const out = new Set(entries);
784
+ readRefs.set(refCounter++, out);
785
+ return out;
786
+ }
787
+ case I_MAP_SMALL:
788
+ case I_MAP_LARGE: {
789
+ const entries = [];
790
+ const size = type === I_MAP_SMALL
791
+ ? charCodeToInt[readStr.charCodeAt(readPtr++)]
792
+ : deserializeLargeUint();
793
+ for ( let i = 0; i < size; i++ ) {
794
+ const k = _deserialize();
795
+ const v = _deserialize();
796
+ entries.push([ k, v ]);
797
+ }
798
+ const out = new Map(entries);
799
+ readRefs.set(refCounter++, out);
800
+ return out;
801
+ }
802
+ case I_ARRAYBUFFER: {
803
+ const byteLength = deserializeLargeUint();
804
+ const maxByteLength = _deserialize();
805
+ let options;
806
+ if ( maxByteLength !== 0 && maxByteLength !== byteLength ) {
807
+ options = { maxByteLength };
808
+ }
809
+ const arrbuf = new ArrayBuffer(byteLength, options);
810
+ const dense = _deserialize();
811
+ const str = _deserialize();
812
+ if ( dense ) {
813
+ denseArrayBufferFromStr(str, arrbuf);
814
+ } else {
815
+ sparseArrayBufferFromStr(str, arrbuf);
816
+ }
817
+ readRefs.set(refCounter++, arrbuf);
818
+ return arrbuf;
819
+ }
820
+ case I_INT8ARRAY:
821
+ case I_UINT8ARRAY:
822
+ case I_UINT8CLAMPEDARRAY:
823
+ case I_INT16ARRAY:
824
+ case I_UINT16ARRAY:
825
+ case I_INT32ARRAY:
826
+ case I_UINT32ARRAY:
827
+ case I_FLOAT32ARRAY:
828
+ case I_FLOAT64ARRAY:
829
+ case I_DATAVIEW: {
830
+ const byteOffset = deserializeLargeUint();
831
+ const length = deserializeLargeUint();
832
+ const arrayBuffer = _deserialize();
833
+ const ctor = toArrayBufferViewConstructor[`${type}`];
834
+ const out = new ctor(arrayBuffer, byteOffset, length);
835
+ readRefs.set(refCounter++, out);
836
+ return out;
837
+ }
838
+ default:
839
+ break;
840
+ }
841
+ readPtr = FAILMARK;
842
+ };
843
+
844
+ /*******************************************************************************
845
+ *
846
+ * LZ4 block compression/decompression
847
+ *
848
+ * Imported from:
849
+ * https://github.com/gorhill/lz4-wasm/blob/8995cdef7b/dist/lz4-block-codec-js.js
850
+ *
851
+ * Customized to avoid external dependencies as I entertain the idea of
852
+ * spinning off the serializer as a standalone utility for all to use.
853
+ *
854
+ * */
855
+
856
+ class LZ4BlockJS {
857
+ constructor() {
858
+ this.hashTable = undefined;
859
+ this.outputBuffer = undefined;
860
+ }
861
+ reset() {
862
+ this.hashTable = undefined;
863
+ this.outputBuffer = undefined;
864
+ }
865
+ growOutputBuffer(size) {
866
+ if ( this.outputBuffer !== undefined ) {
867
+ if ( this.outputBuffer.byteLength >= size ) { return; }
868
+ }
869
+ this.outputBuffer = new ArrayBuffer(size + 0xFFFF & 0x7FFF0000);
870
+ }
871
+ encodeBound(size) {
872
+ return size > 0x7E000000 ? 0 : size + (size / 255 | 0) + 16;
873
+ }
874
+ encodeBlock(iBuf, oOffset) {
875
+ const iLen = iBuf.byteLength;
876
+ if ( iLen >= 0x7E000000 ) { throw new RangeError(); }
877
+ // "The last match must start at least 12 bytes before end of block"
878
+ const lastMatchPos = iLen - 12;
879
+ // "The last 5 bytes are always literals"
880
+ const lastLiteralPos = iLen - 5;
881
+ if ( this.hashTable === undefined ) {
882
+ this.hashTable = new Int32Array(65536);
883
+ }
884
+ this.hashTable.fill(-65536);
885
+ if ( isInstanceOf(iBuf, 'ArrayBuffer') ) {
886
+ iBuf = new Uint8Array(iBuf);
887
+ }
888
+ const oLen = oOffset + this.encodeBound(iLen);
889
+ this.growOutputBuffer(oLen);
890
+ const oBuf = new Uint8Array(this.outputBuffer, 0, oLen);
891
+ let iPos = 0;
892
+ let oPos = oOffset;
893
+ let anchorPos = 0;
894
+ // sequence-finding loop
895
+ for (;;) {
896
+ let refPos;
897
+ let mOffset;
898
+ let sequence = iBuf[iPos] << 8 | iBuf[iPos+1] << 16 | iBuf[iPos+2] << 24;
899
+ // match-finding loop
900
+ while ( iPos <= lastMatchPos ) {
901
+ sequence = sequence >>> 8 | iBuf[iPos+3] << 24;
902
+ const hash = (sequence * 0x9E37 & 0xFFFF) + (sequence * 0x79B1 >>> 16) & 0xFFFF;
903
+ refPos = this.hashTable[hash];
904
+ this.hashTable[hash] = iPos;
905
+ mOffset = iPos - refPos;
906
+ if (
907
+ mOffset < 65536 &&
908
+ iBuf[refPos+0] === ((sequence ) & 0xFF) &&
909
+ iBuf[refPos+1] === ((sequence >>> 8) & 0xFF) &&
910
+ iBuf[refPos+2] === ((sequence >>> 16) & 0xFF) &&
911
+ iBuf[refPos+3] === ((sequence >>> 24) & 0xFF)
912
+ ) {
913
+ break;
914
+ }
915
+ iPos += 1;
916
+ }
917
+ // no match found
918
+ if ( iPos > lastMatchPos ) { break; }
919
+ // match found
920
+ let lLen = iPos - anchorPos;
921
+ let mLen = iPos;
922
+ iPos += 4; refPos += 4;
923
+ while ( iPos < lastLiteralPos && iBuf[iPos] === iBuf[refPos] ) {
924
+ iPos += 1; refPos += 1;
925
+ }
926
+ mLen = iPos - mLen;
927
+ const token = mLen < 19 ? mLen - 4 : 15;
928
+ // write token, length of literals if needed
929
+ if ( lLen >= 15 ) {
930
+ oBuf[oPos++] = 0xF0 | token;
931
+ let l = lLen - 15;
932
+ while ( l >= 255 ) {
933
+ oBuf[oPos++] = 255;
934
+ l -= 255;
935
+ }
936
+ oBuf[oPos++] = l;
937
+ } else {
938
+ oBuf[oPos++] = (lLen << 4) | token;
939
+ }
940
+ // write literals
941
+ while ( lLen-- ) {
942
+ oBuf[oPos++] = iBuf[anchorPos++];
943
+ }
944
+ if ( mLen === 0 ) { break; }
945
+ // write offset of match
946
+ oBuf[oPos+0] = mOffset;
947
+ oBuf[oPos+1] = mOffset >>> 8;
948
+ oPos += 2;
949
+ // write length of match if needed
950
+ if ( mLen >= 19 ) {
951
+ let l = mLen - 19;
952
+ while ( l >= 255 ) {
953
+ oBuf[oPos++] = 255;
954
+ l -= 255;
955
+ }
956
+ oBuf[oPos++] = l;
957
+ }
958
+ anchorPos = iPos;
959
+ }
960
+ // last sequence is literals only
961
+ let lLen = iLen - anchorPos;
962
+ if ( lLen >= 15 ) {
963
+ oBuf[oPos++] = 0xF0;
964
+ let l = lLen - 15;
965
+ while ( l >= 255 ) {
966
+ oBuf[oPos++] = 255;
967
+ l -= 255;
968
+ }
969
+ oBuf[oPos++] = l;
970
+ } else {
971
+ oBuf[oPos++] = lLen << 4;
972
+ }
973
+ while ( lLen-- ) {
974
+ oBuf[oPos++] = iBuf[anchorPos++];
975
+ }
976
+ return new Uint8Array(oBuf.buffer, 0, oPos);
977
+ }
978
+ decodeBlock(iBuf, iOffset, oLen) {
979
+ const iLen = iBuf.byteLength;
980
+ this.growOutputBuffer(oLen);
981
+ const oBuf = new Uint8Array(this.outputBuffer, 0, oLen);
982
+ let iPos = iOffset, oPos = 0;
983
+ while ( iPos < iLen ) {
984
+ const token = iBuf[iPos++];
985
+ // literals
986
+ let clen = token >>> 4;
987
+ // length of literals
988
+ if ( clen !== 0 ) {
989
+ if ( clen === 15 ) {
990
+ let l;
991
+ for (;;) {
992
+ l = iBuf[iPos++];
993
+ if ( l !== 255 ) { break; }
994
+ clen += 255;
995
+ }
996
+ clen += l;
997
+ }
998
+ // copy literals
999
+ const end = iPos + clen;
1000
+ while ( iPos < end ) {
1001
+ oBuf[oPos++] = iBuf[iPos++];
1002
+ }
1003
+ if ( iPos === iLen ) { break; }
1004
+ }
1005
+ // match
1006
+ const mOffset = iBuf[iPos+0] | (iBuf[iPos+1] << 8);
1007
+ if ( mOffset === 0 || mOffset > oPos ) { return; }
1008
+ iPos += 2;
1009
+ // length of match
1010
+ clen = (token & 0x0F) + 4;
1011
+ if ( clen === 19 ) {
1012
+ let l;
1013
+ for (;;) {
1014
+ l = iBuf[iPos++];
1015
+ if ( l !== 255 ) { break; }
1016
+ clen += 255;
1017
+ }
1018
+ clen += l;
1019
+ }
1020
+ // copy match
1021
+ const end = oPos + clen;
1022
+ let mPos = oPos - mOffset;
1023
+ while ( oPos < end ) {
1024
+ oBuf[oPos++] = oBuf[mPos++];
1025
+ }
1026
+ }
1027
+ return oBuf;
1028
+ }
1029
+ encode(input, outputOffset) {
1030
+ if ( isInstanceOf(input, 'ArrayBuffer') ) {
1031
+ input = new Uint8Array(input);
1032
+ } else if ( isInstanceOf(input, 'Uint8Array') === false ) {
1033
+ throw new TypeError();
1034
+ }
1035
+ return this.encodeBlock(input, outputOffset);
1036
+ }
1037
+ decode(input, inputOffset, outputSize) {
1038
+ if ( isInstanceOf(input, 'ArrayBuffer') ) {
1039
+ input = new Uint8Array(input);
1040
+ } else if ( isInstanceOf(input, 'Uint8Array') === false ) {
1041
+ throw new TypeError();
1042
+ }
1043
+ return this.decodeBlock(input, inputOffset, outputSize);
1044
+ }
1045
+ }
1046
+
1047
+ /*******************************************************************************
1048
+ *
1049
+ * Synchronous APIs
1050
+ *
1051
+ * */
1052
+
1053
+ export const serialize = (data, options = {}) => {
1054
+ refCounter = 1;
1055
+ _serialize(data);
1056
+ writeBuffer.unshift(MAGICPREFIX);
1057
+ const s = writeBuffer.join('');
1058
+ writeRefs.clear();
1059
+ writeBuffer.length = 0;
1060
+ if ( shouldCompress(s, options) === false ) { return s; }
1061
+ const lz4Util = new LZ4BlockJS();
1062
+ const uint8ArrayBefore = textEncoder.encode(s);
1063
+ const uint8ArrayAfter = lz4Util.encode(uint8ArrayBefore, 0);
1064
+ const lz4 = {
1065
+ size: uint8ArrayBefore.length,
1066
+ data: new Uint8Array(uint8ArrayAfter),
1067
+ };
1068
+ refCounter = 1;
1069
+ _serialize(lz4);
1070
+ writeBuffer.unshift(MAGICLZ4PREFIX);
1071
+ const t = writeBuffer.join('');
1072
+ writeRefs.clear();
1073
+ writeBuffer.length = 0;
1074
+ const ratio = t.length / s.length;
1075
+ return ratio <= 0.85 ? t : s;
1076
+ };
1077
+
1078
+ export const deserialize = s => {
1079
+ if ( s.startsWith(MAGICLZ4PREFIX) ) {
1080
+ refCounter = 1;
1081
+ readStr = s;
1082
+ readEnd = s.length;
1083
+ readPtr = MAGICLZ4PREFIX.length;
1084
+ const lz4 = _deserialize();
1085
+ readRefs.clear();
1086
+ readStr = '';
1087
+ const lz4Util = new LZ4BlockJS();
1088
+ const uint8ArrayAfter = lz4Util.decode(lz4.data, 0, lz4.size);
1089
+ s = textDecoder.decode(new Uint8Array(uint8ArrayAfter));
1090
+ }
1091
+ if ( s.startsWith(MAGICPREFIX) === false ) { return; }
1092
+ refCounter = 1;
1093
+ readStr = s;
1094
+ readEnd = s.length;
1095
+ readPtr = MAGICPREFIX.length;
1096
+ const data = _deserialize();
1097
+ readRefs.clear();
1098
+ readStr = '';
1099
+ uint8Input = null;
1100
+ if ( readPtr === FAILMARK ) { return; }
1101
+ return data;
1102
+ };
1103
+
1104
+ export const isSerialized = s =>
1105
+ typeof s === 'string' &&
1106
+ (s.startsWith(MAGICLZ4PREFIX) || s.startsWith(MAGICPREFIX));
1107
+
1108
+ export const isCompressed = s =>
1109
+ typeof s === 'string' && s.startsWith(MAGICLZ4PREFIX);
1110
+
1111
+ /*******************************************************************************
1112
+ *
1113
+ * Configuration
1114
+ *
1115
+ * */
1116
+
1117
+ const defaultConfig = {
1118
+ threadTTL: 3000,
1119
+ };
1120
+
1121
+ const validateConfig = {
1122
+ threadTTL: val => val > 0,
1123
+ };
1124
+
1125
+ const currentConfig = Object.assign({}, defaultConfig);
1126
+
1127
+ export const getConfig = ( ) => Object.assign({}, currentConfig);
1128
+
1129
+ export const setConfig = config => {
1130
+ for ( const key in Object.keys(config) ) {
1131
+ if ( defaultConfig.hasOwnProperty(key) === false ) { continue; }
1132
+ const val = config[key];
1133
+ if ( typeof val !== typeof defaultConfig[key] ) { continue; }
1134
+ if ( (validateConfig[key])(val) === false ) { continue; }
1135
+ currentConfig[key] = val;
1136
+ }
1137
+ };
1138
+
1139
+ /*******************************************************************************
1140
+ *
1141
+ * Asynchronous APIs
1142
+ *
1143
+ * Being asynchronous allows to support workers and future features such as
1144
+ * checksums.
1145
+ *
1146
+ * */
1147
+
1148
+ const THREAD_AREYOUREADY = 1;
1149
+ const THREAD_IAMREADY = 2;
1150
+ const THREAD_SERIALIZE = 3;
1151
+ const THREAD_DESERIALIZE = 4;
1152
+
1153
+ class MainThread {
1154
+ constructor() {
1155
+ this.name = 'main';
1156
+ this.jobs = [];
1157
+ this.workload = 0;
1158
+ this.timer = undefined;
1159
+ this.busy = 2;
1160
+ }
1161
+
1162
+ process() {
1163
+ if ( this.jobs.length === 0 ) { return; }
1164
+ const job = this.jobs.shift();
1165
+ this.workload -= job.size;
1166
+ const result = job.what === THREAD_SERIALIZE
1167
+ ? serialize(job.data, job.options)
1168
+ : deserialize(job.data);
1169
+ job.resolve(result);
1170
+ this.processAsync();
1171
+ if ( this.jobs.length === 0 ) {
1172
+ this.busy = 2;
1173
+ } else if ( this.busy > 2 ) {
1174
+ this.busy -= 1;
1175
+ }
1176
+ }
1177
+
1178
+ processAsync() {
1179
+ if ( this.timer !== undefined ) { return; }
1180
+ if ( this.jobs.length === 0 ) { return; }
1181
+ this.timer = globalThis.requestIdleCallback(deadline => {
1182
+ this.timer = undefined;
1183
+ globalThis.queueMicrotask(( ) => {
1184
+ this.process();
1185
+ });
1186
+ if ( deadline.timeRemaining() === 0 ) {
1187
+ this.busy += 1;
1188
+ }
1189
+ }, { timeout: 5 });
1190
+ }
1191
+
1192
+ serialize(data, options) {
1193
+ return new Promise(resolve => {
1194
+ this.workload += 1;
1195
+ this.jobs.push({ what: THREAD_SERIALIZE, data, options, size: 1, resolve });
1196
+ this.processAsync();
1197
+ });
1198
+ }
1199
+
1200
+ deserialize(data, options) {
1201
+ return new Promise(resolve => {
1202
+ const size = data.length;
1203
+ this.workload += size;
1204
+ this.jobs.push({ what: THREAD_DESERIALIZE, data, options, size, resolve });
1205
+ this.processAsync();
1206
+ });
1207
+ }
1208
+
1209
+ get queueSize() {
1210
+ return this.jobs.length;
1211
+ }
1212
+
1213
+ get workSize() {
1214
+ return this.workload * this.busy;
1215
+ }
1216
+ }
1217
+
1218
+ class Thread {
1219
+ constructor(gcer) {
1220
+ this.name = 'worker';
1221
+ this.jobs = new Map();
1222
+ this.jobIdGenerator = 1;
1223
+ this.workload = 0;
1224
+ this.workerAccessTime = 0;
1225
+ this.workerTimer = undefined;
1226
+ this.gcer = gcer;
1227
+ this.workerPromise = new Promise(resolve => {
1228
+ let worker = null;
1229
+ try {
1230
+ worker = new Worker('js/s14e-serializer.js', { type: 'module' });
1231
+ worker.onmessage = ev => {
1232
+ const msg = ev.data;
1233
+ if ( isInstanceOf(msg, 'Object') === false ) { return; }
1234
+ if ( msg.what === THREAD_IAMREADY ) {
1235
+ worker.onmessage = ev => { this.onmessage(ev); };
1236
+ worker.onerror = null;
1237
+ resolve(worker);
1238
+ }
1239
+ };
1240
+ worker.onerror = ( ) => {
1241
+ worker.onmessage = worker.onerror = null;
1242
+ resolve(null);
1243
+ };
1244
+ worker.postMessage({
1245
+ what: THREAD_AREYOUREADY,
1246
+ config: currentConfig,
1247
+ });
1248
+ } catch(ex) {
1249
+ console.info(ex);
1250
+ worker.onmessage = worker.onerror = null;
1251
+ resolve(null);
1252
+ }
1253
+ });
1254
+ }
1255
+
1256
+ countdownWorker() {
1257
+ if ( this.workerTimer !== undefined ) { return; }
1258
+ this.workerTimer = setTimeout(async ( ) => {
1259
+ this.workerTimer = undefined;
1260
+ if ( this.jobs.size !== 0 ) { return; }
1261
+ const idleTime = Date.now() - this.workerAccessTime;
1262
+ if ( idleTime < currentConfig.threadTTL ) {
1263
+ return this.countdownWorker();
1264
+ }
1265
+ const worker = await this.workerPromise;
1266
+ if ( this.jobs.size !== 0 ) { return; }
1267
+ this.gcer(this);
1268
+ if ( worker === null ) { return; }
1269
+ worker.onmessage = worker.onerror = null;
1270
+ worker.terminate();
1271
+ }, currentConfig.threadTTL);
1272
+ }
1273
+
1274
+ onmessage(ev) {
1275
+ this.ondone(ev.data);
1276
+ }
1277
+
1278
+ ondone(job) {
1279
+ const resolve = this.jobs.get(job.id);
1280
+ if ( resolve === undefined ) { return; }
1281
+ this.jobs.delete(job.id);
1282
+ resolve(job.result);
1283
+ this.workload -= job.size;
1284
+ if ( this.jobs.size !== 0 ) { return; }
1285
+ this.countdownWorker();
1286
+ }
1287
+
1288
+ async serialize(data, options) {
1289
+ return new Promise(resolve => {
1290
+ const id = this.jobIdGenerator++;
1291
+ this.workload += 1;
1292
+ this.jobs.set(id, resolve);
1293
+ return this.workerPromise.then(worker => {
1294
+ this.workerAccessTime = Date.now();
1295
+ if ( worker === null ) {
1296
+ this.ondone({ id, result: serialize(data, options), size: 1 });
1297
+ } else {
1298
+ worker.postMessage({ what: THREAD_SERIALIZE, id, data, options, size: 1 });
1299
+ }
1300
+ });
1301
+ });
1302
+ }
1303
+
1304
+ async deserialize(data, options) {
1305
+ return new Promise(resolve => {
1306
+ const id = this.jobIdGenerator++;
1307
+ const size = data.length;
1308
+ this.workload += size;
1309
+ this.jobs.set(id, resolve);
1310
+ return this.workerPromise.then(worker => {
1311
+ this.workerAccessTime = Date.now();
1312
+ if ( worker === null ) {
1313
+ this.ondone({ id, result: deserialize(data, options), size });
1314
+ } else {
1315
+ worker.postMessage({ what: THREAD_DESERIALIZE, id, data, options, size });
1316
+ }
1317
+ });
1318
+ });
1319
+ }
1320
+
1321
+ get queueSize() {
1322
+ return this.jobs.size;
1323
+ }
1324
+
1325
+ get workSize() {
1326
+ return this.workload;
1327
+ }
1328
+ }
1329
+
1330
+ const threads = {
1331
+ pool: [ new MainThread() ],
1332
+ thread(maxPoolSize) {
1333
+ const poolSize = this.pool.length;
1334
+ if ( poolSize !== 0 && poolSize >= maxPoolSize ) {
1335
+ if ( poolSize === 1 ) { return this.pool[0]; }
1336
+ return this.pool.reduce((a, b) => {
1337
+ //console.log(`${a.name}: q=${a.queueSize} w=${a.workSize} ${b.name}: q=${b.queueSize} w=${b.workSize}`);
1338
+ if ( b.queueSize === 0 ) { return b; }
1339
+ if ( a.queueSize === 0 ) { return a; }
1340
+ return b.workSize < a.workSize ? b : a;
1341
+ });
1342
+ }
1343
+ const thread = new Thread(thread => {
1344
+ const pos = this.pool.indexOf(thread);
1345
+ if ( pos === -1 ) { return; }
1346
+ this.pool.splice(pos, 1);
1347
+ });
1348
+ this.pool.push(thread);
1349
+ return thread;
1350
+ },
1351
+ };
1352
+
1353
+ export async function serializeAsync(data, options = {}) {
1354
+ const maxThreadCount = options.multithreaded || 0;
1355
+ if ( maxThreadCount === 0 ) {
1356
+ return serialize(data, options);
1357
+ }
1358
+ const thread = threads.thread(maxThreadCount);
1359
+ //console.log(`serializeAsync: thread=${thread.name} workload=${thread.workSize}`);
1360
+ const result = await thread.serialize(data, options);
1361
+ if ( result !== undefined ) { return result; }
1362
+ return serialize(data, options);
1363
+ }
1364
+
1365
+ export async function deserializeAsync(data, options = {}) {
1366
+ if ( isSerialized(data) === false ) { return data; }
1367
+ const maxThreadCount = options.multithreaded || 0;
1368
+ if ( maxThreadCount === 0 ) {
1369
+ return deserialize(data, options);
1370
+ }
1371
+ const thread = threads.thread(maxThreadCount);
1372
+ //console.log(`deserializeAsync: thread=${thread.name} data=${data.length} workload=${thread.workSize}`);
1373
+ const result = await thread.deserialize(data, options);
1374
+ if ( result !== undefined ) { return result; }
1375
+ return deserialize(data, options);
1376
+ }
1377
+
1378
+ /*******************************************************************************
1379
+ *
1380
+ * Worker-only code
1381
+ *
1382
+ * */
1383
+
1384
+ if ( isInstanceOf(globalThis, 'DedicatedWorkerGlobalScope') ) {
1385
+ globalThis.onmessage = ev => {
1386
+ const msg = ev.data;
1387
+ switch ( msg.what ) {
1388
+ case THREAD_AREYOUREADY:
1389
+ setConfig(msg.config);
1390
+ globalThis.postMessage({ what: THREAD_IAMREADY });
1391
+ break;
1392
+ case THREAD_SERIALIZE:
1393
+ const result = serialize(msg.data, msg.options);
1394
+ globalThis.postMessage({ id: msg.id, size: msg.size, result });
1395
+ break;
1396
+ case THREAD_DESERIALIZE: {
1397
+ const result = deserialize(msg.data);
1398
+ globalThis.postMessage({ id: msg.id, size: msg.size, result });
1399
+ break;
1400
+ }
1401
+ }
1402
+ };
1403
+ }
1404
+
1405
+ /******************************************************************************/