@speckle/objectsender 1.0.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.
- package/dist/objectsender.cjs +3 -0
- package/dist/objectsender.cjs.map +1 -0
- package/dist/objectsender.js +534 -0
- package/dist/objectsender.js.map +1 -0
- package/dist/vite.svg +1 -0
- package/eslint.config.mjs +41 -0
- package/index.html +104 -0
- package/package.json +58 -0
- package/public/vite.svg +1 -0
- package/readme.md +13 -0
- package/src/examples/browser/main.ts +120 -0
- package/src/index.ts +51 -0
- package/src/transports/ITransport.ts +12 -0
- package/src/transports/ServerTransport.ts +61 -0
- package/src/utils/Base.ts +15 -0
- package/src/utils/IDisposable.ts +3 -0
- package/src/utils/Serializer.ts +248 -0
- package/src/utils/Sha1.ts +138 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +23 -0
- package/vite.config.js +18 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ITransport } from './ITransport'
|
|
2
|
+
import { IDisposable } from '../utils/IDisposable'
|
|
3
|
+
/**
|
|
4
|
+
* Basic object sender to a speckle server
|
|
5
|
+
*/
|
|
6
|
+
export class ServerTransport implements ITransport, IDisposable {
|
|
7
|
+
buffer: string[]
|
|
8
|
+
maxSize: number
|
|
9
|
+
currSize: number
|
|
10
|
+
serverUrl: string
|
|
11
|
+
projectId: string
|
|
12
|
+
authToken: string
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
serverUrl: string,
|
|
16
|
+
projectId: string,
|
|
17
|
+
authToken: string,
|
|
18
|
+
maxSize: number = 200_000
|
|
19
|
+
) {
|
|
20
|
+
this.maxSize = maxSize
|
|
21
|
+
this.currSize = 0
|
|
22
|
+
this.serverUrl = serverUrl
|
|
23
|
+
this.projectId = projectId
|
|
24
|
+
this.authToken = authToken
|
|
25
|
+
this.buffer = []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async write(serialisedObject: string, size: number) {
|
|
29
|
+
this.buffer.push(serialisedObject)
|
|
30
|
+
this.currSize += size
|
|
31
|
+
if (this.currSize < this.maxSize) return // return fast
|
|
32
|
+
await this.flush() // block until we send objects
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async flush() {
|
|
36
|
+
if (this.buffer.length === 0) return
|
|
37
|
+
|
|
38
|
+
const formData = new FormData()
|
|
39
|
+
const concat = '[' + this.buffer.join(',') + ']'
|
|
40
|
+
formData.append('object-batch', new Blob([concat], { type: 'application/json' }))
|
|
41
|
+
const url = new URL(`/objects/${this.projectId}`, this.serverUrl)
|
|
42
|
+
const res = await fetch(url, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
45
|
+
body: formData
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (res.status !== 201) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Unexpected error when sending data. Expected status 200, got ${res.status}`
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.buffer = []
|
|
55
|
+
this.currSize = 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
dispose() {
|
|
59
|
+
this.buffer = []
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
/**
|
|
3
|
+
* Basic 'Base'-like object from .NET. It will create a 'speckle_type' prop that defaults to the class' name. This can be overriden by providing yourself a 'speckle_type' property in the props argument of the constructor.
|
|
4
|
+
*/
|
|
5
|
+
export class Base implements Record<string, unknown> {
|
|
6
|
+
speckle_type: string
|
|
7
|
+
constructor(props?: Record<string, unknown>) {
|
|
8
|
+
this.speckle_type = this.constructor.name
|
|
9
|
+
|
|
10
|
+
if (props) {
|
|
11
|
+
for (const key in props) this[key] = props[key]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
[x: string]: unknown
|
|
15
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import { SHA1 } from './Sha1'
|
|
3
|
+
import { ITransport } from '../transports/ITransport'
|
|
4
|
+
import { Base } from './Base'
|
|
5
|
+
import { IDisposable } from './IDisposable'
|
|
6
|
+
import { isObjectLike, get } from '#lodash'
|
|
7
|
+
|
|
8
|
+
type BasicSpeckleObject = Record<string, unknown> & {
|
|
9
|
+
speckle_type: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const isSpeckleObject = (obj: unknown): obj is BasicSpeckleObject =>
|
|
13
|
+
isObjectLike(obj) && !!get(obj, 'speckle_type')
|
|
14
|
+
|
|
15
|
+
export class Serializer implements IDisposable {
|
|
16
|
+
chunkSize: number
|
|
17
|
+
detachLineage: boolean[]
|
|
18
|
+
lineage: string[]
|
|
19
|
+
familyTree: Record<string, Record<string, number>>
|
|
20
|
+
closureTable: Record<string, unknown>
|
|
21
|
+
transport: ITransport | null
|
|
22
|
+
uniqueId: number
|
|
23
|
+
hashingFunction: (s: string) => string
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
transport: ITransport,
|
|
27
|
+
chunkSize: number = 1000,
|
|
28
|
+
hashingFunction: (s: string) => string = SHA1
|
|
29
|
+
) {
|
|
30
|
+
this.chunkSize = chunkSize
|
|
31
|
+
this.detachLineage = [true] // first ever call is always detached
|
|
32
|
+
this.lineage = []
|
|
33
|
+
this.familyTree = {}
|
|
34
|
+
this.closureTable = {}
|
|
35
|
+
this.transport = transport
|
|
36
|
+
this.uniqueId = 0
|
|
37
|
+
this.hashingFunction = hashingFunction || SHA1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async write(obj: Base) {
|
|
41
|
+
return await this.#traverse(obj, true)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async #traverse(obj: Record<string, unknown>, root: boolean) {
|
|
45
|
+
const temporaryId = `${this.uniqueId++}-obj`
|
|
46
|
+
this.lineage.push(temporaryId)
|
|
47
|
+
|
|
48
|
+
const traversed = { speckle_type: obj.speckle_type || 'Base' } as Record<
|
|
49
|
+
string,
|
|
50
|
+
unknown
|
|
51
|
+
>
|
|
52
|
+
|
|
53
|
+
for (const propKey in obj) {
|
|
54
|
+
const value = obj[propKey]
|
|
55
|
+
// 0. skip some props
|
|
56
|
+
if (!value || propKey === 'id' || propKey.startsWith('_')) continue
|
|
57
|
+
|
|
58
|
+
// 1. primitives (numbers, bools, strings)
|
|
59
|
+
if (typeof value !== 'object') {
|
|
60
|
+
traversed[propKey] = value
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const isDetachedProp = propKey.startsWith('@')
|
|
65
|
+
|
|
66
|
+
// 2. chunked arrays
|
|
67
|
+
const isArray = Array.isArray(value)
|
|
68
|
+
const isChunked = isArray ? propKey.match(/^@\((\d*)\)/) : false // chunk syntax
|
|
69
|
+
if (isArray && isChunked && value.length !== 0 && typeof value[0] !== 'object') {
|
|
70
|
+
const chunkSize = isChunked[1] !== '' ? parseInt(isChunked[1]) : this.chunkSize
|
|
71
|
+
const chunkRefs = []
|
|
72
|
+
|
|
73
|
+
let chunk = new DataChunk()
|
|
74
|
+
let count = 0
|
|
75
|
+
for (const el of value) {
|
|
76
|
+
if (count === chunkSize) {
|
|
77
|
+
chunkRefs.push(await this.#handleChunk(chunk))
|
|
78
|
+
chunk = new DataChunk()
|
|
79
|
+
count = 0
|
|
80
|
+
}
|
|
81
|
+
chunk.data.push(el)
|
|
82
|
+
count++
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (chunk.data.length !== 0) chunkRefs.push(await this.#handleChunk(chunk))
|
|
86
|
+
traversed[propKey.replace(isChunked[0], '')] = chunkRefs // strip chunk syntax
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 3. speckle objects
|
|
91
|
+
if ((value as Record<string, unknown>).speckle_type) {
|
|
92
|
+
const child = (await this.#traverseValue({
|
|
93
|
+
value,
|
|
94
|
+
isDetached: isDetachedProp
|
|
95
|
+
})) as {
|
|
96
|
+
id: string
|
|
97
|
+
}
|
|
98
|
+
traversed[propKey] = isDetachedProp ? this.#detachHelper(child.id) : child
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 4. other objects (dicts/maps, lists)
|
|
103
|
+
traversed[propKey] = await this.#traverseValue({
|
|
104
|
+
value,
|
|
105
|
+
isDetached: isDetachedProp
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
// We've finished going through all the properties of this object, now let's perform the last rites
|
|
109
|
+
const detached = this.detachLineage.pop()
|
|
110
|
+
const parent = this.lineage.pop() as string
|
|
111
|
+
|
|
112
|
+
if (this.familyTree[parent]) {
|
|
113
|
+
const closure = {} as Record<string, number>
|
|
114
|
+
|
|
115
|
+
Object.entries(this.familyTree[parent]).forEach(([ref, depth]) => {
|
|
116
|
+
closure[ref] = depth - this.detachLineage.length
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
traversed['totalChildrenCount'] = Object.keys(closure).length
|
|
120
|
+
|
|
121
|
+
if (traversed['totalChildrenCount']) {
|
|
122
|
+
traversed['__closure'] = closure
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const { hash, serializedObject, size } = this.#generateId(traversed)
|
|
127
|
+
traversed.id = hash
|
|
128
|
+
|
|
129
|
+
// Pop it in
|
|
130
|
+
if ((detached || root) && this.transport) {
|
|
131
|
+
await this.transport.write(serializedObject, size)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// We've reached the end, let's flush
|
|
135
|
+
if (root && this.transport) {
|
|
136
|
+
await this.transport.flush()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { hash, traversed }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async #traverseValue({
|
|
143
|
+
value,
|
|
144
|
+
isDetached = false
|
|
145
|
+
}: {
|
|
146
|
+
value: unknown
|
|
147
|
+
isDetached?: boolean
|
|
148
|
+
}): Promise<unknown> {
|
|
149
|
+
// 1. primitives
|
|
150
|
+
if (typeof value !== 'object') return value
|
|
151
|
+
|
|
152
|
+
// 2. arrays
|
|
153
|
+
if (Array.isArray(value)) {
|
|
154
|
+
const arr = value as unknown[]
|
|
155
|
+
// 2.1 empty arrays
|
|
156
|
+
if (arr.length === 0) return value as unknown
|
|
157
|
+
|
|
158
|
+
// 2.2 primitive arrays
|
|
159
|
+
if (typeof arr[0] !== 'object') return arr
|
|
160
|
+
|
|
161
|
+
// 2.3. non-primitive non-detached arrays
|
|
162
|
+
if (!isDetached) {
|
|
163
|
+
return Promise.all(
|
|
164
|
+
value.map(async (el) => await this.#traverseValue({ value: el }))
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 2.4 non-primitive detached arrays
|
|
169
|
+
const detachedList = [] as unknown[]
|
|
170
|
+
for (const el of value) {
|
|
171
|
+
if (isSpeckleObject(el)) {
|
|
172
|
+
this.detachLineage.push(isDetached)
|
|
173
|
+
const { hash } = await this.#traverse(el, false)
|
|
174
|
+
detachedList.push(this.#detachHelper(hash))
|
|
175
|
+
} else {
|
|
176
|
+
detachedList.push(await this.#traverseValue({ value: el, isDetached }))
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return detachedList
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 3. dicts
|
|
183
|
+
if (!(value as { speckle_type?: string }).speckle_type) return value
|
|
184
|
+
|
|
185
|
+
// 4. base objects
|
|
186
|
+
if ((value as { speckle_type?: string }).speckle_type) {
|
|
187
|
+
this.detachLineage.push(isDetached)
|
|
188
|
+
const res = await this.#traverse(value as Record<string, unknown>, false)
|
|
189
|
+
return res.traversed
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
throw new Error(`Unsupported type '${typeof value}': ${value}.`)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#detachHelper(refHash: string) {
|
|
196
|
+
this.lineage.forEach((parent) => {
|
|
197
|
+
if (!this.familyTree[parent]) this.familyTree[parent] = {}
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
!this.familyTree[parent][refHash] ||
|
|
201
|
+
this.familyTree[parent][refHash] > this.detachLineage.length
|
|
202
|
+
) {
|
|
203
|
+
this.familyTree[parent][refHash] = this.detachLineage.length
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
return {
|
|
207
|
+
referencedId: refHash,
|
|
208
|
+
speckle_type: 'reference'
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async #handleChunk(chunk: DataChunk) {
|
|
213
|
+
this.detachLineage.push(true)
|
|
214
|
+
const { hash } = await this.#traverse(
|
|
215
|
+
chunk as unknown as Record<string, unknown>,
|
|
216
|
+
false
|
|
217
|
+
)
|
|
218
|
+
return this.#detachHelper(hash)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#generateId(obj: Record<string, unknown>) {
|
|
222
|
+
const s = JSON.stringify(obj)
|
|
223
|
+
const h = this.hashingFunction(s)
|
|
224
|
+
const f = s.substring(0, 1) + `"id":"${h}",` + s.substring(1)
|
|
225
|
+
return {
|
|
226
|
+
hash: SHA1(s),
|
|
227
|
+
serializedObject: f,
|
|
228
|
+
size: s.length // approx, good enough as we're just limiting artificially batch sizes based on this
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
dispose() {
|
|
233
|
+
this.detachLineage = []
|
|
234
|
+
this.lineage = []
|
|
235
|
+
this.familyTree = {}
|
|
236
|
+
this.closureTable = {}
|
|
237
|
+
this.transport = null
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
class DataChunk {
|
|
242
|
+
speckle_type: 'Speckle.Core.Models.DataChunk'
|
|
243
|
+
data: unknown[]
|
|
244
|
+
constructor() {
|
|
245
|
+
this.data = []
|
|
246
|
+
this.speckle_type = 'Speckle.Core.Models.DataChunk'
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
/**
|
|
3
|
+
* Basic hashing function, to avoid dependencies and crazy build processes
|
|
4
|
+
* @param msg
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export function SHA1(msg: string) {
|
|
8
|
+
function rotate_left(n: number, s: number) {
|
|
9
|
+
const t4 = (n << s) | (n >>> (32 - s))
|
|
10
|
+
return t4
|
|
11
|
+
}
|
|
12
|
+
function cvt_hex(val: number) {
|
|
13
|
+
let str = ''
|
|
14
|
+
let i
|
|
15
|
+
let v
|
|
16
|
+
for (i = 7; i >= 0; i--) {
|
|
17
|
+
v = (val >>> (i * 4)) & 0x0f
|
|
18
|
+
str += v.toString(16)
|
|
19
|
+
}
|
|
20
|
+
return str
|
|
21
|
+
}
|
|
22
|
+
function Utf8Encode(string: string) {
|
|
23
|
+
string = string.replace(/\r\n/g, '\n')
|
|
24
|
+
let utftext = ''
|
|
25
|
+
for (let n = 0; n < string.length; n++) {
|
|
26
|
+
const c = string.charCodeAt(n)
|
|
27
|
+
if (c < 128) {
|
|
28
|
+
utftext += String.fromCharCode(c)
|
|
29
|
+
} else if (c > 127 && c < 2048) {
|
|
30
|
+
utftext += String.fromCharCode((c >> 6) | 192)
|
|
31
|
+
utftext += String.fromCharCode((c & 63) | 128)
|
|
32
|
+
} else {
|
|
33
|
+
utftext += String.fromCharCode((c >> 12) | 224)
|
|
34
|
+
utftext += String.fromCharCode(((c >> 6) & 63) | 128)
|
|
35
|
+
utftext += String.fromCharCode((c & 63) | 128)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return utftext
|
|
39
|
+
}
|
|
40
|
+
let blockstart
|
|
41
|
+
let i, j
|
|
42
|
+
const W = new Array(80)
|
|
43
|
+
let H0 = 0x67452301
|
|
44
|
+
let H1 = 0xefcdab89
|
|
45
|
+
let H2 = 0x98badcfe
|
|
46
|
+
let H3 = 0x10325476
|
|
47
|
+
let H4 = 0xc3d2e1f0
|
|
48
|
+
let A, B, C, D, E
|
|
49
|
+
let temp
|
|
50
|
+
msg = Utf8Encode(msg)
|
|
51
|
+
const msg_len = msg.length
|
|
52
|
+
const word_array = [] as unknown[]
|
|
53
|
+
for (i = 0; i < msg_len - 3; i += 4) {
|
|
54
|
+
j =
|
|
55
|
+
(msg.charCodeAt(i) << 24) |
|
|
56
|
+
(msg.charCodeAt(i + 1) << 16) |
|
|
57
|
+
(msg.charCodeAt(i + 2) << 8) |
|
|
58
|
+
msg.charCodeAt(i + 3)
|
|
59
|
+
word_array.push(j)
|
|
60
|
+
}
|
|
61
|
+
switch (msg_len % 4) {
|
|
62
|
+
case 0:
|
|
63
|
+
i = 0x080000000
|
|
64
|
+
break
|
|
65
|
+
case 1:
|
|
66
|
+
i = (msg.charCodeAt(msg_len - 1) << 24) | 0x0800000
|
|
67
|
+
break
|
|
68
|
+
case 2:
|
|
69
|
+
i =
|
|
70
|
+
(msg.charCodeAt(msg_len - 2) << 24) |
|
|
71
|
+
(msg.charCodeAt(msg_len - 1) << 16) |
|
|
72
|
+
0x08000
|
|
73
|
+
break
|
|
74
|
+
case 3:
|
|
75
|
+
i =
|
|
76
|
+
(msg.charCodeAt(msg_len - 3) << 24) |
|
|
77
|
+
(msg.charCodeAt(msg_len - 2) << 16) |
|
|
78
|
+
(msg.charCodeAt(msg_len - 1) << 8) |
|
|
79
|
+
0x80
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
word_array.push(i)
|
|
83
|
+
while (word_array.length % 16 !== 14) word_array.push(0)
|
|
84
|
+
word_array.push(msg_len >>> 29)
|
|
85
|
+
word_array.push((msg_len << 3) & 0x0ffffffff)
|
|
86
|
+
for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
|
|
87
|
+
for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i]
|
|
88
|
+
for (i = 16; i <= 79; i++)
|
|
89
|
+
W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1)
|
|
90
|
+
A = H0
|
|
91
|
+
B = H1
|
|
92
|
+
C = H2
|
|
93
|
+
D = H3
|
|
94
|
+
E = H4
|
|
95
|
+
for (i = 0; i <= 19; i++) {
|
|
96
|
+
temp =
|
|
97
|
+
(rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5a827999) & 0x0ffffffff
|
|
98
|
+
E = D
|
|
99
|
+
D = C
|
|
100
|
+
C = rotate_left(B, 30)
|
|
101
|
+
B = A
|
|
102
|
+
A = temp
|
|
103
|
+
}
|
|
104
|
+
for (i = 20; i <= 39; i++) {
|
|
105
|
+
temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ed9eba1) & 0x0ffffffff
|
|
106
|
+
E = D
|
|
107
|
+
D = C
|
|
108
|
+
C = rotate_left(B, 30)
|
|
109
|
+
B = A
|
|
110
|
+
A = temp
|
|
111
|
+
}
|
|
112
|
+
for (i = 40; i <= 59; i++) {
|
|
113
|
+
temp =
|
|
114
|
+
(rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8f1bbcdc) &
|
|
115
|
+
0x0ffffffff
|
|
116
|
+
E = D
|
|
117
|
+
D = C
|
|
118
|
+
C = rotate_left(B, 30)
|
|
119
|
+
B = A
|
|
120
|
+
A = temp
|
|
121
|
+
}
|
|
122
|
+
for (i = 60; i <= 79; i++) {
|
|
123
|
+
temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xca62c1d6) & 0x0ffffffff
|
|
124
|
+
E = D
|
|
125
|
+
D = C
|
|
126
|
+
C = rotate_left(B, 30)
|
|
127
|
+
B = A
|
|
128
|
+
A = temp
|
|
129
|
+
}
|
|
130
|
+
H0 = (H0 + A) & 0x0ffffffff
|
|
131
|
+
H1 = (H1 + B) & 0x0ffffffff
|
|
132
|
+
H2 = (H2 + C) & 0x0ffffffff
|
|
133
|
+
H3 = (H3 + D) & 0x0ffffffff
|
|
134
|
+
H4 = (H4 + E) & 0x0ffffffff
|
|
135
|
+
}
|
|
136
|
+
const h = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4)
|
|
137
|
+
return h.toLowerCase().substring(0, 32)
|
|
138
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
/* Linting */
|
|
17
|
+
"strict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["src"]
|
|
23
|
+
}
|
package/vite.config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import pkg from './package.json'
|
|
2
|
+
import { defineConfig } from 'vite'
|
|
3
|
+
import { resolve } from 'path'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
build: {
|
|
7
|
+
lib: {
|
|
8
|
+
entry: resolve(import.meta.dirname, './src/index.ts'),
|
|
9
|
+
name: 'objectsender',
|
|
10
|
+
fileName: 'objectsender',
|
|
11
|
+
formats: ['es', 'cjs']
|
|
12
|
+
},
|
|
13
|
+
sourcemap: true,
|
|
14
|
+
rollupOptions: {
|
|
15
|
+
external: Object.keys(pkg.dependencies || {})
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
})
|