@cloudbase/container 2.5.29-beta.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/src/index.ts ADDED
@@ -0,0 +1,330 @@
1
+ import { ICloudbase } from '@cloudbase/types'
2
+ import { ICloudbaseComponent } from '@cloudbase/types/component'
3
+ import { ICallContainerOptions, IContianerConfig } from '@cloudbase/types/container'
4
+ import { constants, utils, helpers } from '@cloudbase/utilities'
5
+ // import brotliPromise from 'brotli-dec-wasm' // Import the default export
6
+ import decompress from 'brotli/decompress'
7
+
8
+ import Go from './go_wams_exec'
9
+
10
+ declare const cloudbase: ICloudbase
11
+
12
+ const { getSdkName, ERRORS, COMMUNITY_SITE_URL } = constants
13
+ const { execCallback, printWarn } = utils
14
+ const { catchErrorsDecorator } = helpers
15
+
16
+ const COMPONENT_NAME = 'container'
17
+
18
+ /**
19
+ * 封装对齐 wx.request
20
+ */
21
+ async function request({
22
+ url,
23
+ method = 'GET',
24
+ header = {},
25
+ data,
26
+ timeout = 60000,
27
+ dataType = 'json',
28
+ responseType = 'text',
29
+ }: {
30
+ url: string
31
+ data?: string | object | ArrayBuffer
32
+ header?: Record<string, string>
33
+ timeout?: number
34
+ method?: string
35
+ dataType?: 'json' | string
36
+ responseType?: 'text' | 'arrayBuffer'
37
+ }) {
38
+ const headers = { ...header }
39
+
40
+ if (typeof data === 'object' && !(data instanceof ArrayBuffer || ArrayBuffer.isView(data))) {
41
+ if (!headers['Content-Type'] && !headers['content-type']) {
42
+ headers['Content-Type'] = 'application/json'
43
+ }
44
+ try {
45
+ data = JSON.stringify(data)
46
+ } catch (e) {
47
+ throw new Error(JSON.stringify({
48
+ code: ERRORS.INVALID_PARAMS,
49
+ msg: `[${COMPONENT_NAME}.callContainer] invalid data with content-type: application/json`,
50
+ }),)
51
+ }
52
+ }
53
+
54
+ const { signal, abort } = new AbortController()
55
+
56
+ return Promise.race([
57
+ new Promise((_, reject) => {
58
+ setTimeout(() => {
59
+ const error = new Error('请求超时')
60
+ ;(error as any).code = 'SERVER_TIMEOUT'
61
+ reject(error)
62
+ abort()
63
+ }, timeout)
64
+ }),
65
+ fetch(url, {
66
+ method,
67
+ headers,
68
+ body: data,
69
+ signal,
70
+ }).then((response) => {
71
+ if (responseType === 'arrayBuffer') {
72
+ return response.arrayBuffer()
73
+ }
74
+ if (dataType === 'json') {
75
+ try {
76
+ return response.json()
77
+ } catch (e) {
78
+ throw new Error(`[${getSdkName()}][${ERRORS.INVALID_PARAMS}][${COMPONENT_NAME}.callContainer] response data must be json`,)
79
+ }
80
+ }
81
+ return response.text()
82
+ }),
83
+ ])
84
+ }
85
+
86
+ class InvalieParamsError extends Error {
87
+ constructor(scope, message) {
88
+ const msg = JSON.stringify({
89
+ code: ERRORS.INVALID_PARAMS,
90
+ msg: `[${scope}] ${message}`,
91
+ })
92
+ super(msg)
93
+ }
94
+ }
95
+
96
+ interface ICallTcbContainerOptions extends ICallContainerOptions {
97
+ success: (res: { statusCode: string | number; header: ICallContainerOptions['header']; data: any }) => void
98
+ fail: (res: { data: { code: number; message: string } }) => void
99
+ }
100
+
101
+ interface IInitTcbContainerOptions {
102
+ config: Omit<IContianerConfig, 'wasmUrl'>
103
+ success: (res: { statusCode: string | number; header: ICallContainerOptions['header']; data: any }) => void
104
+ fail: (res: { data: { code: number; message: string } }) => void
105
+ }
106
+
107
+ class CloudbaseContainers {
108
+ private readonly config: IContianerConfig
109
+ private wasm: Promise<WebAssembly.Exports>
110
+ private containerInitPromise: /* Promise<any>*/ any | null
111
+
112
+ constructor(config: IContianerConfig) {
113
+ this.config = config
114
+ if (!this.config.wasmUrl) {
115
+ throw new Error(JSON.stringify({
116
+ code: ERRORS.INVALID_PARAMS,
117
+ msg: `[${COMPONENT_NAME}] 缺少 wasmUrl`,
118
+ }),)
119
+ }
120
+ this.wasm = getExportFunction(this.config.wasmUrl, {
121
+ utils: {
122
+ info(e) {
123
+ console.log(e)
124
+ },
125
+ request,
126
+ },
127
+ })
128
+ }
129
+
130
+ @catchErrorsDecorator({
131
+ customInfo: {
132
+ className: 'Cloudbase',
133
+ methodName: 'callContainer',
134
+ },
135
+ title: '调用失败',
136
+ messages: [
137
+ '请确认以下各项:',
138
+ ' 1 - 调用 callContainer() 的语法或参数是否正确',
139
+ ' 2 - 域名 & 路径是否存在',
140
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
141
+ ],
142
+ })
143
+ public async callContainer(options: ICallContainerOptions, callback?: Function) {
144
+ await this.initContainer(this.config)
145
+ const { callContainer }: { callContainer: (options: ICallTcbContainerOptions) => void } = (await this.wasm) as any
146
+
147
+ const {
148
+ url,
149
+ method = 'GET',
150
+ header = {},
151
+ data,
152
+ timeout = 60000,
153
+ dataType = 'json',
154
+ responseType = 'text',
155
+ ...restOptions
156
+ } = options
157
+
158
+ if (!url) {
159
+ throw new InvalieParamsError(`${COMPONENT_NAME}.callContainer`, 'invalid request url')
160
+ }
161
+
162
+ // @ts-ignore
163
+
164
+ try {
165
+ const res = await new Promise((resolve, reject) => {
166
+ const params = { url, method, header, data, timeout, dataType, responseType, ...restOptions }
167
+
168
+ callContainer({
169
+ ...params,
170
+ success: resolve,
171
+ fail: (res) => {
172
+ const { data } = res || {}
173
+ const error = new Error(data?.message || 'call container error')
174
+ ;(error as any).code = data?.code || 'UNKNOWN_ERROR'
175
+ reject(error)
176
+ },
177
+ })
178
+ })
179
+ return execCallback(callback, null, res)
180
+ } catch (e) {
181
+ execCallback(callback, e)
182
+ }
183
+ }
184
+
185
+ async initContainer(config: Omit<IContianerConfig, 'wasmUrl'>) {
186
+ const { initContainer }: { initContainer: (options: IInitTcbContainerOptions) => void } = (await this.wasm) as any
187
+ if (!this.containerInitPromise) {
188
+ this.containerInitPromise = new Promise((resolve, reject) => {
189
+ initContainer({
190
+ config,
191
+ success(res) {
192
+ if (String(res.statusCode) !== '200') {
193
+ reject(new Error(JSON.stringify({
194
+ code: 'INIT_ERROR',
195
+ msg: `[${COMPONENT_NAME}] ${res.statusCode} ${JSON.stringify(res.data)}`,
196
+ }),),)
197
+ }
198
+ resolve(res)
199
+ },
200
+ fail(res) {
201
+ reject(new Error(JSON.stringify({
202
+ code: 'INIT_ERROR',
203
+ msg: `[${COMPONENT_NAME}] ${JSON.stringify(res.data)}`,
204
+ }),),)
205
+ this._containerInitPromis = null
206
+ },
207
+ })
208
+ })
209
+ }
210
+
211
+ return this.containerInitPromise
212
+ }
213
+ }
214
+
215
+ const component: ICloudbaseComponent = {
216
+ name: COMPONENT_NAME,
217
+ namespace: 'container',
218
+ entity(config: IContianerConfig) {
219
+ if (this.containerInstance) {
220
+ printWarn(ERRORS.INVALID_OPERATION, 'every cloudbase instance should has only one container object')
221
+ return this.containerInstance
222
+ }
223
+
224
+ const instance = new CloudbaseContainers(config)
225
+ this.containerInstance = instance
226
+ this.callContainer = instance.callContainer.bind(instance)
227
+
228
+ return this.containerInstance
229
+ },
230
+ }
231
+ try {
232
+ cloudbase.registerComponent(component)
233
+ } catch (e) {}
234
+
235
+ export function registerContainers(app: Pick<ICloudbase, 'registerComponent'>) {
236
+ try {
237
+ app.registerComponent(component)
238
+ } catch (e) {
239
+ console.warn(e)
240
+ }
241
+ }
242
+
243
+ async function getExportFunction(url, module, mode = 'go') {
244
+ async function instantiateStreaming(resp, importObject, brotliCompressed = false) {
245
+ if (brotliCompressed || !WebAssembly.instantiateStreaming) {
246
+ const source = await (await resp).arrayBuffer()
247
+ const buffer = brotliCompressed ? decompress(new Int8Array(source)) : source
248
+ return WebAssembly.instantiate(buffer, importObject)
249
+ }
250
+
251
+ return WebAssembly.instantiateStreaming(resp, importObject)
252
+ }
253
+
254
+ let importObject = {
255
+ env: {
256
+ memoryBase: 0,
257
+ tableBase: 0,
258
+ memory: new WebAssembly.Memory({
259
+ initial: 256,
260
+ }),
261
+ // table: new WebAssembly.Table({
262
+ // initial: 2,
263
+ // element: 'anyfunc',
264
+ // }),
265
+ },
266
+ ...module,
267
+ }
268
+ let go
269
+ const brotliCompressed = isBrotliCompressed(url)
270
+
271
+ if (mode === 'go') {
272
+ go = new Go()
273
+ go._initedModulePromise = new Promise((resolve) => {
274
+ go._initedModuleResolve = resolve
275
+ })
276
+ importObject = {
277
+ ...go.importObject,
278
+ env: {
279
+ ...go.importObject.env,
280
+ exportModule(module) {
281
+ return go._initedModuleResolve(module)
282
+ },
283
+ },
284
+ }
285
+ }
286
+
287
+ const reuslt = await instantiateStreaming(fetch(url), importObject, brotliCompressed)
288
+
289
+ if (mode === 'go') {
290
+ await Promise.race([
291
+ go.run(reuslt.instance),
292
+ new Promise(resolve => setTimeout(() => {
293
+ resolve(null)
294
+ }, 500),),
295
+ ])
296
+
297
+ return { callContainer: (window as any).callContainer, initContainer: (window as any).initContainer }
298
+ }
299
+
300
+ // if (mode === 'go') {
301
+ // go.run(reuslt.instance)
302
+ // const module = await go._initedModulePromise
303
+ // return module
304
+ // }
305
+
306
+ return reuslt.instance.exports
307
+ }
308
+
309
+ function isBrotliCompressed(url) {
310
+ let brotliCompressed = false
311
+
312
+ let pathname = ''
313
+ try {
314
+ const a = document.createElement('a')
315
+ a.href = url
316
+ pathname = a.pathname
317
+ } catch (e) {
318
+ try {
319
+ const res = new URL(url)
320
+ pathname = res?.pathname
321
+ } catch (e) {
322
+ pathname = url
323
+ }
324
+ }
325
+
326
+ if (/\.br$/.test(pathname)) {
327
+ brotliCompressed = true
328
+ }
329
+ return brotliCompressed
330
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "es6",
5
+ "outDir": "dist/esm",
6
+ "rootDir": "src",
7
+ },
8
+ "include": [
9
+ "src/**/*.ts",
10
+ "package.json"
11
+ ],
12
+ "exclude": [
13
+ "node_modules",
14
+ "dist"
15
+ ]
16
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "outDir": "dist/cjs",
6
+ "rootDir": "src",
7
+ },
8
+ "include": [
9
+ "src/**/*.ts",
10
+ "package.json"
11
+ ],
12
+ "exclude": [
13
+ "node_modules",
14
+ "dist"
15
+ ]
16
+ }