@djvlc/runtime-web 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/index.d.ts +115 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/runtime.esm.js +3841 -0
- package/dist/runtime.esm.js.map +1 -0
- package/dist/runtime.iife.js +2 -0
- package/dist/runtime.iife.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,3841 @@
|
|
|
1
|
+
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
2
|
+
ErrorCode2[ErrorCode2["UNKNOWN"] = 1e3] = "UNKNOWN";
|
|
3
|
+
ErrorCode2[ErrorCode2["INVALID_REQUEST"] = 1001] = "INVALID_REQUEST";
|
|
4
|
+
ErrorCode2[ErrorCode2["UNAUTHORIZED"] = 1002] = "UNAUTHORIZED";
|
|
5
|
+
ErrorCode2[ErrorCode2["FORBIDDEN"] = 1003] = "FORBIDDEN";
|
|
6
|
+
ErrorCode2[ErrorCode2["NOT_FOUND"] = 1004] = "NOT_FOUND";
|
|
7
|
+
ErrorCode2[ErrorCode2["RATE_LIMITED"] = 1005] = "RATE_LIMITED";
|
|
8
|
+
ErrorCode2[ErrorCode2["VALIDATION_ERROR"] = 1006] = "VALIDATION_ERROR";
|
|
9
|
+
ErrorCode2[ErrorCode2["INTERNAL_ERROR"] = 1007] = "INTERNAL_ERROR";
|
|
10
|
+
ErrorCode2[ErrorCode2["SERVICE_UNAVAILABLE"] = 1008] = "SERVICE_UNAVAILABLE";
|
|
11
|
+
ErrorCode2[ErrorCode2["TIMEOUT"] = 1009] = "TIMEOUT";
|
|
12
|
+
ErrorCode2[ErrorCode2["ACTION_NOT_FOUND"] = 2001] = "ACTION_NOT_FOUND";
|
|
13
|
+
ErrorCode2[ErrorCode2["ACTION_INVALID_PARAMS"] = 2002] = "ACTION_INVALID_PARAMS";
|
|
14
|
+
ErrorCode2[ErrorCode2["ACTION_EXECUTION_FAILED"] = 2003] = "ACTION_EXECUTION_FAILED";
|
|
15
|
+
ErrorCode2[ErrorCode2["ACTION_IDEMPOTENCY_CONFLICT"] = 2004] = "ACTION_IDEMPOTENCY_CONFLICT";
|
|
16
|
+
ErrorCode2[ErrorCode2["ACTION_BLOCKED"] = 2005] = "ACTION_BLOCKED";
|
|
17
|
+
ErrorCode2[ErrorCode2["ACTION_RISK_REJECTED"] = 2006] = "ACTION_RISK_REJECTED";
|
|
18
|
+
ErrorCode2[ErrorCode2["ACTION_EXPIRED"] = 2007] = "ACTION_EXPIRED";
|
|
19
|
+
ErrorCode2[ErrorCode2["ACTION_QUOTA_EXCEEDED"] = 2008] = "ACTION_QUOTA_EXCEEDED";
|
|
20
|
+
ErrorCode2[ErrorCode2["QUERY_NOT_FOUND"] = 3001] = "QUERY_NOT_FOUND";
|
|
21
|
+
ErrorCode2[ErrorCode2["QUERY_INVALID_PARAMS"] = 3002] = "QUERY_INVALID_PARAMS";
|
|
22
|
+
ErrorCode2[ErrorCode2["QUERY_EXECUTION_FAILED"] = 3003] = "QUERY_EXECUTION_FAILED";
|
|
23
|
+
ErrorCode2[ErrorCode2["QUERY_FIELD_NOT_ALLOWED"] = 3004] = "QUERY_FIELD_NOT_ALLOWED";
|
|
24
|
+
ErrorCode2[ErrorCode2["QUERY_TIMEOUT"] = 3005] = "QUERY_TIMEOUT";
|
|
25
|
+
ErrorCode2[ErrorCode2["QUERY_DISABLED"] = 3006] = "QUERY_DISABLED";
|
|
26
|
+
ErrorCode2[ErrorCode2["COMPONENT_NOT_FOUND"] = 4001] = "COMPONENT_NOT_FOUND";
|
|
27
|
+
ErrorCode2[ErrorCode2["COMPONENT_VERSION_NOT_FOUND"] = 4002] = "COMPONENT_VERSION_NOT_FOUND";
|
|
28
|
+
ErrorCode2[ErrorCode2["COMPONENT_BLOCKED"] = 4003] = "COMPONENT_BLOCKED";
|
|
29
|
+
ErrorCode2[ErrorCode2["COMPONENT_INTEGRITY_MISMATCH"] = 4004] = "COMPONENT_INTEGRITY_MISMATCH";
|
|
30
|
+
ErrorCode2[ErrorCode2["COMPONENT_INCOMPATIBLE"] = 4005] = "COMPONENT_INCOMPATIBLE";
|
|
31
|
+
ErrorCode2[ErrorCode2["COMPONENT_LOAD_FAILED"] = 4006] = "COMPONENT_LOAD_FAILED";
|
|
32
|
+
ErrorCode2[ErrorCode2["COMPONENT_RENDER_ERROR"] = 4007] = "COMPONENT_RENDER_ERROR";
|
|
33
|
+
ErrorCode2[ErrorCode2["PAGE_NOT_FOUND"] = 5001] = "PAGE_NOT_FOUND";
|
|
34
|
+
ErrorCode2[ErrorCode2["PAGE_VERSION_NOT_FOUND"] = 5002] = "PAGE_VERSION_NOT_FOUND";
|
|
35
|
+
ErrorCode2[ErrorCode2["PAGE_SCHEMA_INVALID"] = 5003] = "PAGE_SCHEMA_INVALID";
|
|
36
|
+
ErrorCode2[ErrorCode2["PAGE_MANIFEST_INVALID"] = 5004] = "PAGE_MANIFEST_INVALID";
|
|
37
|
+
ErrorCode2[ErrorCode2["PAGE_PUBLISH_FAILED"] = 5005] = "PAGE_PUBLISH_FAILED";
|
|
38
|
+
ErrorCode2[ErrorCode2["PAGE_ROLLBACK_FAILED"] = 5006] = "PAGE_ROLLBACK_FAILED";
|
|
39
|
+
ErrorCode2[ErrorCode2["ACTIVITY_NOT_FOUND"] = 6001] = "ACTIVITY_NOT_FOUND";
|
|
40
|
+
ErrorCode2[ErrorCode2["ACTIVITY_NOT_STARTED"] = 6002] = "ACTIVITY_NOT_STARTED";
|
|
41
|
+
ErrorCode2[ErrorCode2["ACTIVITY_ENDED"] = 6003] = "ACTIVITY_ENDED";
|
|
42
|
+
ErrorCode2[ErrorCode2["ACTIVITY_ALREADY_CLAIMED"] = 6004] = "ACTIVITY_ALREADY_CLAIMED";
|
|
43
|
+
ErrorCode2[ErrorCode2["ACTIVITY_ALREADY_SIGNED"] = 6005] = "ACTIVITY_ALREADY_SIGNED";
|
|
44
|
+
ErrorCode2[ErrorCode2["ACTIVITY_LIMIT_EXCEEDED"] = 6006] = "ACTIVITY_LIMIT_EXCEEDED";
|
|
45
|
+
ErrorCode2[ErrorCode2["ACTIVITY_DISABLED"] = 6007] = "ACTIVITY_DISABLED";
|
|
46
|
+
ErrorCode2[ErrorCode2["EXPRESSION_SYNTAX_ERROR"] = 7001] = "EXPRESSION_SYNTAX_ERROR";
|
|
47
|
+
ErrorCode2[ErrorCode2["EXPRESSION_UNKNOWN_FUNCTION"] = 7002] = "EXPRESSION_UNKNOWN_FUNCTION";
|
|
48
|
+
ErrorCode2[ErrorCode2["EXPRESSION_INVALID_ARGUMENT"] = 7003] = "EXPRESSION_INVALID_ARGUMENT";
|
|
49
|
+
ErrorCode2[ErrorCode2["EXPRESSION_UNKNOWN_VARIABLE"] = 7004] = "EXPRESSION_UNKNOWN_VARIABLE";
|
|
50
|
+
ErrorCode2[ErrorCode2["EXPRESSION_TYPE_MISMATCH"] = 7005] = "EXPRESSION_TYPE_MISMATCH";
|
|
51
|
+
ErrorCode2[ErrorCode2["EXPRESSION_ACCESS_DENIED"] = 7006] = "EXPRESSION_ACCESS_DENIED";
|
|
52
|
+
ErrorCode2[ErrorCode2["SCHEMA_VERSION_MISMATCH"] = 8001] = "SCHEMA_VERSION_MISMATCH";
|
|
53
|
+
ErrorCode2[ErrorCode2["MIGRATION_FAILED"] = 8002] = "MIGRATION_FAILED";
|
|
54
|
+
ErrorCode2[ErrorCode2["MIGRATION_NOT_FOUND"] = 8003] = "MIGRATION_NOT_FOUND";
|
|
55
|
+
ErrorCode2[ErrorCode2["VERSION_INCOMPATIBLE"] = 8004] = "VERSION_INCOMPATIBLE";
|
|
56
|
+
return ErrorCode2;
|
|
57
|
+
})(ErrorCode || {});
|
|
58
|
+
var ErrorMessages = {
|
|
59
|
+
// 通用错误
|
|
60
|
+
[
|
|
61
|
+
1e3
|
|
62
|
+
/* UNKNOWN */
|
|
63
|
+
]: "未知错误",
|
|
64
|
+
[
|
|
65
|
+
1001
|
|
66
|
+
/* INVALID_REQUEST */
|
|
67
|
+
]: "请求参数无效",
|
|
68
|
+
[
|
|
69
|
+
1002
|
|
70
|
+
/* UNAUTHORIZED */
|
|
71
|
+
]: "未授权访问",
|
|
72
|
+
[
|
|
73
|
+
1003
|
|
74
|
+
/* FORBIDDEN */
|
|
75
|
+
]: "禁止访问",
|
|
76
|
+
[
|
|
77
|
+
1004
|
|
78
|
+
/* NOT_FOUND */
|
|
79
|
+
]: "资源不存在",
|
|
80
|
+
[
|
|
81
|
+
1005
|
|
82
|
+
/* RATE_LIMITED */
|
|
83
|
+
]: "请求频率超限",
|
|
84
|
+
[
|
|
85
|
+
1006
|
|
86
|
+
/* VALIDATION_ERROR */
|
|
87
|
+
]: "数据校验失败",
|
|
88
|
+
[
|
|
89
|
+
1007
|
|
90
|
+
/* INTERNAL_ERROR */
|
|
91
|
+
]: "服务内部错误",
|
|
92
|
+
[
|
|
93
|
+
1008
|
|
94
|
+
/* SERVICE_UNAVAILABLE */
|
|
95
|
+
]: "服务暂不可用",
|
|
96
|
+
[
|
|
97
|
+
1009
|
|
98
|
+
/* TIMEOUT */
|
|
99
|
+
]: "请求超时",
|
|
100
|
+
// Action 错误
|
|
101
|
+
[
|
|
102
|
+
2001
|
|
103
|
+
/* ACTION_NOT_FOUND */
|
|
104
|
+
]: "动作不存在",
|
|
105
|
+
[
|
|
106
|
+
2002
|
|
107
|
+
/* ACTION_INVALID_PARAMS */
|
|
108
|
+
]: "动作参数无效",
|
|
109
|
+
[
|
|
110
|
+
2003
|
|
111
|
+
/* ACTION_EXECUTION_FAILED */
|
|
112
|
+
]: "动作执行失败",
|
|
113
|
+
[
|
|
114
|
+
2004
|
|
115
|
+
/* ACTION_IDEMPOTENCY_CONFLICT */
|
|
116
|
+
]: "重复请求",
|
|
117
|
+
[
|
|
118
|
+
2005
|
|
119
|
+
/* ACTION_BLOCKED */
|
|
120
|
+
]: "动作已被禁用",
|
|
121
|
+
[
|
|
122
|
+
2006
|
|
123
|
+
/* ACTION_RISK_REJECTED */
|
|
124
|
+
]: "风控拒绝",
|
|
125
|
+
[
|
|
126
|
+
2007
|
|
127
|
+
/* ACTION_EXPIRED */
|
|
128
|
+
]: "动作已过期",
|
|
129
|
+
[
|
|
130
|
+
2008
|
|
131
|
+
/* ACTION_QUOTA_EXCEEDED */
|
|
132
|
+
]: "配额已用尽",
|
|
133
|
+
// Data Query 错误
|
|
134
|
+
[
|
|
135
|
+
3001
|
|
136
|
+
/* QUERY_NOT_FOUND */
|
|
137
|
+
]: "查询不存在",
|
|
138
|
+
[
|
|
139
|
+
3002
|
|
140
|
+
/* QUERY_INVALID_PARAMS */
|
|
141
|
+
]: "查询参数无效",
|
|
142
|
+
[
|
|
143
|
+
3003
|
|
144
|
+
/* QUERY_EXECUTION_FAILED */
|
|
145
|
+
]: "查询执行失败",
|
|
146
|
+
[
|
|
147
|
+
3004
|
|
148
|
+
/* QUERY_FIELD_NOT_ALLOWED */
|
|
149
|
+
]: "字段访问被拒绝",
|
|
150
|
+
[
|
|
151
|
+
3005
|
|
152
|
+
/* QUERY_TIMEOUT */
|
|
153
|
+
]: "查询超时",
|
|
154
|
+
[
|
|
155
|
+
3006
|
|
156
|
+
/* QUERY_DISABLED */
|
|
157
|
+
]: "查询已禁用",
|
|
158
|
+
// 组件错误
|
|
159
|
+
[
|
|
160
|
+
4001
|
|
161
|
+
/* COMPONENT_NOT_FOUND */
|
|
162
|
+
]: "组件不存在",
|
|
163
|
+
[
|
|
164
|
+
4002
|
|
165
|
+
/* COMPONENT_VERSION_NOT_FOUND */
|
|
166
|
+
]: "组件版本不存在",
|
|
167
|
+
[
|
|
168
|
+
4003
|
|
169
|
+
/* COMPONENT_BLOCKED */
|
|
170
|
+
]: "组件已被禁用",
|
|
171
|
+
[
|
|
172
|
+
4004
|
|
173
|
+
/* COMPONENT_INTEGRITY_MISMATCH */
|
|
174
|
+
]: "组件完整性校验失败",
|
|
175
|
+
[
|
|
176
|
+
4005
|
|
177
|
+
/* COMPONENT_INCOMPATIBLE */
|
|
178
|
+
]: "组件版本不兼容",
|
|
179
|
+
[
|
|
180
|
+
4006
|
|
181
|
+
/* COMPONENT_LOAD_FAILED */
|
|
182
|
+
]: "组件加载失败",
|
|
183
|
+
[
|
|
184
|
+
4007
|
|
185
|
+
/* COMPONENT_RENDER_ERROR */
|
|
186
|
+
]: "组件渲染错误",
|
|
187
|
+
// 页面错误
|
|
188
|
+
[
|
|
189
|
+
5001
|
|
190
|
+
/* PAGE_NOT_FOUND */
|
|
191
|
+
]: "页面不存在",
|
|
192
|
+
[
|
|
193
|
+
5002
|
|
194
|
+
/* PAGE_VERSION_NOT_FOUND */
|
|
195
|
+
]: "页面版本不存在",
|
|
196
|
+
[
|
|
197
|
+
5003
|
|
198
|
+
/* PAGE_SCHEMA_INVALID */
|
|
199
|
+
]: "页面 Schema 无效",
|
|
200
|
+
[
|
|
201
|
+
5004
|
|
202
|
+
/* PAGE_MANIFEST_INVALID */
|
|
203
|
+
]: "页面 Manifest 无效",
|
|
204
|
+
[
|
|
205
|
+
5005
|
|
206
|
+
/* PAGE_PUBLISH_FAILED */
|
|
207
|
+
]: "页面发布失败",
|
|
208
|
+
[
|
|
209
|
+
5006
|
|
210
|
+
/* PAGE_ROLLBACK_FAILED */
|
|
211
|
+
]: "页面回滚失败",
|
|
212
|
+
// 活动错误
|
|
213
|
+
[
|
|
214
|
+
6001
|
|
215
|
+
/* ACTIVITY_NOT_FOUND */
|
|
216
|
+
]: "活动不存在",
|
|
217
|
+
[
|
|
218
|
+
6002
|
|
219
|
+
/* ACTIVITY_NOT_STARTED */
|
|
220
|
+
]: "活动未开始",
|
|
221
|
+
[
|
|
222
|
+
6003
|
|
223
|
+
/* ACTIVITY_ENDED */
|
|
224
|
+
]: "活动已结束",
|
|
225
|
+
[
|
|
226
|
+
6004
|
|
227
|
+
/* ACTIVITY_ALREADY_CLAIMED */
|
|
228
|
+
]: "已领取过",
|
|
229
|
+
[
|
|
230
|
+
6005
|
|
231
|
+
/* ACTIVITY_ALREADY_SIGNED */
|
|
232
|
+
]: "今日已签到",
|
|
233
|
+
[
|
|
234
|
+
6006
|
|
235
|
+
/* ACTIVITY_LIMIT_EXCEEDED */
|
|
236
|
+
]: "超出领取限制",
|
|
237
|
+
[
|
|
238
|
+
6007
|
|
239
|
+
/* ACTIVITY_DISABLED */
|
|
240
|
+
]: "活动已禁用",
|
|
241
|
+
// 表达式错误
|
|
242
|
+
[
|
|
243
|
+
7001
|
|
244
|
+
/* EXPRESSION_SYNTAX_ERROR */
|
|
245
|
+
]: "表达式语法错误",
|
|
246
|
+
[
|
|
247
|
+
7002
|
|
248
|
+
/* EXPRESSION_UNKNOWN_FUNCTION */
|
|
249
|
+
]: "未知函数",
|
|
250
|
+
[
|
|
251
|
+
7003
|
|
252
|
+
/* EXPRESSION_INVALID_ARGUMENT */
|
|
253
|
+
]: "无效参数",
|
|
254
|
+
[
|
|
255
|
+
7004
|
|
256
|
+
/* EXPRESSION_UNKNOWN_VARIABLE */
|
|
257
|
+
]: "未知变量",
|
|
258
|
+
[
|
|
259
|
+
7005
|
|
260
|
+
/* EXPRESSION_TYPE_MISMATCH */
|
|
261
|
+
]: "类型不匹配",
|
|
262
|
+
[
|
|
263
|
+
7006
|
|
264
|
+
/* EXPRESSION_ACCESS_DENIED */
|
|
265
|
+
]: "访问被拒绝",
|
|
266
|
+
// 版本/迁移错误
|
|
267
|
+
[
|
|
268
|
+
8001
|
|
269
|
+
/* SCHEMA_VERSION_MISMATCH */
|
|
270
|
+
]: "Schema 版本不匹配",
|
|
271
|
+
[
|
|
272
|
+
8002
|
|
273
|
+
/* MIGRATION_FAILED */
|
|
274
|
+
]: "迁移失败",
|
|
275
|
+
[
|
|
276
|
+
8003
|
|
277
|
+
/* MIGRATION_NOT_FOUND */
|
|
278
|
+
]: "迁移脚本不存在",
|
|
279
|
+
[
|
|
280
|
+
8004
|
|
281
|
+
/* VERSION_INCOMPATIBLE */
|
|
282
|
+
]: "版本不兼容"
|
|
283
|
+
};
|
|
284
|
+
var BUILTIN_FUNCTIONS = [
|
|
285
|
+
// 字符串函数
|
|
286
|
+
{
|
|
287
|
+
name: "len",
|
|
288
|
+
description: "获取字符串或数组长度",
|
|
289
|
+
params: [{ name: "value", type: "any", required: true }],
|
|
290
|
+
returnType: "number",
|
|
291
|
+
examples: ['len("hello")', "len([1,2,3])"]
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "trim",
|
|
295
|
+
description: "去除字符串首尾空格",
|
|
296
|
+
params: [{ name: "str", type: "string", required: true }],
|
|
297
|
+
returnType: "string",
|
|
298
|
+
examples: ['trim(" hello ")']
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "upper",
|
|
302
|
+
description: "转为大写",
|
|
303
|
+
params: [{ name: "str", type: "string", required: true }],
|
|
304
|
+
returnType: "string",
|
|
305
|
+
examples: ['upper("hello")']
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "lower",
|
|
309
|
+
description: "转为小写",
|
|
310
|
+
params: [{ name: "str", type: "string", required: true }],
|
|
311
|
+
returnType: "string",
|
|
312
|
+
examples: ['lower("HELLO")']
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: "substr",
|
|
316
|
+
description: "截取子字符串",
|
|
317
|
+
params: [
|
|
318
|
+
{ name: "str", type: "string", required: true },
|
|
319
|
+
{ name: "start", type: "number", required: true },
|
|
320
|
+
{ name: "length", type: "number", required: false }
|
|
321
|
+
],
|
|
322
|
+
returnType: "string",
|
|
323
|
+
examples: ['substr("hello", 0, 2)']
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: "concat",
|
|
327
|
+
description: "连接字符串",
|
|
328
|
+
params: [
|
|
329
|
+
{ name: "values", type: "any", required: true, description: "可变参数" }
|
|
330
|
+
],
|
|
331
|
+
returnType: "string",
|
|
332
|
+
examples: ['concat("a", "b", "c")']
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "replace",
|
|
336
|
+
description: "替换字符串",
|
|
337
|
+
params: [
|
|
338
|
+
{ name: "str", type: "string", required: true },
|
|
339
|
+
{ name: "search", type: "string", required: true },
|
|
340
|
+
{ name: "replacement", type: "string", required: true }
|
|
341
|
+
],
|
|
342
|
+
returnType: "string",
|
|
343
|
+
examples: ['replace("hello", "l", "x")']
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "split",
|
|
347
|
+
description: "分割字符串",
|
|
348
|
+
params: [
|
|
349
|
+
{ name: "str", type: "string", required: true },
|
|
350
|
+
{ name: "separator", type: "string", required: true }
|
|
351
|
+
],
|
|
352
|
+
returnType: "array",
|
|
353
|
+
examples: ['split("a,b,c", ",")']
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "join",
|
|
357
|
+
description: "连接数组为字符串",
|
|
358
|
+
params: [
|
|
359
|
+
{ name: "arr", type: "array", required: true },
|
|
360
|
+
{ name: "separator", type: "string", required: false, defaultValue: "," }
|
|
361
|
+
],
|
|
362
|
+
returnType: "string",
|
|
363
|
+
examples: ['join(["a","b","c"], "-")']
|
|
364
|
+
},
|
|
365
|
+
// 数字函数
|
|
366
|
+
{
|
|
367
|
+
name: "toNumber",
|
|
368
|
+
description: "转为数字",
|
|
369
|
+
params: [{ name: "value", type: "any", required: true }],
|
|
370
|
+
returnType: "number",
|
|
371
|
+
examples: ['toNumber("123")']
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "toString",
|
|
375
|
+
description: "转为字符串",
|
|
376
|
+
params: [{ name: "value", type: "any", required: true }],
|
|
377
|
+
returnType: "string",
|
|
378
|
+
examples: ["toString(123)"]
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "round",
|
|
382
|
+
description: "四舍五入",
|
|
383
|
+
params: [
|
|
384
|
+
{ name: "value", type: "number", required: true },
|
|
385
|
+
{ name: "decimals", type: "number", required: false, defaultValue: 0 }
|
|
386
|
+
],
|
|
387
|
+
returnType: "number",
|
|
388
|
+
examples: ["round(3.14159, 2)"]
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: "floor",
|
|
392
|
+
description: "向下取整",
|
|
393
|
+
params: [{ name: "value", type: "number", required: true }],
|
|
394
|
+
returnType: "number",
|
|
395
|
+
examples: ["floor(3.7)"]
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: "ceil",
|
|
399
|
+
description: "向上取整",
|
|
400
|
+
params: [{ name: "value", type: "number", required: true }],
|
|
401
|
+
returnType: "number",
|
|
402
|
+
examples: ["ceil(3.2)"]
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "abs",
|
|
406
|
+
description: "绝对值",
|
|
407
|
+
params: [{ name: "value", type: "number", required: true }],
|
|
408
|
+
returnType: "number",
|
|
409
|
+
examples: ["abs(-5)"]
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "min",
|
|
413
|
+
description: "最小值",
|
|
414
|
+
params: [
|
|
415
|
+
{ name: "values", type: "number", required: true, description: "可变参数" }
|
|
416
|
+
],
|
|
417
|
+
returnType: "number",
|
|
418
|
+
examples: ["min(1, 2, 3)"]
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "max",
|
|
422
|
+
description: "最大值",
|
|
423
|
+
params: [
|
|
424
|
+
{ name: "values", type: "number", required: true, description: "可变参数" }
|
|
425
|
+
],
|
|
426
|
+
returnType: "number",
|
|
427
|
+
examples: ["max(1, 2, 3)"]
|
|
428
|
+
},
|
|
429
|
+
// 日期函数
|
|
430
|
+
{
|
|
431
|
+
name: "now",
|
|
432
|
+
description: "当前时间戳(毫秒)",
|
|
433
|
+
params: [],
|
|
434
|
+
returnType: "number",
|
|
435
|
+
examples: ["now()"]
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
name: "dateFormat",
|
|
439
|
+
description: "格式化日期",
|
|
440
|
+
params: [
|
|
441
|
+
{ name: "timestamp", type: "number", required: true },
|
|
442
|
+
{ name: "format", type: "string", required: false, defaultValue: "YYYY-MM-DD" }
|
|
443
|
+
],
|
|
444
|
+
returnType: "string",
|
|
445
|
+
examples: ['dateFormat(now(), "YYYY-MM-DD HH:mm:ss")']
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "dateParse",
|
|
449
|
+
description: "解析日期字符串",
|
|
450
|
+
params: [
|
|
451
|
+
{ name: "dateStr", type: "string", required: true },
|
|
452
|
+
{ name: "format", type: "string", required: false }
|
|
453
|
+
],
|
|
454
|
+
returnType: "number",
|
|
455
|
+
examples: ['dateParse("2024-01-01")']
|
|
456
|
+
},
|
|
457
|
+
// 数组函数
|
|
458
|
+
{
|
|
459
|
+
name: "first",
|
|
460
|
+
description: "获取数组第一个元素",
|
|
461
|
+
params: [{ name: "arr", type: "array", required: true }],
|
|
462
|
+
returnType: "any",
|
|
463
|
+
examples: ["first([1,2,3])"]
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "last",
|
|
467
|
+
description: "获取数组最后一个元素",
|
|
468
|
+
params: [{ name: "arr", type: "array", required: true }],
|
|
469
|
+
returnType: "any",
|
|
470
|
+
examples: ["last([1,2,3])"]
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "includes",
|
|
474
|
+
description: "检查数组是否包含元素",
|
|
475
|
+
params: [
|
|
476
|
+
{ name: "arr", type: "array", required: true },
|
|
477
|
+
{ name: "value", type: "any", required: true }
|
|
478
|
+
],
|
|
479
|
+
returnType: "boolean",
|
|
480
|
+
examples: ["includes([1,2,3], 2)"]
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: "slice",
|
|
484
|
+
description: "截取数组",
|
|
485
|
+
params: [
|
|
486
|
+
{ name: "arr", type: "array", required: true },
|
|
487
|
+
{ name: "start", type: "number", required: true },
|
|
488
|
+
{ name: "end", type: "number", required: false }
|
|
489
|
+
],
|
|
490
|
+
returnType: "array",
|
|
491
|
+
examples: ["slice([1,2,3,4], 1, 3)"]
|
|
492
|
+
},
|
|
493
|
+
// 条件函数
|
|
494
|
+
{
|
|
495
|
+
name: "default",
|
|
496
|
+
description: "提供默认值",
|
|
497
|
+
params: [
|
|
498
|
+
{ name: "value", type: "any", required: true },
|
|
499
|
+
{ name: "defaultValue", type: "any", required: true }
|
|
500
|
+
],
|
|
501
|
+
returnType: "any",
|
|
502
|
+
examples: ['default(null, "fallback")']
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
name: "ifElse",
|
|
506
|
+
description: "条件判断",
|
|
507
|
+
params: [
|
|
508
|
+
{ name: "condition", type: "boolean", required: true },
|
|
509
|
+
{ name: "trueValue", type: "any", required: true },
|
|
510
|
+
{ name: "falseValue", type: "any", required: true }
|
|
511
|
+
],
|
|
512
|
+
returnType: "any",
|
|
513
|
+
examples: ['ifElse(age > 18, "成年", "未成年")']
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: "isEmpty",
|
|
517
|
+
description: "检查是否为空",
|
|
518
|
+
params: [{ name: "value", type: "any", required: true }],
|
|
519
|
+
returnType: "boolean",
|
|
520
|
+
examples: ['isEmpty("")', "isEmpty([])"]
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: "isNull",
|
|
524
|
+
description: "检查是否为 null 或 undefined",
|
|
525
|
+
params: [{ name: "value", type: "any", required: true }],
|
|
526
|
+
returnType: "boolean",
|
|
527
|
+
examples: ["isNull(null)"]
|
|
528
|
+
},
|
|
529
|
+
// JSON 函数
|
|
530
|
+
{
|
|
531
|
+
name: "jsonParse",
|
|
532
|
+
description: "解析 JSON 字符串",
|
|
533
|
+
params: [{ name: "str", type: "string", required: true }],
|
|
534
|
+
returnType: "any",
|
|
535
|
+
examples: [`jsonParse('{"a":1}')`]
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: "jsonStringify",
|
|
539
|
+
description: "序列化为 JSON",
|
|
540
|
+
params: [{ name: "value", type: "any", required: true }],
|
|
541
|
+
returnType: "string",
|
|
542
|
+
examples: ["jsonStringify({a:1})"]
|
|
543
|
+
}
|
|
544
|
+
];
|
|
545
|
+
var BUILTIN_FUNCTION_NAMES = new Set(BUILTIN_FUNCTIONS.map((f) => f.name));
|
|
546
|
+
function isExpressionBinding(value) {
|
|
547
|
+
return value !== null && typeof value === "object" && value.__isExpression === true && typeof value.expression === "string";
|
|
548
|
+
}
|
|
549
|
+
var migrations = [];
|
|
550
|
+
function registerMigration(migration) {
|
|
551
|
+
const exists = migrations.some(
|
|
552
|
+
(m) => m.from === migration.from && m.to === migration.to
|
|
553
|
+
);
|
|
554
|
+
if (exists) {
|
|
555
|
+
throw new Error(`Migration from ${migration.from} to ${migration.to} already exists`);
|
|
556
|
+
}
|
|
557
|
+
migrations.push(migration);
|
|
558
|
+
}
|
|
559
|
+
registerMigration({
|
|
560
|
+
from: "1.0.0",
|
|
561
|
+
to: "1.1.0",
|
|
562
|
+
description: "添加 Definition 版本绑定字段",
|
|
563
|
+
migrate: (data) => {
|
|
564
|
+
return {
|
|
565
|
+
...data,
|
|
566
|
+
actionDefVersionIds: data.actionDefVersionIds || [],
|
|
567
|
+
dataQueryVersionIds: data.dataQueryVersionIds || []
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
registerMigration({
|
|
572
|
+
from: "1.1.0",
|
|
573
|
+
to: "2.0.0",
|
|
574
|
+
description: "重构组件事件结构",
|
|
575
|
+
breaking: true,
|
|
576
|
+
migrate: (data) => {
|
|
577
|
+
return {
|
|
578
|
+
...data,
|
|
579
|
+
components: data.components.map((component) => {
|
|
580
|
+
var _a;
|
|
581
|
+
return {
|
|
582
|
+
...component,
|
|
583
|
+
// 示例:将 events 数组中的 actions 结构标准化
|
|
584
|
+
events: (_a = component.events) == null ? void 0 : _a.map((event) => ({
|
|
585
|
+
...event,
|
|
586
|
+
// 确保 enabled 字段存在
|
|
587
|
+
enabled: event.enabled ?? true
|
|
588
|
+
}))
|
|
589
|
+
};
|
|
590
|
+
})
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
var DjvlcRuntimeError = class extends Error {
|
|
595
|
+
constructor(code, message, details, traceId) {
|
|
596
|
+
super(message || ErrorMessages[code] || "Unknown error");
|
|
597
|
+
this.name = "DjvlcRuntimeError";
|
|
598
|
+
this.code = code;
|
|
599
|
+
this.details = details;
|
|
600
|
+
this.traceId = traceId;
|
|
601
|
+
this.timestamp = Date.now();
|
|
602
|
+
}
|
|
603
|
+
toJSON() {
|
|
604
|
+
return {
|
|
605
|
+
name: this.name,
|
|
606
|
+
code: this.code,
|
|
607
|
+
message: this.message,
|
|
608
|
+
details: this.details,
|
|
609
|
+
traceId: this.traceId,
|
|
610
|
+
timestamp: this.timestamp
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
var PageLoadError = class extends DjvlcRuntimeError {
|
|
615
|
+
constructor(message, details, traceId) {
|
|
616
|
+
super(ErrorCode.PAGE_NOT_FOUND, message, details, traceId);
|
|
617
|
+
this.name = "PageLoadError";
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
var ComponentLoadError = class extends DjvlcRuntimeError {
|
|
621
|
+
constructor(componentName, componentVersion, message, code = ErrorCode.COMPONENT_NOT_FOUND, details) {
|
|
622
|
+
super(code, message, { ...details, componentName, componentVersion });
|
|
623
|
+
this.name = "ComponentLoadError";
|
|
624
|
+
this.componentName = componentName;
|
|
625
|
+
this.componentVersion = componentVersion;
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
var IntegrityError = class extends DjvlcRuntimeError {
|
|
629
|
+
constructor(componentName, componentVersion, expectedHash, actualHash) {
|
|
630
|
+
super(
|
|
631
|
+
ErrorCode.COMPONENT_INTEGRITY_MISMATCH,
|
|
632
|
+
`Integrity check failed for ${componentName}@${componentVersion}`,
|
|
633
|
+
{ expectedHash, actualHash }
|
|
634
|
+
);
|
|
635
|
+
this.name = "IntegrityError";
|
|
636
|
+
this.componentName = componentName;
|
|
637
|
+
this.componentVersion = componentVersion;
|
|
638
|
+
this.expectedHash = expectedHash;
|
|
639
|
+
this.actualHash = actualHash;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
var ComponentBlockedError = class extends DjvlcRuntimeError {
|
|
643
|
+
constructor(componentName, componentVersion, reason) {
|
|
644
|
+
super(ErrorCode.COMPONENT_BLOCKED, `Component ${componentName}@${componentVersion} is blocked`, {
|
|
645
|
+
componentName,
|
|
646
|
+
componentVersion,
|
|
647
|
+
reason
|
|
648
|
+
});
|
|
649
|
+
this.name = "ComponentBlockedError";
|
|
650
|
+
this.componentName = componentName;
|
|
651
|
+
this.componentVersion = componentVersion;
|
|
652
|
+
this.reason = reason;
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
var ExpressionError = class extends DjvlcRuntimeError {
|
|
656
|
+
constructor(expression, message, position, details) {
|
|
657
|
+
super(ErrorCode.UNKNOWN, message, { ...details, expression, position });
|
|
658
|
+
this.name = "ExpressionError";
|
|
659
|
+
this.expression = expression;
|
|
660
|
+
this.position = position;
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
var PageLoader = class {
|
|
664
|
+
constructor(options) {
|
|
665
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
666
|
+
this.options = {
|
|
667
|
+
channel: "prod",
|
|
668
|
+
cache: { enabled: true, maxAge: 300 },
|
|
669
|
+
...options
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* 解析页面
|
|
674
|
+
* @param pageUid 页面 UID
|
|
675
|
+
* @param params 额外参数
|
|
676
|
+
*/
|
|
677
|
+
async resolve(pageUid, params) {
|
|
678
|
+
var _a, _b;
|
|
679
|
+
const cacheKey = this.getCacheKey(pageUid, params);
|
|
680
|
+
if ((_a = this.options.cache) == null ? void 0 : _a.enabled) {
|
|
681
|
+
const cached = this.cache.get(cacheKey);
|
|
682
|
+
if (cached && this.isCacheValid(cached.timestamp)) {
|
|
683
|
+
this.log("debug", `Page ${pageUid} loaded from cache`);
|
|
684
|
+
return cached.data;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const startTime = performance.now();
|
|
688
|
+
try {
|
|
689
|
+
const url = this.buildResolveUrl(pageUid, params);
|
|
690
|
+
const response = await fetch(url, {
|
|
691
|
+
method: "GET",
|
|
692
|
+
headers: this.buildHeaders()
|
|
693
|
+
});
|
|
694
|
+
if (!response.ok) {
|
|
695
|
+
throw new PageLoadError(
|
|
696
|
+
`Failed to resolve page: ${response.status} ${response.statusText}`,
|
|
697
|
+
{ pageUid, status: response.status }
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
const result = await response.json();
|
|
701
|
+
if (!this.isValidPageResolveResult(result)) {
|
|
702
|
+
throw new PageLoadError("Invalid page resolve response", { pageUid });
|
|
703
|
+
}
|
|
704
|
+
const data = result.data;
|
|
705
|
+
if ((_b = this.options.cache) == null ? void 0 : _b.enabled) {
|
|
706
|
+
this.cache.set(cacheKey, { data, timestamp: Date.now() });
|
|
707
|
+
}
|
|
708
|
+
const loadTime = performance.now() - startTime;
|
|
709
|
+
this.log("info", `Page ${pageUid} resolved in ${loadTime.toFixed(2)}ms`);
|
|
710
|
+
return data;
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if (error instanceof PageLoadError) {
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
throw new PageLoadError(
|
|
716
|
+
`Failed to resolve page: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
717
|
+
{ pageUid }
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* 预连接 API 服务器
|
|
723
|
+
*/
|
|
724
|
+
preconnect() {
|
|
725
|
+
const link = document.createElement("link");
|
|
726
|
+
link.rel = "preconnect";
|
|
727
|
+
link.href = this.options.apiBaseUrl;
|
|
728
|
+
document.head.appendChild(link);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* 清除缓存
|
|
732
|
+
*/
|
|
733
|
+
clearCache(pageUid) {
|
|
734
|
+
if (pageUid) {
|
|
735
|
+
for (const key of this.cache.keys()) {
|
|
736
|
+
if (key.startsWith(pageUid)) {
|
|
737
|
+
this.cache.delete(key);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
} else {
|
|
741
|
+
this.cache.clear();
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
buildResolveUrl(pageUid, params) {
|
|
745
|
+
const url = new URL(`${this.options.apiBaseUrl}/page/resolve`);
|
|
746
|
+
url.searchParams.set("pageUid", pageUid);
|
|
747
|
+
if (this.options.channel) {
|
|
748
|
+
url.searchParams.set("channel", this.options.channel);
|
|
749
|
+
}
|
|
750
|
+
if (this.options.previewToken) {
|
|
751
|
+
url.searchParams.set("previewToken", this.options.previewToken);
|
|
752
|
+
}
|
|
753
|
+
if (params == null ? void 0 : params.uid) {
|
|
754
|
+
url.searchParams.set("uid", params.uid);
|
|
755
|
+
}
|
|
756
|
+
if (params == null ? void 0 : params.deviceId) {
|
|
757
|
+
url.searchParams.set("deviceId", params.deviceId);
|
|
758
|
+
}
|
|
759
|
+
return url.toString();
|
|
760
|
+
}
|
|
761
|
+
buildHeaders() {
|
|
762
|
+
const headers = {
|
|
763
|
+
"Content-Type": "application/json",
|
|
764
|
+
...this.options.headers
|
|
765
|
+
};
|
|
766
|
+
if (this.options.authToken) {
|
|
767
|
+
headers["Authorization"] = `Bearer ${this.options.authToken}`;
|
|
768
|
+
}
|
|
769
|
+
return headers;
|
|
770
|
+
}
|
|
771
|
+
getCacheKey(pageUid, params) {
|
|
772
|
+
const parts = [pageUid, this.options.channel];
|
|
773
|
+
if (params == null ? void 0 : params.uid) parts.push(params.uid);
|
|
774
|
+
if (params == null ? void 0 : params.deviceId) parts.push(params.deviceId);
|
|
775
|
+
return parts.join(":");
|
|
776
|
+
}
|
|
777
|
+
isCacheValid(timestamp) {
|
|
778
|
+
var _a;
|
|
779
|
+
const maxAge = (((_a = this.options.cache) == null ? void 0 : _a.maxAge) ?? 300) * 1e3;
|
|
780
|
+
return Date.now() - timestamp < maxAge;
|
|
781
|
+
}
|
|
782
|
+
isValidPageResolveResult(result) {
|
|
783
|
+
if (!result || typeof result !== "object") return false;
|
|
784
|
+
const r = result;
|
|
785
|
+
if (!r.data || typeof r.data !== "object") return false;
|
|
786
|
+
const data = r.data;
|
|
787
|
+
return typeof data.pageUid === "string" && typeof data.pageVersionId === "string" && data.pageJson !== void 0 && data.manifest !== void 0;
|
|
788
|
+
}
|
|
789
|
+
log(level, message) {
|
|
790
|
+
if (this.options.logger) {
|
|
791
|
+
this.options.logger[level](message);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
var ComponentLoader = class {
|
|
796
|
+
constructor(options) {
|
|
797
|
+
this.loadedComponents = /* @__PURE__ */ new Map();
|
|
798
|
+
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
799
|
+
this.options = {
|
|
800
|
+
enableSRI: true,
|
|
801
|
+
concurrency: 4,
|
|
802
|
+
timeout: 3e4,
|
|
803
|
+
blockedComponents: [],
|
|
804
|
+
...options
|
|
805
|
+
};
|
|
806
|
+
this.blockedSet = new Set(this.options.blockedComponents);
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* 加载单个组件
|
|
810
|
+
*/
|
|
811
|
+
async load(item) {
|
|
812
|
+
const key = this.getComponentKey(item.name, item.version);
|
|
813
|
+
if (this.isBlocked(item.name, item.version)) {
|
|
814
|
+
throw new ComponentBlockedError(item.name, item.version, "Component is blocked");
|
|
815
|
+
}
|
|
816
|
+
const loaded = this.loadedComponents.get(key);
|
|
817
|
+
if (loaded) {
|
|
818
|
+
return loaded;
|
|
819
|
+
}
|
|
820
|
+
const loading = this.loadingPromises.get(key);
|
|
821
|
+
if (loading) {
|
|
822
|
+
return loading;
|
|
823
|
+
}
|
|
824
|
+
const loadPromise = this.loadComponent(item);
|
|
825
|
+
this.loadingPromises.set(key, loadPromise);
|
|
826
|
+
try {
|
|
827
|
+
const component = await loadPromise;
|
|
828
|
+
this.loadedComponents.set(key, component);
|
|
829
|
+
return component;
|
|
830
|
+
} finally {
|
|
831
|
+
this.loadingPromises.delete(key);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* 加载 Manifest 中的所有组件
|
|
836
|
+
*/
|
|
837
|
+
async loadAll(manifest) {
|
|
838
|
+
const results = /* @__PURE__ */ new Map();
|
|
839
|
+
const { concurrency = 4 } = this.options;
|
|
840
|
+
const components = manifest.components;
|
|
841
|
+
for (let i = 0; i < components.length; i += concurrency) {
|
|
842
|
+
const batch = components.slice(i, i + concurrency);
|
|
843
|
+
const batchPromises = batch.map(async (item) => {
|
|
844
|
+
const key = this.getComponentKey(item.name, item.version);
|
|
845
|
+
const startTime = performance.now();
|
|
846
|
+
try {
|
|
847
|
+
const loaded = await this.load(item);
|
|
848
|
+
results.set(key, {
|
|
849
|
+
name: item.name,
|
|
850
|
+
version: item.version,
|
|
851
|
+
status: "loaded",
|
|
852
|
+
component: loaded.Component,
|
|
853
|
+
loadTime: performance.now() - startTime
|
|
854
|
+
});
|
|
855
|
+
} catch (error) {
|
|
856
|
+
const status = error instanceof ComponentBlockedError ? "blocked" : "failed";
|
|
857
|
+
results.set(key, {
|
|
858
|
+
name: item.name,
|
|
859
|
+
version: item.version,
|
|
860
|
+
status,
|
|
861
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
862
|
+
loadTime: performance.now() - startTime
|
|
863
|
+
});
|
|
864
|
+
if (item.critical) {
|
|
865
|
+
throw error;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
await Promise.all(batchPromises);
|
|
870
|
+
}
|
|
871
|
+
return results;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* 预加载组件
|
|
875
|
+
*/
|
|
876
|
+
preload(items) {
|
|
877
|
+
items.forEach((item) => {
|
|
878
|
+
const link = document.createElement("link");
|
|
879
|
+
link.rel = "preload";
|
|
880
|
+
link.as = "script";
|
|
881
|
+
link.href = this.resolveUrl(item.entryUrl);
|
|
882
|
+
if (this.options.enableSRI && item.integrity) {
|
|
883
|
+
link.integrity = item.integrity;
|
|
884
|
+
link.crossOrigin = "anonymous";
|
|
885
|
+
}
|
|
886
|
+
document.head.appendChild(link);
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* 检查组件是否已加载
|
|
891
|
+
*/
|
|
892
|
+
isLoaded(name, version) {
|
|
893
|
+
return this.loadedComponents.has(this.getComponentKey(name, version));
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* 获取已加载的组件
|
|
897
|
+
*/
|
|
898
|
+
get(name, version) {
|
|
899
|
+
return this.loadedComponents.get(this.getComponentKey(name, version));
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* 检查组件是否被阻断
|
|
903
|
+
*/
|
|
904
|
+
isBlocked(name, version) {
|
|
905
|
+
return this.blockedSet.has(`${name}@${version}`) || this.blockedSet.has(name);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* 更新阻断列表
|
|
909
|
+
*/
|
|
910
|
+
updateBlockedList(blocked) {
|
|
911
|
+
this.blockedSet = new Set(blocked);
|
|
912
|
+
}
|
|
913
|
+
async loadComponent(item) {
|
|
914
|
+
const startTime = performance.now();
|
|
915
|
+
const url = this.resolveUrl(item.entryUrl);
|
|
916
|
+
this.log("debug", `Loading component ${item.name}@${item.version}`);
|
|
917
|
+
try {
|
|
918
|
+
const response = await this.fetchWithTimeout(url);
|
|
919
|
+
if (!response.ok) {
|
|
920
|
+
throw new ComponentLoadError(
|
|
921
|
+
item.name,
|
|
922
|
+
item.version,
|
|
923
|
+
`Failed to fetch component: ${response.status} ${response.statusText}`
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
const content = await response.text();
|
|
927
|
+
if (this.options.enableSRI && item.integrity) {
|
|
928
|
+
await this.validateIntegrity(item, content);
|
|
929
|
+
}
|
|
930
|
+
const Component = await this.executeScript(content, item);
|
|
931
|
+
const loadTime = performance.now() - startTime;
|
|
932
|
+
this.log("info", `Component ${item.name}@${item.version} loaded in ${loadTime.toFixed(2)}ms`);
|
|
933
|
+
return {
|
|
934
|
+
name: item.name,
|
|
935
|
+
version: item.version,
|
|
936
|
+
Component,
|
|
937
|
+
loadTime
|
|
938
|
+
};
|
|
939
|
+
} catch (error) {
|
|
940
|
+
if (error instanceof ComponentLoadError || error instanceof IntegrityError || error instanceof ComponentBlockedError) {
|
|
941
|
+
throw error;
|
|
942
|
+
}
|
|
943
|
+
throw new ComponentLoadError(
|
|
944
|
+
item.name,
|
|
945
|
+
item.version,
|
|
946
|
+
`Failed to load component: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
async fetchWithTimeout(url) {
|
|
951
|
+
const controller = new AbortController();
|
|
952
|
+
const timeout = setTimeout(() => controller.abort(), this.options.timeout);
|
|
953
|
+
try {
|
|
954
|
+
return await fetch(url, {
|
|
955
|
+
signal: controller.signal,
|
|
956
|
+
credentials: "omit"
|
|
957
|
+
});
|
|
958
|
+
} finally {
|
|
959
|
+
clearTimeout(timeout);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
async validateIntegrity(item, content) {
|
|
963
|
+
if (!item.integrity) return;
|
|
964
|
+
const [algorithm, expectedHash] = item.integrity.split("-");
|
|
965
|
+
if (!algorithm || !expectedHash) {
|
|
966
|
+
throw new IntegrityError(item.name, item.version, item.integrity, "Invalid format");
|
|
967
|
+
}
|
|
968
|
+
const hashBuffer = await crypto.subtle.digest(
|
|
969
|
+
algorithm.toUpperCase(),
|
|
970
|
+
new TextEncoder().encode(content)
|
|
971
|
+
);
|
|
972
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
973
|
+
const actualHash = btoa(String.fromCharCode(...hashArray));
|
|
974
|
+
if (actualHash !== expectedHash) {
|
|
975
|
+
throw new IntegrityError(item.name, item.version, expectedHash, actualHash);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
async executeScript(content, item) {
|
|
979
|
+
const blob = new Blob([content], { type: "application/javascript" });
|
|
980
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
981
|
+
try {
|
|
982
|
+
const module = await import(
|
|
983
|
+
/* @vite-ignore */
|
|
984
|
+
blobUrl
|
|
985
|
+
);
|
|
986
|
+
const Component = module.default || module[item.name] || module.Component;
|
|
987
|
+
if (!Component) {
|
|
988
|
+
throw new ComponentLoadError(
|
|
989
|
+
item.name,
|
|
990
|
+
item.version,
|
|
991
|
+
"Component module does not export a valid component"
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
return Component;
|
|
995
|
+
} finally {
|
|
996
|
+
URL.revokeObjectURL(blobUrl);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
resolveUrl(entryUrl) {
|
|
1000
|
+
if (entryUrl.startsWith("http://") || entryUrl.startsWith("https://")) {
|
|
1001
|
+
return entryUrl;
|
|
1002
|
+
}
|
|
1003
|
+
return `${this.options.cdnBaseUrl}/${entryUrl.replace(/^\//, "")}`;
|
|
1004
|
+
}
|
|
1005
|
+
getComponentKey(name, version) {
|
|
1006
|
+
return `${name}@${version}`;
|
|
1007
|
+
}
|
|
1008
|
+
log(level, message) {
|
|
1009
|
+
if (this.options.logger) {
|
|
1010
|
+
this.options.logger[level](message);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
var AssetLoader = class {
|
|
1015
|
+
constructor(options) {
|
|
1016
|
+
this.preconnectedHosts = /* @__PURE__ */ new Set();
|
|
1017
|
+
this.preloadedAssets = /* @__PURE__ */ new Set();
|
|
1018
|
+
this.options = options;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* 预连接所有域名
|
|
1022
|
+
*/
|
|
1023
|
+
preconnectAll() {
|
|
1024
|
+
const allHosts = [...this.options.cdnHosts, ...this.options.apiHosts];
|
|
1025
|
+
allHosts.forEach((host) => this.preconnect(host));
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* 预连接单个域名
|
|
1029
|
+
*/
|
|
1030
|
+
preconnect(host) {
|
|
1031
|
+
if (this.preconnectedHosts.has(host)) return;
|
|
1032
|
+
const link = document.createElement("link");
|
|
1033
|
+
link.rel = "preconnect";
|
|
1034
|
+
link.href = host.startsWith("http") ? host : `https://${host}`;
|
|
1035
|
+
link.crossOrigin = "anonymous";
|
|
1036
|
+
document.head.appendChild(link);
|
|
1037
|
+
this.preconnectedHosts.add(host);
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* DNS 预解析
|
|
1041
|
+
*/
|
|
1042
|
+
dnsPrefetch(host) {
|
|
1043
|
+
const link = document.createElement("link");
|
|
1044
|
+
link.rel = "dns-prefetch";
|
|
1045
|
+
link.href = host.startsWith("http") ? host : `https://${host}`;
|
|
1046
|
+
document.head.appendChild(link);
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* 预加载脚本
|
|
1050
|
+
*/
|
|
1051
|
+
preloadScript(url, integrity) {
|
|
1052
|
+
if (this.preloadedAssets.has(url)) return;
|
|
1053
|
+
const link = document.createElement("link");
|
|
1054
|
+
link.rel = "preload";
|
|
1055
|
+
link.as = "script";
|
|
1056
|
+
link.href = url;
|
|
1057
|
+
if (integrity) {
|
|
1058
|
+
link.integrity = integrity;
|
|
1059
|
+
link.crossOrigin = "anonymous";
|
|
1060
|
+
}
|
|
1061
|
+
document.head.appendChild(link);
|
|
1062
|
+
this.preloadedAssets.add(url);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* 预加载样式
|
|
1066
|
+
*/
|
|
1067
|
+
preloadStyle(url, integrity) {
|
|
1068
|
+
if (this.preloadedAssets.has(url)) return;
|
|
1069
|
+
const link = document.createElement("link");
|
|
1070
|
+
link.rel = "preload";
|
|
1071
|
+
link.as = "style";
|
|
1072
|
+
link.href = url;
|
|
1073
|
+
if (integrity) {
|
|
1074
|
+
link.integrity = integrity;
|
|
1075
|
+
link.crossOrigin = "anonymous";
|
|
1076
|
+
}
|
|
1077
|
+
document.head.appendChild(link);
|
|
1078
|
+
this.preloadedAssets.add(url);
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* 预加载图片
|
|
1082
|
+
*/
|
|
1083
|
+
preloadImage(url) {
|
|
1084
|
+
if (this.preloadedAssets.has(url)) return;
|
|
1085
|
+
const link = document.createElement("link");
|
|
1086
|
+
link.rel = "preload";
|
|
1087
|
+
link.as = "image";
|
|
1088
|
+
link.href = url;
|
|
1089
|
+
document.head.appendChild(link);
|
|
1090
|
+
this.preloadedAssets.add(url);
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* 预获取资源(低优先级)
|
|
1094
|
+
*/
|
|
1095
|
+
prefetch(url, as) {
|
|
1096
|
+
const link = document.createElement("link");
|
|
1097
|
+
link.rel = "prefetch";
|
|
1098
|
+
link.href = url;
|
|
1099
|
+
if (as) link.as = as;
|
|
1100
|
+
document.head.appendChild(link);
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* 加载样式表
|
|
1104
|
+
*/
|
|
1105
|
+
loadStylesheet(url, integrity) {
|
|
1106
|
+
return new Promise((resolve, reject) => {
|
|
1107
|
+
const link = document.createElement("link");
|
|
1108
|
+
link.rel = "stylesheet";
|
|
1109
|
+
link.href = url;
|
|
1110
|
+
if (integrity) {
|
|
1111
|
+
link.integrity = integrity;
|
|
1112
|
+
link.crossOrigin = "anonymous";
|
|
1113
|
+
}
|
|
1114
|
+
link.onload = () => resolve();
|
|
1115
|
+
link.onerror = () => reject(new Error(`Failed to load stylesheet: ${url}`));
|
|
1116
|
+
document.head.appendChild(link);
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* 加载脚本
|
|
1121
|
+
*/
|
|
1122
|
+
loadScript(url, integrity) {
|
|
1123
|
+
return new Promise((resolve, reject) => {
|
|
1124
|
+
const script = document.createElement("script");
|
|
1125
|
+
script.src = url;
|
|
1126
|
+
script.async = true;
|
|
1127
|
+
if (integrity) {
|
|
1128
|
+
script.integrity = integrity;
|
|
1129
|
+
script.crossOrigin = "anonymous";
|
|
1130
|
+
}
|
|
1131
|
+
script.onload = () => resolve();
|
|
1132
|
+
script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
|
|
1133
|
+
document.body.appendChild(script);
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
var StateManager = class {
|
|
1138
|
+
constructor() {
|
|
1139
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1140
|
+
this.state = this.createInitialState();
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* 获取当前状态
|
|
1144
|
+
*/
|
|
1145
|
+
getState() {
|
|
1146
|
+
return this.state;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* 获取当前阶段
|
|
1150
|
+
*/
|
|
1151
|
+
getPhase() {
|
|
1152
|
+
return this.state.phase;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* 设置阶段
|
|
1156
|
+
*/
|
|
1157
|
+
setPhase(phase) {
|
|
1158
|
+
this.setState({ phase });
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* 设置页面数据
|
|
1162
|
+
*/
|
|
1163
|
+
setPage(page) {
|
|
1164
|
+
this.setState({
|
|
1165
|
+
page,
|
|
1166
|
+
variables: page.pageJson.page.variables || {}
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* 设置错误
|
|
1171
|
+
*/
|
|
1172
|
+
setError(error) {
|
|
1173
|
+
this.setState({
|
|
1174
|
+
phase: "error",
|
|
1175
|
+
error
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* 获取变量值
|
|
1180
|
+
*/
|
|
1181
|
+
getVariable(key) {
|
|
1182
|
+
return this.state.variables[key];
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* 设置变量值
|
|
1186
|
+
*/
|
|
1187
|
+
setVariable(key, value) {
|
|
1188
|
+
this.setState({
|
|
1189
|
+
variables: {
|
|
1190
|
+
...this.state.variables,
|
|
1191
|
+
[key]: value
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* 批量设置变量
|
|
1197
|
+
*/
|
|
1198
|
+
setVariables(variables) {
|
|
1199
|
+
this.setState({
|
|
1200
|
+
variables: {
|
|
1201
|
+
...this.state.variables,
|
|
1202
|
+
...variables
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* 获取查询结果
|
|
1208
|
+
*/
|
|
1209
|
+
getQuery(queryId) {
|
|
1210
|
+
return this.state.queries[queryId];
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* 设置查询结果
|
|
1214
|
+
*/
|
|
1215
|
+
setQuery(queryId, data) {
|
|
1216
|
+
this.setState({
|
|
1217
|
+
queries: {
|
|
1218
|
+
...this.state.queries,
|
|
1219
|
+
[queryId]: data
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* 更新组件加载状态
|
|
1225
|
+
*/
|
|
1226
|
+
setComponentStatus(key, result) {
|
|
1227
|
+
const components = new Map(this.state.components);
|
|
1228
|
+
components.set(key, result);
|
|
1229
|
+
this.setState({ components });
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* 标记为已销毁
|
|
1233
|
+
*/
|
|
1234
|
+
setDestroyed() {
|
|
1235
|
+
this.setState({ destroyed: true });
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* 订阅状态变更
|
|
1239
|
+
*/
|
|
1240
|
+
subscribe(listener) {
|
|
1241
|
+
this.listeners.add(listener);
|
|
1242
|
+
return () => {
|
|
1243
|
+
this.listeners.delete(listener);
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* 重置状态
|
|
1248
|
+
*/
|
|
1249
|
+
reset() {
|
|
1250
|
+
this.state = this.createInitialState();
|
|
1251
|
+
this.notifyListeners();
|
|
1252
|
+
}
|
|
1253
|
+
setState(partial) {
|
|
1254
|
+
this.state = { ...this.state, ...partial };
|
|
1255
|
+
this.notifyListeners();
|
|
1256
|
+
}
|
|
1257
|
+
notifyListeners() {
|
|
1258
|
+
this.listeners.forEach((listener) => {
|
|
1259
|
+
try {
|
|
1260
|
+
listener(this.state);
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
console.error("State listener error:", error);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
createInitialState() {
|
|
1267
|
+
return {
|
|
1268
|
+
phase: "idle",
|
|
1269
|
+
page: null,
|
|
1270
|
+
variables: {},
|
|
1271
|
+
queries: {},
|
|
1272
|
+
components: /* @__PURE__ */ new Map(),
|
|
1273
|
+
error: null,
|
|
1274
|
+
destroyed: false
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
var EventBus = class {
|
|
1279
|
+
constructor(options = {}) {
|
|
1280
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
1281
|
+
this.options = {
|
|
1282
|
+
debug: false,
|
|
1283
|
+
maxListeners: 100,
|
|
1284
|
+
...options
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* 发送事件
|
|
1289
|
+
*/
|
|
1290
|
+
emit(event) {
|
|
1291
|
+
if (this.options.debug) {
|
|
1292
|
+
this.log("debug", `Event emitted: ${event.type}`, event);
|
|
1293
|
+
}
|
|
1294
|
+
const handlers = this.handlers.get(event.type);
|
|
1295
|
+
if (!handlers) return;
|
|
1296
|
+
handlers.forEach((handler) => {
|
|
1297
|
+
try {
|
|
1298
|
+
handler(event);
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
this.log("error", `Error in event handler for ${event.type}:`, error);
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* 订阅事件
|
|
1306
|
+
*/
|
|
1307
|
+
on(type, handler) {
|
|
1308
|
+
let handlers = this.handlers.get(type);
|
|
1309
|
+
if (!handlers) {
|
|
1310
|
+
handlers = /* @__PURE__ */ new Set();
|
|
1311
|
+
this.handlers.set(type, handlers);
|
|
1312
|
+
}
|
|
1313
|
+
if (handlers.size >= (this.options.maxListeners ?? 100)) {
|
|
1314
|
+
this.log("warn", `Max listeners (${this.options.maxListeners}) reached for event: ${type}`);
|
|
1315
|
+
}
|
|
1316
|
+
handlers.add(handler);
|
|
1317
|
+
return () => {
|
|
1318
|
+
handlers == null ? void 0 : handlers.delete(handler);
|
|
1319
|
+
if ((handlers == null ? void 0 : handlers.size) === 0) {
|
|
1320
|
+
this.handlers.delete(type);
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* 取消订阅
|
|
1326
|
+
*/
|
|
1327
|
+
off(type, handler) {
|
|
1328
|
+
const handlers = this.handlers.get(type);
|
|
1329
|
+
if (handlers) {
|
|
1330
|
+
handlers.delete(handler);
|
|
1331
|
+
if (handlers.size === 0) {
|
|
1332
|
+
this.handlers.delete(type);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* 一次性订阅
|
|
1338
|
+
*/
|
|
1339
|
+
once(type, handler) {
|
|
1340
|
+
const wrappedHandler = (event) => {
|
|
1341
|
+
unsubscribe();
|
|
1342
|
+
handler(event);
|
|
1343
|
+
};
|
|
1344
|
+
const unsubscribe = this.on(type, wrappedHandler);
|
|
1345
|
+
return unsubscribe;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* 清除所有监听器
|
|
1349
|
+
*/
|
|
1350
|
+
clear(type) {
|
|
1351
|
+
if (type) {
|
|
1352
|
+
this.handlers.delete(type);
|
|
1353
|
+
} else {
|
|
1354
|
+
this.handlers.clear();
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* 获取监听器数量
|
|
1359
|
+
*/
|
|
1360
|
+
listenerCount(type) {
|
|
1361
|
+
var _a;
|
|
1362
|
+
return ((_a = this.handlers.get(type)) == null ? void 0 : _a.size) ?? 0;
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* 创建事件
|
|
1366
|
+
*/
|
|
1367
|
+
static createEvent(type, data, traceId) {
|
|
1368
|
+
return {
|
|
1369
|
+
type,
|
|
1370
|
+
data,
|
|
1371
|
+
timestamp: Date.now(),
|
|
1372
|
+
traceId
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
log(level, message, ...args) {
|
|
1376
|
+
if (this.options.logger) {
|
|
1377
|
+
this.options.logger[level](message, ...args);
|
|
1378
|
+
} else if (this.options.debug) {
|
|
1379
|
+
console[level](`[EventBus] ${message}`, ...args);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
var Lexer = class {
|
|
1384
|
+
constructor(input) {
|
|
1385
|
+
this.pos = 0;
|
|
1386
|
+
this.tokens = [];
|
|
1387
|
+
this.input = input;
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* 分析表达式,返回 Token 列表
|
|
1391
|
+
*/
|
|
1392
|
+
tokenize() {
|
|
1393
|
+
this.pos = 0;
|
|
1394
|
+
this.tokens = [];
|
|
1395
|
+
while (this.pos < this.input.length) {
|
|
1396
|
+
this.skipWhitespace();
|
|
1397
|
+
if (this.pos >= this.input.length) break;
|
|
1398
|
+
const token = this.readToken();
|
|
1399
|
+
if (token) {
|
|
1400
|
+
this.tokens.push(token);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
this.tokens.push({
|
|
1404
|
+
type: "EOF",
|
|
1405
|
+
value: null,
|
|
1406
|
+
start: this.input.length,
|
|
1407
|
+
end: this.input.length
|
|
1408
|
+
});
|
|
1409
|
+
return this.tokens;
|
|
1410
|
+
}
|
|
1411
|
+
readToken() {
|
|
1412
|
+
const char = this.input[this.pos];
|
|
1413
|
+
const start = this.pos;
|
|
1414
|
+
if (this.isDigit(char) || char === "-" && this.isDigit(this.peek(1))) {
|
|
1415
|
+
return this.readNumber();
|
|
1416
|
+
}
|
|
1417
|
+
if (char === '"' || char === "'") {
|
|
1418
|
+
return this.readString(char);
|
|
1419
|
+
}
|
|
1420
|
+
if (this.isIdentifierStart(char)) {
|
|
1421
|
+
return this.readIdentifier();
|
|
1422
|
+
}
|
|
1423
|
+
const operator = this.readOperator();
|
|
1424
|
+
if (operator) {
|
|
1425
|
+
return operator;
|
|
1426
|
+
}
|
|
1427
|
+
switch (char) {
|
|
1428
|
+
case ".":
|
|
1429
|
+
this.pos++;
|
|
1430
|
+
return { type: "DOT", value: ".", start, end: this.pos };
|
|
1431
|
+
case "[":
|
|
1432
|
+
this.pos++;
|
|
1433
|
+
return { type: "LBRACKET", value: "[", start, end: this.pos };
|
|
1434
|
+
case "]":
|
|
1435
|
+
this.pos++;
|
|
1436
|
+
return { type: "RBRACKET", value: "]", start, end: this.pos };
|
|
1437
|
+
case "(":
|
|
1438
|
+
this.pos++;
|
|
1439
|
+
return { type: "LPAREN", value: "(", start, end: this.pos };
|
|
1440
|
+
case ")":
|
|
1441
|
+
this.pos++;
|
|
1442
|
+
return { type: "RPAREN", value: ")", start, end: this.pos };
|
|
1443
|
+
case ",":
|
|
1444
|
+
this.pos++;
|
|
1445
|
+
return { type: "COMMA", value: ",", start, end: this.pos };
|
|
1446
|
+
case "?":
|
|
1447
|
+
this.pos++;
|
|
1448
|
+
return { type: "QUESTION", value: "?", start, end: this.pos };
|
|
1449
|
+
case ":":
|
|
1450
|
+
this.pos++;
|
|
1451
|
+
return { type: "COLON", value: ":", start, end: this.pos };
|
|
1452
|
+
}
|
|
1453
|
+
throw new Error(`Unexpected character '${char}' at position ${this.pos}`);
|
|
1454
|
+
}
|
|
1455
|
+
readNumber() {
|
|
1456
|
+
const start = this.pos;
|
|
1457
|
+
let value = "";
|
|
1458
|
+
if (this.input[this.pos] === "-") {
|
|
1459
|
+
value += "-";
|
|
1460
|
+
this.pos++;
|
|
1461
|
+
}
|
|
1462
|
+
while (this.isDigit(this.input[this.pos])) {
|
|
1463
|
+
value += this.input[this.pos];
|
|
1464
|
+
this.pos++;
|
|
1465
|
+
}
|
|
1466
|
+
if (this.input[this.pos] === "." && this.isDigit(this.peek(1))) {
|
|
1467
|
+
value += ".";
|
|
1468
|
+
this.pos++;
|
|
1469
|
+
while (this.isDigit(this.input[this.pos])) {
|
|
1470
|
+
value += this.input[this.pos];
|
|
1471
|
+
this.pos++;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return {
|
|
1475
|
+
type: "NUMBER",
|
|
1476
|
+
value: parseFloat(value),
|
|
1477
|
+
start,
|
|
1478
|
+
end: this.pos
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
readString(quote) {
|
|
1482
|
+
const start = this.pos;
|
|
1483
|
+
this.pos++;
|
|
1484
|
+
let value = "";
|
|
1485
|
+
while (this.pos < this.input.length && this.input[this.pos] !== quote) {
|
|
1486
|
+
if (this.input[this.pos] === "\\") {
|
|
1487
|
+
this.pos++;
|
|
1488
|
+
const escaped = this.input[this.pos];
|
|
1489
|
+
switch (escaped) {
|
|
1490
|
+
case "n":
|
|
1491
|
+
value += "\n";
|
|
1492
|
+
break;
|
|
1493
|
+
case "t":
|
|
1494
|
+
value += " ";
|
|
1495
|
+
break;
|
|
1496
|
+
case "r":
|
|
1497
|
+
value += "\r";
|
|
1498
|
+
break;
|
|
1499
|
+
case "\\":
|
|
1500
|
+
value += "\\";
|
|
1501
|
+
break;
|
|
1502
|
+
case '"':
|
|
1503
|
+
value += '"';
|
|
1504
|
+
break;
|
|
1505
|
+
case "'":
|
|
1506
|
+
value += "'";
|
|
1507
|
+
break;
|
|
1508
|
+
default:
|
|
1509
|
+
value += escaped;
|
|
1510
|
+
}
|
|
1511
|
+
} else {
|
|
1512
|
+
value += this.input[this.pos];
|
|
1513
|
+
}
|
|
1514
|
+
this.pos++;
|
|
1515
|
+
}
|
|
1516
|
+
if (this.input[this.pos] !== quote) {
|
|
1517
|
+
throw new Error(`Unterminated string at position ${start}`);
|
|
1518
|
+
}
|
|
1519
|
+
this.pos++;
|
|
1520
|
+
return {
|
|
1521
|
+
type: "STRING",
|
|
1522
|
+
value,
|
|
1523
|
+
start,
|
|
1524
|
+
end: this.pos
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
readIdentifier() {
|
|
1528
|
+
const start = this.pos;
|
|
1529
|
+
let value = "";
|
|
1530
|
+
while (this.pos < this.input.length && this.isIdentifierChar(this.input[this.pos])) {
|
|
1531
|
+
value += this.input[this.pos];
|
|
1532
|
+
this.pos++;
|
|
1533
|
+
}
|
|
1534
|
+
if (value === "true") {
|
|
1535
|
+
return { type: "BOOLEAN", value: true, start, end: this.pos };
|
|
1536
|
+
}
|
|
1537
|
+
if (value === "false") {
|
|
1538
|
+
return { type: "BOOLEAN", value: false, start, end: this.pos };
|
|
1539
|
+
}
|
|
1540
|
+
if (value === "null") {
|
|
1541
|
+
return { type: "NULL", value: null, start, end: this.pos };
|
|
1542
|
+
}
|
|
1543
|
+
return { type: "IDENTIFIER", value, start, end: this.pos };
|
|
1544
|
+
}
|
|
1545
|
+
readOperator() {
|
|
1546
|
+
const start = this.pos;
|
|
1547
|
+
const twoChar = this.input.slice(this.pos, this.pos + 2);
|
|
1548
|
+
const oneChar = this.input[this.pos];
|
|
1549
|
+
const twoCharOps = ["==", "!=", ">=", "<=", "&&", "||", "??"];
|
|
1550
|
+
if (twoCharOps.includes(twoChar)) {
|
|
1551
|
+
this.pos += 2;
|
|
1552
|
+
return { type: "OPERATOR", value: twoChar, start, end: this.pos };
|
|
1553
|
+
}
|
|
1554
|
+
const oneCharOps = ["+", "-", "*", "/", "%", ">", "<", "!"];
|
|
1555
|
+
if (oneCharOps.includes(oneChar)) {
|
|
1556
|
+
this.pos++;
|
|
1557
|
+
return { type: "OPERATOR", value: oneChar, start, end: this.pos };
|
|
1558
|
+
}
|
|
1559
|
+
return null;
|
|
1560
|
+
}
|
|
1561
|
+
skipWhitespace() {
|
|
1562
|
+
while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) {
|
|
1563
|
+
this.pos++;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
isDigit(char) {
|
|
1567
|
+
return /[0-9]/.test(char);
|
|
1568
|
+
}
|
|
1569
|
+
isIdentifierStart(char) {
|
|
1570
|
+
return /[a-zA-Z_$]/.test(char);
|
|
1571
|
+
}
|
|
1572
|
+
isIdentifierChar(char) {
|
|
1573
|
+
return /[a-zA-Z0-9_$]/.test(char);
|
|
1574
|
+
}
|
|
1575
|
+
peek(offset = 1) {
|
|
1576
|
+
return this.input[this.pos + offset] || "";
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
var PRECEDENCE = {
|
|
1580
|
+
"||": 1,
|
|
1581
|
+
"??": 1,
|
|
1582
|
+
"&&": 2,
|
|
1583
|
+
"==": 3,
|
|
1584
|
+
"!=": 3,
|
|
1585
|
+
"<": 4,
|
|
1586
|
+
">": 4,
|
|
1587
|
+
"<=": 4,
|
|
1588
|
+
">=": 4,
|
|
1589
|
+
"+": 5,
|
|
1590
|
+
"-": 5,
|
|
1591
|
+
"*": 6,
|
|
1592
|
+
"/": 6,
|
|
1593
|
+
"%": 6
|
|
1594
|
+
};
|
|
1595
|
+
var Parser = class {
|
|
1596
|
+
constructor(tokens) {
|
|
1597
|
+
this.pos = 0;
|
|
1598
|
+
this.tokens = tokens;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* 解析表达式
|
|
1602
|
+
*/
|
|
1603
|
+
parse() {
|
|
1604
|
+
const result = this.parseExpression();
|
|
1605
|
+
if (this.current().type !== "EOF") {
|
|
1606
|
+
throw new Error(`Unexpected token '${this.current().value}' at position ${this.current().start}`);
|
|
1607
|
+
}
|
|
1608
|
+
return result;
|
|
1609
|
+
}
|
|
1610
|
+
parseExpression() {
|
|
1611
|
+
return this.parseTernary();
|
|
1612
|
+
}
|
|
1613
|
+
parseTernary() {
|
|
1614
|
+
const test = this.parseBinary(0);
|
|
1615
|
+
if (this.current().type === "QUESTION") {
|
|
1616
|
+
this.advance();
|
|
1617
|
+
const consequent = this.parseExpression();
|
|
1618
|
+
this.expect("COLON");
|
|
1619
|
+
const alternate = this.parseExpression();
|
|
1620
|
+
return {
|
|
1621
|
+
type: "conditional",
|
|
1622
|
+
test,
|
|
1623
|
+
consequent,
|
|
1624
|
+
alternate,
|
|
1625
|
+
raw: void 0
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
return test;
|
|
1629
|
+
}
|
|
1630
|
+
parseBinary(minPrecedence) {
|
|
1631
|
+
let left = this.parseUnary();
|
|
1632
|
+
while (true) {
|
|
1633
|
+
const token = this.current();
|
|
1634
|
+
if (token.type !== "OPERATOR") break;
|
|
1635
|
+
const precedence = PRECEDENCE[token.value];
|
|
1636
|
+
if (precedence === void 0 || precedence < minPrecedence) break;
|
|
1637
|
+
this.advance();
|
|
1638
|
+
const right = this.parseBinary(precedence + 1);
|
|
1639
|
+
left = {
|
|
1640
|
+
type: "binary",
|
|
1641
|
+
operator: token.value,
|
|
1642
|
+
left,
|
|
1643
|
+
right,
|
|
1644
|
+
raw: void 0
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
return left;
|
|
1648
|
+
}
|
|
1649
|
+
parseUnary() {
|
|
1650
|
+
const token = this.current();
|
|
1651
|
+
if (token.type === "OPERATOR" && (token.value === "!" || token.value === "-")) {
|
|
1652
|
+
this.advance();
|
|
1653
|
+
const argument = this.parseUnary();
|
|
1654
|
+
return {
|
|
1655
|
+
type: "unary",
|
|
1656
|
+
operator: token.value,
|
|
1657
|
+
argument,
|
|
1658
|
+
raw: void 0
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
return this.parsePostfix();
|
|
1662
|
+
}
|
|
1663
|
+
parsePostfix() {
|
|
1664
|
+
let node = this.parsePrimary();
|
|
1665
|
+
while (true) {
|
|
1666
|
+
const token = this.current();
|
|
1667
|
+
if (token.type === "DOT") {
|
|
1668
|
+
this.advance();
|
|
1669
|
+
const propToken = this.expect("IDENTIFIER");
|
|
1670
|
+
node = {
|
|
1671
|
+
type: "member",
|
|
1672
|
+
object: node,
|
|
1673
|
+
property: propToken.value,
|
|
1674
|
+
computed: false,
|
|
1675
|
+
raw: void 0
|
|
1676
|
+
};
|
|
1677
|
+
} else if (token.type === "LBRACKET") {
|
|
1678
|
+
this.advance();
|
|
1679
|
+
const index2 = this.parseExpression();
|
|
1680
|
+
this.expect("RBRACKET");
|
|
1681
|
+
node = {
|
|
1682
|
+
type: "member",
|
|
1683
|
+
object: node,
|
|
1684
|
+
property: index2,
|
|
1685
|
+
computed: true,
|
|
1686
|
+
raw: void 0
|
|
1687
|
+
};
|
|
1688
|
+
} else if (token.type === "LPAREN" && node.type === "identifier") {
|
|
1689
|
+
const callee = node.name;
|
|
1690
|
+
if (!BUILTIN_FUNCTION_NAMES.has(callee)) {
|
|
1691
|
+
throw new Error(`Unknown function '${callee}' at position ${token.start}`);
|
|
1692
|
+
}
|
|
1693
|
+
this.advance();
|
|
1694
|
+
const args = this.parseArguments();
|
|
1695
|
+
this.expect("RPAREN");
|
|
1696
|
+
node = {
|
|
1697
|
+
type: "call",
|
|
1698
|
+
callee,
|
|
1699
|
+
arguments: args,
|
|
1700
|
+
raw: void 0
|
|
1701
|
+
};
|
|
1702
|
+
} else {
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
return node;
|
|
1707
|
+
}
|
|
1708
|
+
parsePrimary() {
|
|
1709
|
+
const token = this.current();
|
|
1710
|
+
if (token.type === "NUMBER" || token.type === "STRING" || token.type === "BOOLEAN" || token.type === "NULL") {
|
|
1711
|
+
this.advance();
|
|
1712
|
+
return {
|
|
1713
|
+
type: "literal",
|
|
1714
|
+
value: token.value,
|
|
1715
|
+
start: token.start,
|
|
1716
|
+
end: token.end,
|
|
1717
|
+
raw: void 0
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
if (token.type === "IDENTIFIER") {
|
|
1721
|
+
this.advance();
|
|
1722
|
+
return {
|
|
1723
|
+
type: "identifier",
|
|
1724
|
+
name: token.value,
|
|
1725
|
+
start: token.start,
|
|
1726
|
+
end: token.end,
|
|
1727
|
+
raw: void 0
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
if (token.type === "LBRACKET") {
|
|
1731
|
+
return this.parseArray();
|
|
1732
|
+
}
|
|
1733
|
+
if (token.type === "LPAREN") {
|
|
1734
|
+
this.advance();
|
|
1735
|
+
const expr = this.parseExpression();
|
|
1736
|
+
this.expect("RPAREN");
|
|
1737
|
+
return expr;
|
|
1738
|
+
}
|
|
1739
|
+
throw new Error(`Unexpected token '${token.value}' at position ${token.start}`);
|
|
1740
|
+
}
|
|
1741
|
+
parseArray() {
|
|
1742
|
+
const start = this.current().start;
|
|
1743
|
+
this.advance();
|
|
1744
|
+
const elements = [];
|
|
1745
|
+
while (this.current().type !== "RBRACKET") {
|
|
1746
|
+
elements.push(this.parseExpression());
|
|
1747
|
+
if (this.current().type === "COMMA") {
|
|
1748
|
+
this.advance();
|
|
1749
|
+
} else {
|
|
1750
|
+
break;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
const end = this.current().end;
|
|
1754
|
+
this.expect("RBRACKET");
|
|
1755
|
+
return {
|
|
1756
|
+
type: "array",
|
|
1757
|
+
elements,
|
|
1758
|
+
start,
|
|
1759
|
+
end,
|
|
1760
|
+
raw: void 0
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
parseArguments() {
|
|
1764
|
+
const args = [];
|
|
1765
|
+
if (this.current().type !== "RPAREN") {
|
|
1766
|
+
args.push(this.parseExpression());
|
|
1767
|
+
while (this.current().type === "COMMA") {
|
|
1768
|
+
this.advance();
|
|
1769
|
+
args.push(this.parseExpression());
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return args;
|
|
1773
|
+
}
|
|
1774
|
+
current() {
|
|
1775
|
+
return this.tokens[this.pos];
|
|
1776
|
+
}
|
|
1777
|
+
advance() {
|
|
1778
|
+
return this.tokens[this.pos++];
|
|
1779
|
+
}
|
|
1780
|
+
expect(type) {
|
|
1781
|
+
const token = this.current();
|
|
1782
|
+
if (token.type !== type) {
|
|
1783
|
+
throw new Error(`Expected '${type}' but got '${token.type}' at position ${token.start}`);
|
|
1784
|
+
}
|
|
1785
|
+
return this.advance();
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1788
|
+
var builtinFunctions = {
|
|
1789
|
+
// ==================== 字符串函数 ====================
|
|
1790
|
+
len: (value) => {
|
|
1791
|
+
if (typeof value === "string" || Array.isArray(value)) {
|
|
1792
|
+
return value.length;
|
|
1793
|
+
}
|
|
1794
|
+
return 0;
|
|
1795
|
+
},
|
|
1796
|
+
trim: (str) => {
|
|
1797
|
+
return String(str ?? "").trim();
|
|
1798
|
+
},
|
|
1799
|
+
upper: (str) => {
|
|
1800
|
+
return String(str ?? "").toUpperCase();
|
|
1801
|
+
},
|
|
1802
|
+
lower: (str) => {
|
|
1803
|
+
return String(str ?? "").toLowerCase();
|
|
1804
|
+
},
|
|
1805
|
+
substr: (str, start, length) => {
|
|
1806
|
+
const s = String(str ?? "");
|
|
1807
|
+
const startNum = Number(start) || 0;
|
|
1808
|
+
const lengthNum = length !== void 0 ? Number(length) : void 0;
|
|
1809
|
+
return s.substring(startNum, lengthNum !== void 0 ? startNum + lengthNum : void 0);
|
|
1810
|
+
},
|
|
1811
|
+
concat: (...args) => {
|
|
1812
|
+
return args.map((a) => String(a ?? "")).join("");
|
|
1813
|
+
},
|
|
1814
|
+
replace: (str, search, replacement) => {
|
|
1815
|
+
return String(str ?? "").split(String(search)).join(String(replacement));
|
|
1816
|
+
},
|
|
1817
|
+
split: (str, separator) => {
|
|
1818
|
+
return String(str ?? "").split(String(separator));
|
|
1819
|
+
},
|
|
1820
|
+
join: (arr, separator) => {
|
|
1821
|
+
if (!Array.isArray(arr)) return "";
|
|
1822
|
+
return arr.join(separator !== void 0 ? String(separator) : ",");
|
|
1823
|
+
},
|
|
1824
|
+
startsWith: (str, search) => {
|
|
1825
|
+
return String(str ?? "").startsWith(String(search));
|
|
1826
|
+
},
|
|
1827
|
+
endsWith: (str, search) => {
|
|
1828
|
+
return String(str ?? "").endsWith(String(search));
|
|
1829
|
+
},
|
|
1830
|
+
contains: (str, search) => {
|
|
1831
|
+
return String(str ?? "").includes(String(search));
|
|
1832
|
+
},
|
|
1833
|
+
// ==================== 数字函数 ====================
|
|
1834
|
+
toNumber: (value) => {
|
|
1835
|
+
const num = Number(value);
|
|
1836
|
+
return isNaN(num) ? 0 : num;
|
|
1837
|
+
},
|
|
1838
|
+
toString: (value) => {
|
|
1839
|
+
return String(value ?? "");
|
|
1840
|
+
},
|
|
1841
|
+
toInt: (value) => {
|
|
1842
|
+
return Math.trunc(Number(value) || 0);
|
|
1843
|
+
},
|
|
1844
|
+
toFloat: (value) => {
|
|
1845
|
+
return parseFloat(String(value)) || 0;
|
|
1846
|
+
},
|
|
1847
|
+
round: (value, decimals) => {
|
|
1848
|
+
const num = Number(value) || 0;
|
|
1849
|
+
const dec = Number(decimals) || 0;
|
|
1850
|
+
const factor = Math.pow(10, dec);
|
|
1851
|
+
return Math.round(num * factor) / factor;
|
|
1852
|
+
},
|
|
1853
|
+
floor: (value) => {
|
|
1854
|
+
return Math.floor(Number(value) || 0);
|
|
1855
|
+
},
|
|
1856
|
+
ceil: (value) => {
|
|
1857
|
+
return Math.ceil(Number(value) || 0);
|
|
1858
|
+
},
|
|
1859
|
+
abs: (value) => {
|
|
1860
|
+
return Math.abs(Number(value) || 0);
|
|
1861
|
+
},
|
|
1862
|
+
min: (...args) => {
|
|
1863
|
+
const nums = args.map((a) => Number(a)).filter((n) => !isNaN(n));
|
|
1864
|
+
return nums.length > 0 ? Math.min(...nums) : 0;
|
|
1865
|
+
},
|
|
1866
|
+
max: (...args) => {
|
|
1867
|
+
const nums = args.map((a) => Number(a)).filter((n) => !isNaN(n));
|
|
1868
|
+
return nums.length > 0 ? Math.max(...nums) : 0;
|
|
1869
|
+
},
|
|
1870
|
+
sum: (arr) => {
|
|
1871
|
+
if (!Array.isArray(arr)) return 0;
|
|
1872
|
+
return arr.reduce((acc, val) => acc + (Number(val) || 0), 0);
|
|
1873
|
+
},
|
|
1874
|
+
avg: (arr) => {
|
|
1875
|
+
if (!Array.isArray(arr) || arr.length === 0) return 0;
|
|
1876
|
+
const sum = arr.reduce((acc, val) => acc + (Number(val) || 0), 0);
|
|
1877
|
+
return sum / arr.length;
|
|
1878
|
+
},
|
|
1879
|
+
random: () => {
|
|
1880
|
+
return Math.random();
|
|
1881
|
+
},
|
|
1882
|
+
randomInt: (min, max) => {
|
|
1883
|
+
const minNum = Math.ceil(Number(min) || 0);
|
|
1884
|
+
const maxNum = Math.floor(Number(max) || 100);
|
|
1885
|
+
return Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum;
|
|
1886
|
+
},
|
|
1887
|
+
// ==================== 日期函数 ====================
|
|
1888
|
+
now: () => {
|
|
1889
|
+
return Date.now();
|
|
1890
|
+
},
|
|
1891
|
+
today: () => {
|
|
1892
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1893
|
+
},
|
|
1894
|
+
dateFormat: (timestamp, format) => {
|
|
1895
|
+
const date = new Date(Number(timestamp) || Date.now());
|
|
1896
|
+
const fmt = String(format || "YYYY-MM-DD");
|
|
1897
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
1898
|
+
return fmt.replace("YYYY", date.getFullYear().toString()).replace("MM", pad(date.getMonth() + 1)).replace("DD", pad(date.getDate())).replace("HH", pad(date.getHours())).replace("mm", pad(date.getMinutes())).replace("ss", pad(date.getSeconds()));
|
|
1899
|
+
},
|
|
1900
|
+
dateParse: (dateStr) => {
|
|
1901
|
+
const date = new Date(String(dateStr));
|
|
1902
|
+
return date.getTime();
|
|
1903
|
+
},
|
|
1904
|
+
year: (timestamp) => {
|
|
1905
|
+
return new Date(Number(timestamp) || Date.now()).getFullYear();
|
|
1906
|
+
},
|
|
1907
|
+
month: (timestamp) => {
|
|
1908
|
+
return new Date(Number(timestamp) || Date.now()).getMonth() + 1;
|
|
1909
|
+
},
|
|
1910
|
+
day: (timestamp) => {
|
|
1911
|
+
return new Date(Number(timestamp) || Date.now()).getDate();
|
|
1912
|
+
},
|
|
1913
|
+
addDays: (timestamp, days) => {
|
|
1914
|
+
const date = new Date(Number(timestamp) || Date.now());
|
|
1915
|
+
date.setDate(date.getDate() + (Number(days) || 0));
|
|
1916
|
+
return date.getTime();
|
|
1917
|
+
},
|
|
1918
|
+
diffDays: (timestamp1, timestamp2) => {
|
|
1919
|
+
const date1 = new Date(Number(timestamp1) || Date.now());
|
|
1920
|
+
const date2 = new Date(Number(timestamp2) || Date.now());
|
|
1921
|
+
const diffTime = Math.abs(date2.getTime() - date1.getTime());
|
|
1922
|
+
return Math.floor(diffTime / (1e3 * 60 * 60 * 24));
|
|
1923
|
+
},
|
|
1924
|
+
// ==================== 类型检查函数 ====================
|
|
1925
|
+
isNull: (value) => {
|
|
1926
|
+
return value === null || value === void 0;
|
|
1927
|
+
},
|
|
1928
|
+
isUndefined: (value) => {
|
|
1929
|
+
return value === void 0;
|
|
1930
|
+
},
|
|
1931
|
+
isEmpty: (value) => {
|
|
1932
|
+
if (value === null || value === void 0) return true;
|
|
1933
|
+
if (typeof value === "string") return value.length === 0;
|
|
1934
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
1935
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
1936
|
+
return false;
|
|
1937
|
+
},
|
|
1938
|
+
isArray: (value) => {
|
|
1939
|
+
return Array.isArray(value);
|
|
1940
|
+
},
|
|
1941
|
+
isObject: (value) => {
|
|
1942
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1943
|
+
},
|
|
1944
|
+
isString: (value) => {
|
|
1945
|
+
return typeof value === "string";
|
|
1946
|
+
},
|
|
1947
|
+
isNumber: (value) => {
|
|
1948
|
+
return typeof value === "number" && !isNaN(value);
|
|
1949
|
+
},
|
|
1950
|
+
isBoolean: (value) => {
|
|
1951
|
+
return typeof value === "boolean";
|
|
1952
|
+
},
|
|
1953
|
+
typeOf: (value) => {
|
|
1954
|
+
if (value === null) return "null";
|
|
1955
|
+
if (Array.isArray(value)) return "array";
|
|
1956
|
+
return typeof value;
|
|
1957
|
+
},
|
|
1958
|
+
// ==================== 默认值函数 ====================
|
|
1959
|
+
default: (value, defaultValue) => {
|
|
1960
|
+
return value ?? defaultValue;
|
|
1961
|
+
},
|
|
1962
|
+
coalesce: (...args) => {
|
|
1963
|
+
for (const arg of args) {
|
|
1964
|
+
if (arg !== null && arg !== void 0) return arg;
|
|
1965
|
+
}
|
|
1966
|
+
return null;
|
|
1967
|
+
},
|
|
1968
|
+
ifElse: (condition, trueValue, falseValue) => {
|
|
1969
|
+
return condition ? trueValue : falseValue;
|
|
1970
|
+
},
|
|
1971
|
+
// ==================== 数组函数 ====================
|
|
1972
|
+
first: (arr) => {
|
|
1973
|
+
if (!Array.isArray(arr)) return void 0;
|
|
1974
|
+
return arr[0];
|
|
1975
|
+
},
|
|
1976
|
+
last: (arr) => {
|
|
1977
|
+
if (!Array.isArray(arr)) return void 0;
|
|
1978
|
+
return arr[arr.length - 1];
|
|
1979
|
+
},
|
|
1980
|
+
at: (arr, index2) => {
|
|
1981
|
+
if (!Array.isArray(arr)) return void 0;
|
|
1982
|
+
return arr[Number(index2) || 0];
|
|
1983
|
+
},
|
|
1984
|
+
slice: (arr, start, end) => {
|
|
1985
|
+
if (!Array.isArray(arr)) return [];
|
|
1986
|
+
return arr.slice(Number(start) || 0, end !== void 0 ? Number(end) : void 0);
|
|
1987
|
+
},
|
|
1988
|
+
includes: (arr, value) => {
|
|
1989
|
+
if (!Array.isArray(arr)) return false;
|
|
1990
|
+
return arr.includes(value);
|
|
1991
|
+
},
|
|
1992
|
+
indexOf: (arr, value) => {
|
|
1993
|
+
if (!Array.isArray(arr)) return -1;
|
|
1994
|
+
return arr.indexOf(value);
|
|
1995
|
+
},
|
|
1996
|
+
reverse: (arr) => {
|
|
1997
|
+
if (!Array.isArray(arr)) return [];
|
|
1998
|
+
return [...arr].reverse();
|
|
1999
|
+
},
|
|
2000
|
+
sort: (arr) => {
|
|
2001
|
+
if (!Array.isArray(arr)) return [];
|
|
2002
|
+
return [...arr].sort();
|
|
2003
|
+
},
|
|
2004
|
+
unique: (arr) => {
|
|
2005
|
+
if (!Array.isArray(arr)) return [];
|
|
2006
|
+
return [...new Set(arr)];
|
|
2007
|
+
},
|
|
2008
|
+
flatten: (arr) => {
|
|
2009
|
+
if (!Array.isArray(arr)) return [];
|
|
2010
|
+
return arr.flat();
|
|
2011
|
+
},
|
|
2012
|
+
count: (arr) => {
|
|
2013
|
+
if (!Array.isArray(arr)) return 0;
|
|
2014
|
+
return arr.length;
|
|
2015
|
+
},
|
|
2016
|
+
// ==================== 对象函数 ====================
|
|
2017
|
+
get: (obj, path, defaultValue) => {
|
|
2018
|
+
if (obj === null || obj === void 0) return defaultValue;
|
|
2019
|
+
const pathStr = String(path);
|
|
2020
|
+
const parts = pathStr.split(".");
|
|
2021
|
+
let current = obj;
|
|
2022
|
+
for (const part of parts) {
|
|
2023
|
+
if (current === null || current === void 0) return defaultValue;
|
|
2024
|
+
current = current[part];
|
|
2025
|
+
}
|
|
2026
|
+
return current ?? defaultValue;
|
|
2027
|
+
},
|
|
2028
|
+
keys: (obj) => {
|
|
2029
|
+
if (typeof obj !== "object" || obj === null) return [];
|
|
2030
|
+
return Object.keys(obj);
|
|
2031
|
+
},
|
|
2032
|
+
values: (obj) => {
|
|
2033
|
+
if (typeof obj !== "object" || obj === null) return [];
|
|
2034
|
+
return Object.values(obj);
|
|
2035
|
+
},
|
|
2036
|
+
entries: (obj) => {
|
|
2037
|
+
if (typeof obj !== "object" || obj === null) return [];
|
|
2038
|
+
return Object.entries(obj);
|
|
2039
|
+
},
|
|
2040
|
+
has: (obj, key) => {
|
|
2041
|
+
if (typeof obj !== "object" || obj === null) return false;
|
|
2042
|
+
return String(key) in obj;
|
|
2043
|
+
},
|
|
2044
|
+
merge: (...objs) => {
|
|
2045
|
+
const result = {};
|
|
2046
|
+
for (const obj of objs) {
|
|
2047
|
+
if (typeof obj === "object" && obj !== null) {
|
|
2048
|
+
Object.assign(result, obj);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return result;
|
|
2052
|
+
},
|
|
2053
|
+
// ==================== 逻辑函数 ====================
|
|
2054
|
+
and: (...args) => {
|
|
2055
|
+
return args.every((a) => Boolean(a));
|
|
2056
|
+
},
|
|
2057
|
+
or: (...args) => {
|
|
2058
|
+
return args.some((a) => Boolean(a));
|
|
2059
|
+
},
|
|
2060
|
+
not: (value) => {
|
|
2061
|
+
return !value;
|
|
2062
|
+
},
|
|
2063
|
+
eq: (a, b) => {
|
|
2064
|
+
return a === b;
|
|
2065
|
+
},
|
|
2066
|
+
ne: (a, b) => {
|
|
2067
|
+
return a !== b;
|
|
2068
|
+
},
|
|
2069
|
+
gt: (a, b) => {
|
|
2070
|
+
return Number(a) > Number(b);
|
|
2071
|
+
},
|
|
2072
|
+
gte: (a, b) => {
|
|
2073
|
+
return Number(a) >= Number(b);
|
|
2074
|
+
},
|
|
2075
|
+
lt: (a, b) => {
|
|
2076
|
+
return Number(a) < Number(b);
|
|
2077
|
+
},
|
|
2078
|
+
lte: (a, b) => {
|
|
2079
|
+
return Number(a) <= Number(b);
|
|
2080
|
+
},
|
|
2081
|
+
between: (value, min, max) => {
|
|
2082
|
+
const num = Number(value);
|
|
2083
|
+
return num >= Number(min) && num <= Number(max);
|
|
2084
|
+
},
|
|
2085
|
+
// ==================== 格式化函数 ====================
|
|
2086
|
+
formatNumber: (value, decimals) => {
|
|
2087
|
+
const num = Number(value) || 0;
|
|
2088
|
+
const dec = Number(decimals) ?? 0;
|
|
2089
|
+
return num.toLocaleString(void 0, {
|
|
2090
|
+
minimumFractionDigits: dec,
|
|
2091
|
+
maximumFractionDigits: dec
|
|
2092
|
+
});
|
|
2093
|
+
},
|
|
2094
|
+
formatCurrency: (value, currency) => {
|
|
2095
|
+
const num = Number(value) || 0;
|
|
2096
|
+
const cur = String(currency || "CNY");
|
|
2097
|
+
return num.toLocaleString("zh-CN", { style: "currency", currency: cur });
|
|
2098
|
+
},
|
|
2099
|
+
formatPercent: (value, decimals) => {
|
|
2100
|
+
const num = Number(value) || 0;
|
|
2101
|
+
const dec = Number(decimals) ?? 0;
|
|
2102
|
+
return (num * 100).toFixed(dec) + "%";
|
|
2103
|
+
},
|
|
2104
|
+
// ==================== JSON 函数 ====================
|
|
2105
|
+
jsonParse: (str) => {
|
|
2106
|
+
try {
|
|
2107
|
+
return JSON.parse(String(str));
|
|
2108
|
+
} catch {
|
|
2109
|
+
return null;
|
|
2110
|
+
}
|
|
2111
|
+
},
|
|
2112
|
+
jsonStringify: (value) => {
|
|
2113
|
+
return JSON.stringify(value);
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
var Evaluator = class {
|
|
2117
|
+
constructor(options = {}) {
|
|
2118
|
+
this.depth = 0;
|
|
2119
|
+
this.startTime = 0;
|
|
2120
|
+
this.options = {
|
|
2121
|
+
maxDepth: 100,
|
|
2122
|
+
timeout: 1e3,
|
|
2123
|
+
debug: false,
|
|
2124
|
+
...options
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* 求值表达式
|
|
2129
|
+
*/
|
|
2130
|
+
evaluate(ast, context) {
|
|
2131
|
+
this.depth = 0;
|
|
2132
|
+
this.startTime = Date.now();
|
|
2133
|
+
try {
|
|
2134
|
+
const value = this.evaluateNode(ast, context);
|
|
2135
|
+
return { value };
|
|
2136
|
+
} catch (error) {
|
|
2137
|
+
return {
|
|
2138
|
+
value: void 0,
|
|
2139
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
evaluateNode(node, context) {
|
|
2144
|
+
this.checkLimits();
|
|
2145
|
+
switch (node.type) {
|
|
2146
|
+
case "literal":
|
|
2147
|
+
return this.evaluateLiteral(node);
|
|
2148
|
+
case "identifier":
|
|
2149
|
+
return this.evaluateIdentifier(node, context);
|
|
2150
|
+
case "member":
|
|
2151
|
+
return this.evaluateMember(node, context);
|
|
2152
|
+
case "call":
|
|
2153
|
+
return this.evaluateCall(node, context);
|
|
2154
|
+
case "binary":
|
|
2155
|
+
return this.evaluateBinary(node, context);
|
|
2156
|
+
case "unary":
|
|
2157
|
+
return this.evaluateUnary(node, context);
|
|
2158
|
+
case "conditional":
|
|
2159
|
+
return this.evaluateConditional(node, context);
|
|
2160
|
+
case "array":
|
|
2161
|
+
return this.evaluateArray(node, context);
|
|
2162
|
+
default:
|
|
2163
|
+
throw new ExpressionError("", `Unknown node type: ${node.type}`);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
evaluateLiteral(node) {
|
|
2167
|
+
return node.value;
|
|
2168
|
+
}
|
|
2169
|
+
evaluateIdentifier(node, context) {
|
|
2170
|
+
const name = node.name;
|
|
2171
|
+
switch (name) {
|
|
2172
|
+
case "state":
|
|
2173
|
+
return context.state;
|
|
2174
|
+
case "query":
|
|
2175
|
+
return context.query;
|
|
2176
|
+
case "context":
|
|
2177
|
+
return context.context;
|
|
2178
|
+
case "event":
|
|
2179
|
+
return context.event;
|
|
2180
|
+
case "item":
|
|
2181
|
+
return context.item;
|
|
2182
|
+
case "index":
|
|
2183
|
+
return context.index;
|
|
2184
|
+
default:
|
|
2185
|
+
throw new ExpressionError(
|
|
2186
|
+
"",
|
|
2187
|
+
`Unknown variable '${name}'`,
|
|
2188
|
+
node.start !== void 0 && node.end !== void 0 ? { start: node.start, end: node.end } : void 0
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
evaluateMember(node, context) {
|
|
2193
|
+
const object = this.evaluateNode(node.object, context);
|
|
2194
|
+
if (object === null || object === void 0) {
|
|
2195
|
+
return void 0;
|
|
2196
|
+
}
|
|
2197
|
+
let property;
|
|
2198
|
+
if (node.computed) {
|
|
2199
|
+
const propValue = this.evaluateNode(node.property, context);
|
|
2200
|
+
property = propValue;
|
|
2201
|
+
} else {
|
|
2202
|
+
property = node.property;
|
|
2203
|
+
}
|
|
2204
|
+
if (typeof object === "object" && object !== null) {
|
|
2205
|
+
return object[property];
|
|
2206
|
+
}
|
|
2207
|
+
return void 0;
|
|
2208
|
+
}
|
|
2209
|
+
evaluateCall(node, context) {
|
|
2210
|
+
const fn = builtinFunctions[node.callee];
|
|
2211
|
+
if (!fn) {
|
|
2212
|
+
throw new ExpressionError("", `Unknown function '${node.callee}'`);
|
|
2213
|
+
}
|
|
2214
|
+
const args = node.arguments.map((arg) => this.evaluateNode(arg, context));
|
|
2215
|
+
return fn(...args);
|
|
2216
|
+
}
|
|
2217
|
+
evaluateBinary(node, context) {
|
|
2218
|
+
const op = node.operator;
|
|
2219
|
+
if (op === "&&") {
|
|
2220
|
+
const left2 = this.evaluateNode(node.left, context);
|
|
2221
|
+
if (!left2) return left2;
|
|
2222
|
+
return this.evaluateNode(node.right, context);
|
|
2223
|
+
}
|
|
2224
|
+
if (op === "||") {
|
|
2225
|
+
const left2 = this.evaluateNode(node.left, context);
|
|
2226
|
+
if (left2) return left2;
|
|
2227
|
+
return this.evaluateNode(node.right, context);
|
|
2228
|
+
}
|
|
2229
|
+
if (op === "??") {
|
|
2230
|
+
const left2 = this.evaluateNode(node.left, context);
|
|
2231
|
+
if (left2 !== null && left2 !== void 0) return left2;
|
|
2232
|
+
return this.evaluateNode(node.right, context);
|
|
2233
|
+
}
|
|
2234
|
+
const left = this.evaluateNode(node.left, context);
|
|
2235
|
+
const right = this.evaluateNode(node.right, context);
|
|
2236
|
+
switch (op) {
|
|
2237
|
+
case "+":
|
|
2238
|
+
if (typeof left === "string" || typeof right === "string") {
|
|
2239
|
+
return String(left) + String(right);
|
|
2240
|
+
}
|
|
2241
|
+
return left + right;
|
|
2242
|
+
case "-":
|
|
2243
|
+
return left - right;
|
|
2244
|
+
case "*":
|
|
2245
|
+
return left * right;
|
|
2246
|
+
case "/":
|
|
2247
|
+
return left / right;
|
|
2248
|
+
case "%":
|
|
2249
|
+
return left % right;
|
|
2250
|
+
case "==":
|
|
2251
|
+
return left === right;
|
|
2252
|
+
case "!=":
|
|
2253
|
+
return left !== right;
|
|
2254
|
+
case "<":
|
|
2255
|
+
return left < right;
|
|
2256
|
+
case ">":
|
|
2257
|
+
return left > right;
|
|
2258
|
+
case "<=":
|
|
2259
|
+
return left <= right;
|
|
2260
|
+
case ">=":
|
|
2261
|
+
return left >= right;
|
|
2262
|
+
default:
|
|
2263
|
+
throw new ExpressionError("", `Unknown operator '${op}'`);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
evaluateUnary(node, context) {
|
|
2267
|
+
const argument = this.evaluateNode(node.argument, context);
|
|
2268
|
+
switch (node.operator) {
|
|
2269
|
+
case "!":
|
|
2270
|
+
return !argument;
|
|
2271
|
+
case "-":
|
|
2272
|
+
return -argument;
|
|
2273
|
+
default:
|
|
2274
|
+
throw new ExpressionError("", `Unknown unary operator '${node.operator}'`);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
evaluateConditional(node, context) {
|
|
2278
|
+
const test = this.evaluateNode(node.test, context);
|
|
2279
|
+
if (test) {
|
|
2280
|
+
return this.evaluateNode(node.consequent, context);
|
|
2281
|
+
}
|
|
2282
|
+
return this.evaluateNode(node.alternate, context);
|
|
2283
|
+
}
|
|
2284
|
+
evaluateArray(node, context) {
|
|
2285
|
+
return node.elements.map((el) => this.evaluateNode(el, context));
|
|
2286
|
+
}
|
|
2287
|
+
checkLimits() {
|
|
2288
|
+
this.depth++;
|
|
2289
|
+
if (this.depth > (this.options.maxDepth ?? 100)) {
|
|
2290
|
+
throw new ExpressionError("", "Maximum recursion depth exceeded");
|
|
2291
|
+
}
|
|
2292
|
+
const elapsed = Date.now() - this.startTime;
|
|
2293
|
+
if (elapsed > (this.options.timeout ?? 1e3)) {
|
|
2294
|
+
throw new ExpressionError("", "Expression evaluation timeout");
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2298
|
+
var ExpressionEngine = class {
|
|
2299
|
+
constructor(options = {}) {
|
|
2300
|
+
this.astCache = /* @__PURE__ */ new Map();
|
|
2301
|
+
this.options = {
|
|
2302
|
+
cacheAST: true,
|
|
2303
|
+
maxCacheSize: 1e3,
|
|
2304
|
+
...options
|
|
2305
|
+
};
|
|
2306
|
+
this.evaluator = new Evaluator(options);
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* 求值表达式
|
|
2310
|
+
*/
|
|
2311
|
+
evaluate(expression, context) {
|
|
2312
|
+
try {
|
|
2313
|
+
const ast = this.parse(expression);
|
|
2314
|
+
return this.evaluator.evaluate(ast, context);
|
|
2315
|
+
} catch (error) {
|
|
2316
|
+
return {
|
|
2317
|
+
value: void 0,
|
|
2318
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
/**
|
|
2323
|
+
* 求值表达式并返回值
|
|
2324
|
+
* 出错时返回 fallback 值
|
|
2325
|
+
*/
|
|
2326
|
+
evaluateWithFallback(expression, context, fallback) {
|
|
2327
|
+
const result = this.evaluate(expression, context);
|
|
2328
|
+
if (result.error) {
|
|
2329
|
+
this.log("warn", `Expression evaluation failed: ${result.error.message}`, expression);
|
|
2330
|
+
return fallback;
|
|
2331
|
+
}
|
|
2332
|
+
return result.value;
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* 求值模板字符串
|
|
2336
|
+
* 支持 ${expression} 语法
|
|
2337
|
+
*/
|
|
2338
|
+
evaluateTemplate(template, context) {
|
|
2339
|
+
return template.replace(/\$\{([^}]+)\}/g, (_, expr) => {
|
|
2340
|
+
const result = this.evaluate(expr.trim(), context);
|
|
2341
|
+
if (result.error) {
|
|
2342
|
+
this.log("warn", `Template expression failed: ${result.error.message}`, expr);
|
|
2343
|
+
return "";
|
|
2344
|
+
}
|
|
2345
|
+
return String(result.value ?? "");
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
/**
|
|
2349
|
+
* 解析表达式为 AST
|
|
2350
|
+
*/
|
|
2351
|
+
parse(expression) {
|
|
2352
|
+
if (this.options.cacheAST) {
|
|
2353
|
+
const cached = this.astCache.get(expression);
|
|
2354
|
+
if (cached) {
|
|
2355
|
+
return cached;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
try {
|
|
2359
|
+
const lexer = new Lexer(expression);
|
|
2360
|
+
const tokens = lexer.tokenize();
|
|
2361
|
+
const parser = new Parser(tokens);
|
|
2362
|
+
const ast = parser.parse();
|
|
2363
|
+
if (this.options.cacheAST) {
|
|
2364
|
+
if (this.astCache.size >= (this.options.maxCacheSize ?? 1e3)) {
|
|
2365
|
+
const keysToDelete = Array.from(this.astCache.keys()).slice(
|
|
2366
|
+
0,
|
|
2367
|
+
Math.floor(this.astCache.size / 2)
|
|
2368
|
+
);
|
|
2369
|
+
keysToDelete.forEach((key) => this.astCache.delete(key));
|
|
2370
|
+
}
|
|
2371
|
+
this.astCache.set(expression, ast);
|
|
2372
|
+
}
|
|
2373
|
+
return ast;
|
|
2374
|
+
} catch (error) {
|
|
2375
|
+
throw new ExpressionError(
|
|
2376
|
+
expression,
|
|
2377
|
+
error instanceof Error ? error.message : "Parse error"
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* 校验表达式
|
|
2383
|
+
*/
|
|
2384
|
+
validate(expression) {
|
|
2385
|
+
const errors = [];
|
|
2386
|
+
const warnings = [];
|
|
2387
|
+
const referencedPaths = [];
|
|
2388
|
+
const calledFunctions = [];
|
|
2389
|
+
try {
|
|
2390
|
+
const ast = this.parse(expression);
|
|
2391
|
+
this.collectReferences(ast, referencedPaths, calledFunctions);
|
|
2392
|
+
return {
|
|
2393
|
+
valid: true,
|
|
2394
|
+
errors: [],
|
|
2395
|
+
warnings,
|
|
2396
|
+
referencedPaths,
|
|
2397
|
+
calledFunctions
|
|
2398
|
+
};
|
|
2399
|
+
} catch (error) {
|
|
2400
|
+
errors.push({
|
|
2401
|
+
type: "SYNTAX_ERROR",
|
|
2402
|
+
message: error instanceof Error ? error.message : "Parse error"
|
|
2403
|
+
});
|
|
2404
|
+
return {
|
|
2405
|
+
valid: false,
|
|
2406
|
+
errors,
|
|
2407
|
+
warnings,
|
|
2408
|
+
referencedPaths,
|
|
2409
|
+
calledFunctions
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* 清除 AST 缓存
|
|
2415
|
+
*/
|
|
2416
|
+
clearCache() {
|
|
2417
|
+
this.astCache.clear();
|
|
2418
|
+
}
|
|
2419
|
+
collectReferences(node, paths, functions) {
|
|
2420
|
+
switch (node.type) {
|
|
2421
|
+
case "identifier":
|
|
2422
|
+
paths.push(node.name);
|
|
2423
|
+
break;
|
|
2424
|
+
case "member": {
|
|
2425
|
+
const path = this.buildMemberPath(node);
|
|
2426
|
+
if (path) paths.push(path);
|
|
2427
|
+
break;
|
|
2428
|
+
}
|
|
2429
|
+
case "call":
|
|
2430
|
+
functions.push(node.callee);
|
|
2431
|
+
node.arguments.forEach((arg) => this.collectReferences(arg, paths, functions));
|
|
2432
|
+
break;
|
|
2433
|
+
case "binary":
|
|
2434
|
+
this.collectReferences(node.left, paths, functions);
|
|
2435
|
+
this.collectReferences(node.right, paths, functions);
|
|
2436
|
+
break;
|
|
2437
|
+
case "unary":
|
|
2438
|
+
this.collectReferences(node.argument, paths, functions);
|
|
2439
|
+
break;
|
|
2440
|
+
case "conditional":
|
|
2441
|
+
this.collectReferences(node.test, paths, functions);
|
|
2442
|
+
this.collectReferences(node.consequent, paths, functions);
|
|
2443
|
+
this.collectReferences(node.alternate, paths, functions);
|
|
2444
|
+
break;
|
|
2445
|
+
case "array":
|
|
2446
|
+
node.elements.forEach((el) => this.collectReferences(el, paths, functions));
|
|
2447
|
+
break;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
buildMemberPath(node) {
|
|
2451
|
+
if (node.type === "identifier") {
|
|
2452
|
+
return node.name;
|
|
2453
|
+
}
|
|
2454
|
+
if (node.type === "member" && !node.computed) {
|
|
2455
|
+
const objectPath = this.buildMemberPath(node.object);
|
|
2456
|
+
if (objectPath) {
|
|
2457
|
+
return `${objectPath}.${node.property}`;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
return null;
|
|
2461
|
+
}
|
|
2462
|
+
log(level, message, ...args) {
|
|
2463
|
+
if (this.options.logger) {
|
|
2464
|
+
this.options.logger[level](message, ...args);
|
|
2465
|
+
} else if (this.options.debug) {
|
|
2466
|
+
console[level](`[ExpressionEngine] ${message}`, ...args);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
};
|
|
2470
|
+
var HostAPIImpl = class {
|
|
2471
|
+
constructor(options) {
|
|
2472
|
+
this.storage = {
|
|
2473
|
+
get: (key, options2) => {
|
|
2474
|
+
const fullKey = this.getStorageKey(key, options2 == null ? void 0 : options2.namespace);
|
|
2475
|
+
const item = localStorage.getItem(fullKey);
|
|
2476
|
+
if (!item) return void 0;
|
|
2477
|
+
try {
|
|
2478
|
+
const parsed = JSON.parse(item);
|
|
2479
|
+
if (parsed.expires && Date.now() > parsed.expires) {
|
|
2480
|
+
localStorage.removeItem(fullKey);
|
|
2481
|
+
return void 0;
|
|
2482
|
+
}
|
|
2483
|
+
return parsed.value;
|
|
2484
|
+
} catch {
|
|
2485
|
+
return void 0;
|
|
2486
|
+
}
|
|
2487
|
+
},
|
|
2488
|
+
set: (key, value, options2) => {
|
|
2489
|
+
const fullKey = this.getStorageKey(key, options2 == null ? void 0 : options2.namespace);
|
|
2490
|
+
const item = {
|
|
2491
|
+
value,
|
|
2492
|
+
expires: (options2 == null ? void 0 : options2.ttl) ? Date.now() + options2.ttl * 1e3 : void 0
|
|
2493
|
+
};
|
|
2494
|
+
localStorage.setItem(fullKey, JSON.stringify(item));
|
|
2495
|
+
},
|
|
2496
|
+
remove: (key, options2) => {
|
|
2497
|
+
const fullKey = this.getStorageKey(key, options2 == null ? void 0 : options2.namespace);
|
|
2498
|
+
localStorage.removeItem(fullKey);
|
|
2499
|
+
}
|
|
2500
|
+
};
|
|
2501
|
+
this.options = options;
|
|
2502
|
+
this.storageNamespace = `djvlc:${options.context.pageUid}`;
|
|
2503
|
+
}
|
|
2504
|
+
// ==================== 数据请求 ====================
|
|
2505
|
+
async requestData(queryId, params) {
|
|
2506
|
+
this.log("debug", `Requesting data: ${queryId}`, params);
|
|
2507
|
+
const startTime = performance.now();
|
|
2508
|
+
const context = this.options.context;
|
|
2509
|
+
try {
|
|
2510
|
+
const response = await fetch(`${this.options.apiBaseUrl}/data/query`, {
|
|
2511
|
+
method: "POST",
|
|
2512
|
+
headers: this.buildHeaders(),
|
|
2513
|
+
body: JSON.stringify({
|
|
2514
|
+
queryVersionId: queryId,
|
|
2515
|
+
params: params || {},
|
|
2516
|
+
context: {
|
|
2517
|
+
pageVersionId: context.pageVersionId,
|
|
2518
|
+
uid: context.userId
|
|
2519
|
+
}
|
|
2520
|
+
})
|
|
2521
|
+
});
|
|
2522
|
+
const result = await response.json();
|
|
2523
|
+
const duration = performance.now() - startTime;
|
|
2524
|
+
this.log("debug", `Data query completed in ${duration.toFixed(2)}ms`);
|
|
2525
|
+
if (result.success && result.data) {
|
|
2526
|
+
this.options.stateManager.setQuery(queryId, result.data);
|
|
2527
|
+
}
|
|
2528
|
+
return result;
|
|
2529
|
+
} catch (error) {
|
|
2530
|
+
this.log("error", `Data query failed: ${queryId}`, error);
|
|
2531
|
+
return {
|
|
2532
|
+
success: false,
|
|
2533
|
+
message: error instanceof Error ? error.message : "Query failed"
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
async executeAction(actionType, params) {
|
|
2538
|
+
this.log("debug", `Executing action: ${actionType}`, params);
|
|
2539
|
+
const startTime = performance.now();
|
|
2540
|
+
const context = this.options.context;
|
|
2541
|
+
try {
|
|
2542
|
+
const response = await fetch(`${this.options.apiBaseUrl}/actions/execute`, {
|
|
2543
|
+
method: "POST",
|
|
2544
|
+
headers: this.buildHeaders(),
|
|
2545
|
+
body: JSON.stringify({
|
|
2546
|
+
actionType,
|
|
2547
|
+
params: params || {},
|
|
2548
|
+
context: {
|
|
2549
|
+
pageVersionId: context.pageVersionId,
|
|
2550
|
+
uid: context.userId,
|
|
2551
|
+
deviceId: context.deviceId,
|
|
2552
|
+
channel: context.channel
|
|
2553
|
+
},
|
|
2554
|
+
idempotencyKey: this.generateIdempotencyKey(actionType, params)
|
|
2555
|
+
})
|
|
2556
|
+
});
|
|
2557
|
+
const result = await response.json();
|
|
2558
|
+
const duration = performance.now() - startTime;
|
|
2559
|
+
this.log("debug", `Action completed in ${duration.toFixed(2)}ms`);
|
|
2560
|
+
return result;
|
|
2561
|
+
} catch (error) {
|
|
2562
|
+
this.log("error", `Action failed: ${actionType}`, error);
|
|
2563
|
+
return {
|
|
2564
|
+
success: false,
|
|
2565
|
+
message: error instanceof Error ? error.message : "Action failed"
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
// ==================== 导航 ====================
|
|
2570
|
+
navigate(options) {
|
|
2571
|
+
this.log("debug", "Navigate:", options);
|
|
2572
|
+
this.track({
|
|
2573
|
+
eventName: "navigate",
|
|
2574
|
+
params: { url: options.url, newTab: options.newTab }
|
|
2575
|
+
});
|
|
2576
|
+
let url = options.url;
|
|
2577
|
+
if (options.params) {
|
|
2578
|
+
const searchParams = new URLSearchParams(options.params);
|
|
2579
|
+
url += (url.includes("?") ? "&" : "?") + searchParams.toString();
|
|
2580
|
+
}
|
|
2581
|
+
if (options.newTab) {
|
|
2582
|
+
window.open(url, "_blank");
|
|
2583
|
+
} else if (options.replace) {
|
|
2584
|
+
window.location.replace(url);
|
|
2585
|
+
} else {
|
|
2586
|
+
window.location.href = url;
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
goBack() {
|
|
2590
|
+
this.log("debug", "Navigate back");
|
|
2591
|
+
window.history.back();
|
|
2592
|
+
}
|
|
2593
|
+
refresh() {
|
|
2594
|
+
this.log("debug", "Refresh page");
|
|
2595
|
+
window.location.reload();
|
|
2596
|
+
}
|
|
2597
|
+
// ==================== 对话框 ====================
|
|
2598
|
+
async openDialog(options) {
|
|
2599
|
+
this.log("debug", "Open dialog:", options);
|
|
2600
|
+
switch (options.type) {
|
|
2601
|
+
case "alert":
|
|
2602
|
+
window.alert(options.content || options.title);
|
|
2603
|
+
return true;
|
|
2604
|
+
case "confirm":
|
|
2605
|
+
return window.confirm(options.content || options.title);
|
|
2606
|
+
case "prompt":
|
|
2607
|
+
return window.prompt(options.content || options.title) || "";
|
|
2608
|
+
case "custom":
|
|
2609
|
+
this.log("warn", "Custom dialog not implemented");
|
|
2610
|
+
return false;
|
|
2611
|
+
default:
|
|
2612
|
+
return false;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
closeDialog() {
|
|
2616
|
+
this.log("debug", "Close dialog");
|
|
2617
|
+
}
|
|
2618
|
+
showToast(options) {
|
|
2619
|
+
this.log("debug", "Show toast:", options);
|
|
2620
|
+
const toast = document.createElement("div");
|
|
2621
|
+
toast.className = `djvlc-toast djvlc-toast-${options.type || "info"} djvlc-toast-${options.position || "top"}`;
|
|
2622
|
+
toast.textContent = options.message;
|
|
2623
|
+
Object.assign(toast.style, {
|
|
2624
|
+
position: "fixed",
|
|
2625
|
+
left: "50%",
|
|
2626
|
+
transform: "translateX(-50%)",
|
|
2627
|
+
padding: "12px 24px",
|
|
2628
|
+
borderRadius: "8px",
|
|
2629
|
+
color: "#fff",
|
|
2630
|
+
fontSize: "14px",
|
|
2631
|
+
zIndex: "10000",
|
|
2632
|
+
animation: "djvlc-toast-fade-in 0.3s ease"
|
|
2633
|
+
});
|
|
2634
|
+
const colors = {
|
|
2635
|
+
success: "#52c41a",
|
|
2636
|
+
error: "#ff4d4f",
|
|
2637
|
+
warning: "#faad14",
|
|
2638
|
+
info: "#1890ff"
|
|
2639
|
+
};
|
|
2640
|
+
toast.style.backgroundColor = colors[options.type || "info"];
|
|
2641
|
+
const positions = {
|
|
2642
|
+
top: { top: "20px" },
|
|
2643
|
+
center: { top: "50%", transform: "translate(-50%, -50%)" },
|
|
2644
|
+
bottom: { bottom: "20px" }
|
|
2645
|
+
};
|
|
2646
|
+
Object.assign(toast.style, positions[options.position || "top"]);
|
|
2647
|
+
document.body.appendChild(toast);
|
|
2648
|
+
setTimeout(() => {
|
|
2649
|
+
toast.style.animation = "djvlc-toast-fade-out 0.3s ease";
|
|
2650
|
+
setTimeout(() => toast.remove(), 300);
|
|
2651
|
+
}, options.duration || 3e3);
|
|
2652
|
+
}
|
|
2653
|
+
// ==================== 埋点 ====================
|
|
2654
|
+
track(event) {
|
|
2655
|
+
this.log("debug", "Track event:", event);
|
|
2656
|
+
const context = this.options.context;
|
|
2657
|
+
fetch(`${this.options.apiBaseUrl}/track`, {
|
|
2658
|
+
method: "POST",
|
|
2659
|
+
headers: this.buildHeaders(),
|
|
2660
|
+
body: JSON.stringify({
|
|
2661
|
+
eventName: event.eventName,
|
|
2662
|
+
params: event.params,
|
|
2663
|
+
timestamp: event.timestamp || Date.now(),
|
|
2664
|
+
context: {
|
|
2665
|
+
pageVersionId: context.pageVersionId,
|
|
2666
|
+
runtimeVersion: context.runtimeVersion,
|
|
2667
|
+
userId: context.userId,
|
|
2668
|
+
deviceId: context.deviceId,
|
|
2669
|
+
channel: context.channel
|
|
2670
|
+
}
|
|
2671
|
+
}),
|
|
2672
|
+
keepalive: true
|
|
2673
|
+
// 页面关闭时也能发送
|
|
2674
|
+
}).catch((error) => {
|
|
2675
|
+
this.log("warn", "Track failed:", error);
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
// ==================== 剪贴板 ====================
|
|
2679
|
+
async copyToClipboard(text) {
|
|
2680
|
+
try {
|
|
2681
|
+
await navigator.clipboard.writeText(text);
|
|
2682
|
+
return { success: true, content: text };
|
|
2683
|
+
} catch (error) {
|
|
2684
|
+
return {
|
|
2685
|
+
success: false,
|
|
2686
|
+
error: error instanceof Error ? error.message : "Copy failed"
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
async readFromClipboard() {
|
|
2691
|
+
try {
|
|
2692
|
+
const text = await navigator.clipboard.readText();
|
|
2693
|
+
return { success: true, content: text };
|
|
2694
|
+
} catch (error) {
|
|
2695
|
+
return {
|
|
2696
|
+
success: false,
|
|
2697
|
+
error: error instanceof Error ? error.message : "Read failed"
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
// ==================== 状态管理 ====================
|
|
2702
|
+
getState(key) {
|
|
2703
|
+
return this.options.stateManager.getVariable(key);
|
|
2704
|
+
}
|
|
2705
|
+
setState(key, value) {
|
|
2706
|
+
this.options.stateManager.setVariable(key, value);
|
|
2707
|
+
}
|
|
2708
|
+
getVariable(name) {
|
|
2709
|
+
return this.options.stateManager.getVariable(name);
|
|
2710
|
+
}
|
|
2711
|
+
// ==================== 组件通信 ====================
|
|
2712
|
+
postMessage(componentId, message) {
|
|
2713
|
+
this.log("debug", `Post message to ${componentId}:`, message);
|
|
2714
|
+
const event = new CustomEvent(`djvlc:message:${componentId}`, {
|
|
2715
|
+
detail: { message, from: this.options.context.pageUid }
|
|
2716
|
+
});
|
|
2717
|
+
document.dispatchEvent(event);
|
|
2718
|
+
}
|
|
2719
|
+
broadcast(channel, message) {
|
|
2720
|
+
this.log("debug", `Broadcast to ${channel}:`, message);
|
|
2721
|
+
const event = new CustomEvent(`djvlc:broadcast:${channel}`, {
|
|
2722
|
+
detail: { message, from: this.options.context.pageUid }
|
|
2723
|
+
});
|
|
2724
|
+
document.dispatchEvent(event);
|
|
2725
|
+
}
|
|
2726
|
+
// ==================== 上下文信息 ====================
|
|
2727
|
+
getContext() {
|
|
2728
|
+
return { ...this.options.context };
|
|
2729
|
+
}
|
|
2730
|
+
// ==================== 私有方法 ====================
|
|
2731
|
+
buildHeaders() {
|
|
2732
|
+
const headers = {
|
|
2733
|
+
"Content-Type": "application/json",
|
|
2734
|
+
...this.options.headers
|
|
2735
|
+
};
|
|
2736
|
+
if (this.options.authToken) {
|
|
2737
|
+
headers["Authorization"] = `Bearer ${this.options.authToken}`;
|
|
2738
|
+
}
|
|
2739
|
+
return headers;
|
|
2740
|
+
}
|
|
2741
|
+
getStorageKey(key, namespace) {
|
|
2742
|
+
return `${namespace || this.storageNamespace}:${key}`;
|
|
2743
|
+
}
|
|
2744
|
+
generateIdempotencyKey(actionType, params) {
|
|
2745
|
+
const timestamp = Date.now();
|
|
2746
|
+
const paramsStr = JSON.stringify(params || {});
|
|
2747
|
+
return `${actionType}:${timestamp}:${this.simpleHash(paramsStr)}`;
|
|
2748
|
+
}
|
|
2749
|
+
simpleHash(str) {
|
|
2750
|
+
let hash = 0;
|
|
2751
|
+
for (let i = 0; i < str.length; i++) {
|
|
2752
|
+
const char = str.charCodeAt(i);
|
|
2753
|
+
hash = (hash << 5) - hash + char;
|
|
2754
|
+
hash = hash & hash;
|
|
2755
|
+
}
|
|
2756
|
+
return Math.abs(hash).toString(36);
|
|
2757
|
+
}
|
|
2758
|
+
log(level, message, ...args) {
|
|
2759
|
+
if (this.options.logger) {
|
|
2760
|
+
this.options.logger[level](message, ...args);
|
|
2761
|
+
} else if (this.options.debug) {
|
|
2762
|
+
console[level](`[HostAPI] ${message}`, ...args);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
};
|
|
2766
|
+
var SecurityManager = class {
|
|
2767
|
+
constructor(options = {}) {
|
|
2768
|
+
this.blockedComponentsMap = /* @__PURE__ */ new Map();
|
|
2769
|
+
this.blockedActionsSet = /* @__PURE__ */ new Set();
|
|
2770
|
+
this.options = {
|
|
2771
|
+
enableSRI: true,
|
|
2772
|
+
cdnDomains: [],
|
|
2773
|
+
apiDomains: [],
|
|
2774
|
+
blockedComponents: [],
|
|
2775
|
+
blockedActions: [],
|
|
2776
|
+
...options
|
|
2777
|
+
};
|
|
2778
|
+
this.updateBlockedList(options.blockedComponents || [], options.blockedActions || []);
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* 更新阻断列表
|
|
2782
|
+
*/
|
|
2783
|
+
updateBlockedList(components, actions) {
|
|
2784
|
+
this.blockedComponentsMap.clear();
|
|
2785
|
+
components.forEach((c) => {
|
|
2786
|
+
this.blockedComponentsMap.set(`${c.name}@${c.version}`, c);
|
|
2787
|
+
});
|
|
2788
|
+
this.blockedActionsSet = new Set(actions);
|
|
2789
|
+
}
|
|
2790
|
+
/**
|
|
2791
|
+
* 检查组件是否被阻断
|
|
2792
|
+
*/
|
|
2793
|
+
isComponentBlocked(name, version) {
|
|
2794
|
+
return this.blockedComponentsMap.has(`${name}@${version}`);
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* 获取组件阻断信息
|
|
2798
|
+
*/
|
|
2799
|
+
getBlockedInfo(name, version) {
|
|
2800
|
+
return this.blockedComponentsMap.get(`${name}@${version}`);
|
|
2801
|
+
}
|
|
2802
|
+
/**
|
|
2803
|
+
* 检查动作是否被阻断
|
|
2804
|
+
*/
|
|
2805
|
+
isActionBlocked(actionType) {
|
|
2806
|
+
return this.blockedActionsSet.has(actionType);
|
|
2807
|
+
}
|
|
2808
|
+
/**
|
|
2809
|
+
* 验证组件完整性
|
|
2810
|
+
*/
|
|
2811
|
+
async validateComponent(name, version, content, expectedIntegrity) {
|
|
2812
|
+
if (!this.options.enableSRI) return;
|
|
2813
|
+
const [algorithm, expectedHash] = expectedIntegrity.split("-");
|
|
2814
|
+
if (!algorithm || !expectedHash) {
|
|
2815
|
+
throw new IntegrityError(name, version, expectedIntegrity, "Invalid format");
|
|
2816
|
+
}
|
|
2817
|
+
const actualHash = await this.computeHash(content, algorithm);
|
|
2818
|
+
if (actualHash !== expectedHash) {
|
|
2819
|
+
throw new IntegrityError(name, version, expectedHash, actualHash);
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
/**
|
|
2823
|
+
* 验证 URL 是否在白名单内
|
|
2824
|
+
*/
|
|
2825
|
+
isAllowedUrl(url, type) {
|
|
2826
|
+
const domains = type === "cdn" ? this.options.cdnDomains : this.options.apiDomains;
|
|
2827
|
+
if (!domains || domains.length === 0) return true;
|
|
2828
|
+
try {
|
|
2829
|
+
const parsedUrl = new URL(url);
|
|
2830
|
+
return domains.some((domain) => {
|
|
2831
|
+
if (domain.startsWith("*.")) {
|
|
2832
|
+
const suffix = domain.slice(2);
|
|
2833
|
+
return parsedUrl.hostname.endsWith(suffix);
|
|
2834
|
+
}
|
|
2835
|
+
return parsedUrl.hostname === domain;
|
|
2836
|
+
});
|
|
2837
|
+
} catch {
|
|
2838
|
+
return false;
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* 生成 CSP 策略
|
|
2843
|
+
*/
|
|
2844
|
+
generateCSPPolicy() {
|
|
2845
|
+
const cdnDomains = this.options.cdnDomains || [];
|
|
2846
|
+
const apiDomains = this.options.apiDomains || [];
|
|
2847
|
+
const scriptSrc = ["'self'", ...cdnDomains].join(" ");
|
|
2848
|
+
const connectSrc = ["'self'", ...apiDomains].join(" ");
|
|
2849
|
+
const styleSrc = ["'self'", "'unsafe-inline'", ...cdnDomains].join(" ");
|
|
2850
|
+
const imgSrc = ["'self'", "data:", "blob:", ...cdnDomains].join(" ");
|
|
2851
|
+
return [
|
|
2852
|
+
`default-src 'self'`,
|
|
2853
|
+
`script-src ${scriptSrc}`,
|
|
2854
|
+
`style-src ${styleSrc}`,
|
|
2855
|
+
`img-src ${imgSrc}`,
|
|
2856
|
+
`connect-src ${connectSrc}`,
|
|
2857
|
+
`font-src 'self' data: ${cdnDomains.join(" ")}`,
|
|
2858
|
+
`frame-ancestors 'self'`,
|
|
2859
|
+
`base-uri 'self'`,
|
|
2860
|
+
`form-action 'self'`
|
|
2861
|
+
].join("; ");
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* 应用 CSP 策略
|
|
2865
|
+
*/
|
|
2866
|
+
applyCSP() {
|
|
2867
|
+
const meta = document.createElement("meta");
|
|
2868
|
+
meta.httpEquiv = "Content-Security-Policy";
|
|
2869
|
+
meta.content = this.generateCSPPolicy();
|
|
2870
|
+
document.head.appendChild(meta);
|
|
2871
|
+
}
|
|
2872
|
+
/**
|
|
2873
|
+
* 确保组件未被阻断
|
|
2874
|
+
*/
|
|
2875
|
+
assertNotBlocked(name, version) {
|
|
2876
|
+
const blocked = this.getBlockedInfo(name, version);
|
|
2877
|
+
if (blocked) {
|
|
2878
|
+
throw new ComponentBlockedError(name, version, blocked.reason);
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* 计算哈希值
|
|
2883
|
+
*/
|
|
2884
|
+
async computeHash(content, algorithm) {
|
|
2885
|
+
const encoder = new TextEncoder();
|
|
2886
|
+
const data = encoder.encode(content);
|
|
2887
|
+
const hashBuffer = await crypto.subtle.digest(
|
|
2888
|
+
algorithm.toUpperCase(),
|
|
2889
|
+
data
|
|
2890
|
+
);
|
|
2891
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
2892
|
+
return btoa(String.fromCharCode(...hashArray));
|
|
2893
|
+
}
|
|
2894
|
+
_log(level, message) {
|
|
2895
|
+
if (this.options.logger) {
|
|
2896
|
+
this.options.logger[level](message);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
// 使用 _log 避免 unused 错误,后续可按需启用日志
|
|
2900
|
+
get log() {
|
|
2901
|
+
return this._log.bind(this);
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
var TelemetryManager = class {
|
|
2905
|
+
constructor(options) {
|
|
2906
|
+
this.spans = /* @__PURE__ */ new Map();
|
|
2907
|
+
this.metrics = [];
|
|
2908
|
+
this.errors = [];
|
|
2909
|
+
this.options = {
|
|
2910
|
+
enabled: true,
|
|
2911
|
+
sampleRate: 1,
|
|
2912
|
+
...options
|
|
2913
|
+
};
|
|
2914
|
+
this.traceId = this.generateId();
|
|
2915
|
+
this.shouldSample = Math.random() < (this.options.sampleRate ?? 1);
|
|
2916
|
+
}
|
|
2917
|
+
/**
|
|
2918
|
+
* 获取 Trace ID
|
|
2919
|
+
*/
|
|
2920
|
+
getTraceId() {
|
|
2921
|
+
return this.traceId;
|
|
2922
|
+
}
|
|
2923
|
+
/**
|
|
2924
|
+
* 获取 W3C Trace Context 格式的 traceparent
|
|
2925
|
+
*/
|
|
2926
|
+
getTraceparent() {
|
|
2927
|
+
return `00-${this.traceId}-${this.generateId().slice(0, 16)}-01`;
|
|
2928
|
+
}
|
|
2929
|
+
/**
|
|
2930
|
+
* 开始一个 Span
|
|
2931
|
+
*/
|
|
2932
|
+
startSpan(name, parentSpanId, attributes) {
|
|
2933
|
+
const span = {
|
|
2934
|
+
spanId: this.generateId().slice(0, 16),
|
|
2935
|
+
traceId: this.traceId,
|
|
2936
|
+
parentSpanId,
|
|
2937
|
+
name,
|
|
2938
|
+
startTime: performance.now(),
|
|
2939
|
+
attributes
|
|
2940
|
+
};
|
|
2941
|
+
this.spans.set(span.spanId, span);
|
|
2942
|
+
this.log("debug", `Span started: ${name} (${span.spanId})`);
|
|
2943
|
+
return span;
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* 结束一个 Span
|
|
2947
|
+
*/
|
|
2948
|
+
endSpan(spanId, status = "ok") {
|
|
2949
|
+
const span = this.spans.get(spanId);
|
|
2950
|
+
if (span) {
|
|
2951
|
+
span.endTime = performance.now();
|
|
2952
|
+
span.status = status;
|
|
2953
|
+
this.log("debug", `Span ended: ${span.name} (${spanId}) - ${span.endTime - span.startTime}ms`);
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
/**
|
|
2957
|
+
* 记录性能指标
|
|
2958
|
+
*/
|
|
2959
|
+
recordMetric(type, name, duration, extra) {
|
|
2960
|
+
const metric = {
|
|
2961
|
+
type,
|
|
2962
|
+
name,
|
|
2963
|
+
duration,
|
|
2964
|
+
startTime: performance.now() - duration,
|
|
2965
|
+
extra
|
|
2966
|
+
};
|
|
2967
|
+
this.metrics.push(metric);
|
|
2968
|
+
this.log("debug", `Metric: ${type} - ${name} - ${duration}ms`);
|
|
2969
|
+
if (this.options.onMetric) {
|
|
2970
|
+
this.options.onMetric(metric);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
/**
|
|
2974
|
+
* 记录错误
|
|
2975
|
+
*/
|
|
2976
|
+
recordError(error, context) {
|
|
2977
|
+
const runtimeError = {
|
|
2978
|
+
type: "LOAD_ERROR",
|
|
2979
|
+
message: error.message,
|
|
2980
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
2981
|
+
timestamp: Date.now(),
|
|
2982
|
+
traceId: this.traceId,
|
|
2983
|
+
...context
|
|
2984
|
+
};
|
|
2985
|
+
this.errors.push(runtimeError);
|
|
2986
|
+
this.log("error", `Error recorded: ${error.message}`);
|
|
2987
|
+
if (this.shouldSample && this.options.enabled) {
|
|
2988
|
+
this.reportError(runtimeError);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* 记录页面加载时间
|
|
2993
|
+
*/
|
|
2994
|
+
recordPageLoad(duration, extra) {
|
|
2995
|
+
this.recordMetric("page_resolve", "page_load", duration, {
|
|
2996
|
+
pageVersionId: this.options.pageVersionId,
|
|
2997
|
+
...extra
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* 记录组件加载时间
|
|
3002
|
+
*/
|
|
3003
|
+
recordComponentLoad(name, version, duration, success) {
|
|
3004
|
+
this.recordMetric("component_load", `${name}@${version}`, duration, {
|
|
3005
|
+
success
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* 记录首次渲染时间
|
|
3010
|
+
*/
|
|
3011
|
+
recordFirstRender(duration) {
|
|
3012
|
+
this.recordMetric("first_render", "first_render", duration, {
|
|
3013
|
+
pageVersionId: this.options.pageVersionId
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
/**
|
|
3017
|
+
* 记录动作执行时间
|
|
3018
|
+
*/
|
|
3019
|
+
recordActionExecute(actionType, duration, success) {
|
|
3020
|
+
this.recordMetric("action_execute", actionType, duration, { success });
|
|
3021
|
+
}
|
|
3022
|
+
/**
|
|
3023
|
+
* 记录查询执行时间
|
|
3024
|
+
*/
|
|
3025
|
+
recordQueryExecute(queryId, duration, success, fromCache) {
|
|
3026
|
+
this.recordMetric("query_execute", queryId, duration, {
|
|
3027
|
+
success,
|
|
3028
|
+
fromCache
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
/**
|
|
3032
|
+
* 获取所有指标
|
|
3033
|
+
*/
|
|
3034
|
+
getMetrics() {
|
|
3035
|
+
return [...this.metrics];
|
|
3036
|
+
}
|
|
3037
|
+
/**
|
|
3038
|
+
* 获取所有 Span
|
|
3039
|
+
*/
|
|
3040
|
+
getSpans() {
|
|
3041
|
+
return Array.from(this.spans.values());
|
|
3042
|
+
}
|
|
3043
|
+
/**
|
|
3044
|
+
* 清理数据
|
|
3045
|
+
*/
|
|
3046
|
+
clear() {
|
|
3047
|
+
this.spans.clear();
|
|
3048
|
+
this.metrics = [];
|
|
3049
|
+
this.errors = [];
|
|
3050
|
+
}
|
|
3051
|
+
/**
|
|
3052
|
+
* 刷新上报
|
|
3053
|
+
*/
|
|
3054
|
+
async flush() {
|
|
3055
|
+
if (!this.shouldSample || !this.options.enabled || !this.options.endpoint) {
|
|
3056
|
+
return;
|
|
3057
|
+
}
|
|
3058
|
+
const payload = {
|
|
3059
|
+
traceId: this.traceId,
|
|
3060
|
+
pageVersionId: this.options.pageVersionId,
|
|
3061
|
+
appId: this.options.appId,
|
|
3062
|
+
spans: this.getSpans(),
|
|
3063
|
+
metrics: this.metrics,
|
|
3064
|
+
errors: this.errors,
|
|
3065
|
+
timestamp: Date.now()
|
|
3066
|
+
};
|
|
3067
|
+
try {
|
|
3068
|
+
await fetch(this.options.endpoint, {
|
|
3069
|
+
method: "POST",
|
|
3070
|
+
headers: { "Content-Type": "application/json" },
|
|
3071
|
+
body: JSON.stringify(payload),
|
|
3072
|
+
keepalive: true
|
|
3073
|
+
});
|
|
3074
|
+
} catch (error) {
|
|
3075
|
+
this.log("warn", "Failed to flush telemetry:", error);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
reportError(error) {
|
|
3079
|
+
if (this.options.endpoint) {
|
|
3080
|
+
fetch(`${this.options.endpoint}/errors`, {
|
|
3081
|
+
method: "POST",
|
|
3082
|
+
headers: { "Content-Type": "application/json" },
|
|
3083
|
+
body: JSON.stringify(error),
|
|
3084
|
+
keepalive: true
|
|
3085
|
+
}).catch(() => {
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
generateId() {
|
|
3090
|
+
const array = new Uint8Array(16);
|
|
3091
|
+
crypto.getRandomValues(array);
|
|
3092
|
+
return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3093
|
+
}
|
|
3094
|
+
log(level, message, ...args) {
|
|
3095
|
+
if (this.options.logger) {
|
|
3096
|
+
this.options.logger[level](message, ...args);
|
|
3097
|
+
} else if (this.options.debug) {
|
|
3098
|
+
console[level](`[Telemetry] ${message}`, ...args);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
3102
|
+
var BaseRenderer = class {
|
|
3103
|
+
constructor(options) {
|
|
3104
|
+
this.container = null;
|
|
3105
|
+
this.renderedElements = /* @__PURE__ */ new Map();
|
|
3106
|
+
this.expressionContext = {
|
|
3107
|
+
state: {},
|
|
3108
|
+
query: {},
|
|
3109
|
+
context: {}
|
|
3110
|
+
};
|
|
3111
|
+
this.options = options;
|
|
3112
|
+
}
|
|
3113
|
+
/**
|
|
3114
|
+
* 初始化渲染器
|
|
3115
|
+
*/
|
|
3116
|
+
init() {
|
|
3117
|
+
this.log("debug", "Renderer initialized");
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* 渲染页面
|
|
3121
|
+
*/
|
|
3122
|
+
render(schema, container) {
|
|
3123
|
+
this.container = container;
|
|
3124
|
+
this.log("debug", "Rendering page", schema.page.id);
|
|
3125
|
+
container.innerHTML = "";
|
|
3126
|
+
this.applyPageStyles(schema.page.canvas);
|
|
3127
|
+
const fragment = document.createDocumentFragment();
|
|
3128
|
+
for (const component of schema.components) {
|
|
3129
|
+
const element = this.renderComponent(component);
|
|
3130
|
+
if (element) {
|
|
3131
|
+
fragment.appendChild(element);
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
container.appendChild(fragment);
|
|
3135
|
+
this.log("info", `Rendered ${schema.components.length} components`);
|
|
3136
|
+
}
|
|
3137
|
+
/**
|
|
3138
|
+
* 更新组件属性
|
|
3139
|
+
*/
|
|
3140
|
+
updateComponent(componentId, props) {
|
|
3141
|
+
const element = this.renderedElements.get(componentId);
|
|
3142
|
+
if (!element) {
|
|
3143
|
+
this.log("warn", `Component not found: ${componentId}`);
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
this.applyProps(element, props);
|
|
3147
|
+
}
|
|
3148
|
+
/**
|
|
3149
|
+
* 更新表达式上下文
|
|
3150
|
+
*/
|
|
3151
|
+
updateContext(context) {
|
|
3152
|
+
this.expressionContext = {
|
|
3153
|
+
...this.expressionContext,
|
|
3154
|
+
...context
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
/**
|
|
3158
|
+
* 销毁渲染器
|
|
3159
|
+
*/
|
|
3160
|
+
destroy() {
|
|
3161
|
+
this.renderedElements.forEach((element) => {
|
|
3162
|
+
element.remove();
|
|
3163
|
+
});
|
|
3164
|
+
this.renderedElements.clear();
|
|
3165
|
+
if (this.container) {
|
|
3166
|
+
this.container.innerHTML = "";
|
|
3167
|
+
}
|
|
3168
|
+
this.log("debug", "Renderer destroyed");
|
|
3169
|
+
}
|
|
3170
|
+
/**
|
|
3171
|
+
* 渲染单个组件
|
|
3172
|
+
*/
|
|
3173
|
+
renderComponent(instance) {
|
|
3174
|
+
const { id, type, props, style, children, visible } = instance;
|
|
3175
|
+
if (visible === false) {
|
|
3176
|
+
return null;
|
|
3177
|
+
}
|
|
3178
|
+
try {
|
|
3179
|
+
const loadedComponent = this.options.components.get(type);
|
|
3180
|
+
let element;
|
|
3181
|
+
if (loadedComponent && customElements.get(type)) {
|
|
3182
|
+
element = document.createElement(type);
|
|
3183
|
+
} else {
|
|
3184
|
+
element = document.createElement("div");
|
|
3185
|
+
element.className = `djvlc-component djvlc-${type}`;
|
|
3186
|
+
if (!loadedComponent) {
|
|
3187
|
+
this.log("warn", `Component not loaded: ${type}`);
|
|
3188
|
+
element.textContent = `[Component: ${type}]`;
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
element.setAttribute("data-component-id", id);
|
|
3192
|
+
element.setAttribute("data-component-type", type);
|
|
3193
|
+
const resolvedProps = this.resolveProps(props);
|
|
3194
|
+
this.applyProps(element, resolvedProps);
|
|
3195
|
+
if (style) {
|
|
3196
|
+
this.applyStyles(element, style);
|
|
3197
|
+
}
|
|
3198
|
+
this.options.injectHostApi(element, id);
|
|
3199
|
+
if (children && children.length > 0) {
|
|
3200
|
+
for (const child of children) {
|
|
3201
|
+
const childElement = this.renderComponent(child);
|
|
3202
|
+
if (childElement) {
|
|
3203
|
+
element.appendChild(childElement);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
this.renderedElements.set(id, element);
|
|
3208
|
+
return element;
|
|
3209
|
+
} catch (error) {
|
|
3210
|
+
this.log("error", `Failed to render component: ${id}`, error);
|
|
3211
|
+
if (this.options.onRenderError) {
|
|
3212
|
+
return this.options.onRenderError(id, error);
|
|
3213
|
+
}
|
|
3214
|
+
const fallback = document.createElement("div");
|
|
3215
|
+
fallback.className = "djvlc-error-boundary";
|
|
3216
|
+
fallback.textContent = `Error rendering component: ${type}`;
|
|
3217
|
+
return fallback;
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
/**
|
|
3221
|
+
* 解析 props 中的表达式
|
|
3222
|
+
*/
|
|
3223
|
+
resolveProps(props) {
|
|
3224
|
+
const resolved = {};
|
|
3225
|
+
for (const [key, value] of Object.entries(props)) {
|
|
3226
|
+
if (isExpressionBinding(value)) {
|
|
3227
|
+
const result = this.options.expressionEngine.evaluateWithFallback(
|
|
3228
|
+
value.expression,
|
|
3229
|
+
this.expressionContext,
|
|
3230
|
+
value.fallback
|
|
3231
|
+
);
|
|
3232
|
+
resolved[key] = result;
|
|
3233
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3234
|
+
resolved[key] = this.resolveProps(value);
|
|
3235
|
+
} else {
|
|
3236
|
+
resolved[key] = value;
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
return resolved;
|
|
3240
|
+
}
|
|
3241
|
+
/**
|
|
3242
|
+
* 应用 props 到元素
|
|
3243
|
+
*/
|
|
3244
|
+
applyProps(element, props) {
|
|
3245
|
+
for (const [key, value] of Object.entries(props)) {
|
|
3246
|
+
if (value === null || value === void 0) continue;
|
|
3247
|
+
if (element.tagName.includes("-")) {
|
|
3248
|
+
element[key] = value;
|
|
3249
|
+
} else {
|
|
3250
|
+
if (typeof value === "boolean") {
|
|
3251
|
+
if (value) {
|
|
3252
|
+
element.setAttribute(key, "");
|
|
3253
|
+
} else {
|
|
3254
|
+
element.removeAttribute(key);
|
|
3255
|
+
}
|
|
3256
|
+
} else if (typeof value === "object") {
|
|
3257
|
+
element.setAttribute(key, JSON.stringify(value));
|
|
3258
|
+
} else {
|
|
3259
|
+
element.setAttribute(key, String(value));
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
/**
|
|
3265
|
+
* 应用样式到元素
|
|
3266
|
+
*/
|
|
3267
|
+
applyStyles(element, style) {
|
|
3268
|
+
for (const [key, value] of Object.entries(style)) {
|
|
3269
|
+
if (value === null || value === void 0) continue;
|
|
3270
|
+
let cssValue;
|
|
3271
|
+
if (typeof value === "number") {
|
|
3272
|
+
const unitless = ["zIndex", "opacity", "flex", "fontWeight"];
|
|
3273
|
+
cssValue = unitless.includes(key) ? String(value) : `${value}px`;
|
|
3274
|
+
} else {
|
|
3275
|
+
cssValue = String(value);
|
|
3276
|
+
}
|
|
3277
|
+
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
3278
|
+
element.style.setProperty(cssKey, cssValue);
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
/**
|
|
3282
|
+
* 应用页面样式
|
|
3283
|
+
*/
|
|
3284
|
+
applyPageStyles(canvas) {
|
|
3285
|
+
if (!this.container) return;
|
|
3286
|
+
this.container.style.width = `${canvas.width}px`;
|
|
3287
|
+
if (canvas.height) {
|
|
3288
|
+
this.container.style.minHeight = `${canvas.height}px`;
|
|
3289
|
+
}
|
|
3290
|
+
if (canvas.background) {
|
|
3291
|
+
this.container.style.background = canvas.background;
|
|
3292
|
+
}
|
|
3293
|
+
this.container.classList.add("djvlc-page", `djvlc-canvas-${canvas.type}`);
|
|
3294
|
+
}
|
|
3295
|
+
log(level, message, ...args) {
|
|
3296
|
+
if (this.options.logger) {
|
|
3297
|
+
this.options.logger[level](message, ...args);
|
|
3298
|
+
} else if (this.options.debug) {
|
|
3299
|
+
console[level](`[Renderer] ${message}`, ...args);
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
};
|
|
3303
|
+
function registerFallbackComponents() {
|
|
3304
|
+
if (!customElements.get("djvlc-fallback")) {
|
|
3305
|
+
customElements.define(
|
|
3306
|
+
"djvlc-fallback",
|
|
3307
|
+
class extends HTMLElement {
|
|
3308
|
+
constructor() {
|
|
3309
|
+
super();
|
|
3310
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
3311
|
+
shadow.innerHTML = `
|
|
3312
|
+
<style>
|
|
3313
|
+
:host {
|
|
3314
|
+
display: block;
|
|
3315
|
+
padding: 16px;
|
|
3316
|
+
background: #fff2f0;
|
|
3317
|
+
border: 1px solid #ffccc7;
|
|
3318
|
+
border-radius: 4px;
|
|
3319
|
+
color: #ff4d4f;
|
|
3320
|
+
font-size: 14px;
|
|
3321
|
+
}
|
|
3322
|
+
.title {
|
|
3323
|
+
font-weight: 600;
|
|
3324
|
+
margin-bottom: 8px;
|
|
3325
|
+
}
|
|
3326
|
+
.message {
|
|
3327
|
+
color: #666;
|
|
3328
|
+
}
|
|
3329
|
+
</style>
|
|
3330
|
+
<div class="title">组件加载失败</div>
|
|
3331
|
+
<div class="message"><slot>请刷新页面重试</slot></div>
|
|
3332
|
+
`;
|
|
3333
|
+
}
|
|
3334
|
+
static get observedAttributes() {
|
|
3335
|
+
return ["message", "component-name"];
|
|
3336
|
+
}
|
|
3337
|
+
attributeChangedCallback(name, _oldValue, newValue) {
|
|
3338
|
+
if (name === "message" && this.shadowRoot) {
|
|
3339
|
+
const message = this.shadowRoot.querySelector(".message");
|
|
3340
|
+
if (message) message.textContent = newValue;
|
|
3341
|
+
}
|
|
3342
|
+
if (name === "component-name" && this.shadowRoot) {
|
|
3343
|
+
const title = this.shadowRoot.querySelector(".title");
|
|
3344
|
+
if (title) title.textContent = `组件 ${newValue} 加载失败`;
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
);
|
|
3349
|
+
}
|
|
3350
|
+
if (!customElements.get("djvlc-blocked")) {
|
|
3351
|
+
customElements.define(
|
|
3352
|
+
"djvlc-blocked",
|
|
3353
|
+
class extends HTMLElement {
|
|
3354
|
+
constructor() {
|
|
3355
|
+
super();
|
|
3356
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
3357
|
+
shadow.innerHTML = `
|
|
3358
|
+
<style>
|
|
3359
|
+
:host {
|
|
3360
|
+
display: block;
|
|
3361
|
+
padding: 16px;
|
|
3362
|
+
background: #fffbe6;
|
|
3363
|
+
border: 1px solid #ffe58f;
|
|
3364
|
+
border-radius: 4px;
|
|
3365
|
+
color: #faad14;
|
|
3366
|
+
font-size: 14px;
|
|
3367
|
+
}
|
|
3368
|
+
.icon {
|
|
3369
|
+
margin-right: 8px;
|
|
3370
|
+
}
|
|
3371
|
+
</style>
|
|
3372
|
+
<span class="icon">⚠️</span>
|
|
3373
|
+
<span>此组件已被暂停使用</span>
|
|
3374
|
+
`;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
if (!customElements.get("djvlc-error-boundary")) {
|
|
3380
|
+
customElements.define(
|
|
3381
|
+
"djvlc-error-boundary",
|
|
3382
|
+
class extends HTMLElement {
|
|
3383
|
+
constructor() {
|
|
3384
|
+
super();
|
|
3385
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
3386
|
+
shadow.innerHTML = `
|
|
3387
|
+
<style>
|
|
3388
|
+
:host {
|
|
3389
|
+
display: block;
|
|
3390
|
+
padding: 16px;
|
|
3391
|
+
background: #f5f5f5;
|
|
3392
|
+
border: 1px dashed #d9d9d9;
|
|
3393
|
+
border-radius: 4px;
|
|
3394
|
+
color: #999;
|
|
3395
|
+
font-size: 14px;
|
|
3396
|
+
text-align: center;
|
|
3397
|
+
}
|
|
3398
|
+
</style>
|
|
3399
|
+
<slot>渲染出错</slot>
|
|
3400
|
+
`;
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
function createFallbackElement(type, message, componentName) {
|
|
3407
|
+
const tagName = `djvlc-${"error-boundary"}`;
|
|
3408
|
+
const element = document.createElement(tagName);
|
|
3409
|
+
if (message) {
|
|
3410
|
+
element.setAttribute("message", message);
|
|
3411
|
+
}
|
|
3412
|
+
return element;
|
|
3413
|
+
}
|
|
3414
|
+
function createRuntime(options) {
|
|
3415
|
+
return new DjvlcRuntime(options);
|
|
3416
|
+
}
|
|
3417
|
+
var DjvlcRuntime = class {
|
|
3418
|
+
constructor(options) {
|
|
3419
|
+
this.container = null;
|
|
3420
|
+
this.options = {
|
|
3421
|
+
channel: "prod",
|
|
3422
|
+
debug: false,
|
|
3423
|
+
enableSRI: true,
|
|
3424
|
+
...options
|
|
3425
|
+
};
|
|
3426
|
+
this.logger = this.createLogger();
|
|
3427
|
+
this.stateManager = new StateManager();
|
|
3428
|
+
this.eventBus = new EventBus({ debug: options.debug, logger: this.logger });
|
|
3429
|
+
this.expressionEngine = new ExpressionEngine({ debug: options.debug, logger: this.logger });
|
|
3430
|
+
this.pageLoader = new PageLoader({
|
|
3431
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
3432
|
+
channel: options.channel,
|
|
3433
|
+
authToken: options.authToken,
|
|
3434
|
+
previewToken: options.previewToken,
|
|
3435
|
+
headers: options.headers,
|
|
3436
|
+
logger: this.logger
|
|
3437
|
+
});
|
|
3438
|
+
this.componentLoader = new ComponentLoader({
|
|
3439
|
+
cdnBaseUrl: options.cdnBaseUrl,
|
|
3440
|
+
enableSRI: options.enableSRI,
|
|
3441
|
+
logger: this.logger
|
|
3442
|
+
});
|
|
3443
|
+
this.assetLoader = new AssetLoader({
|
|
3444
|
+
cdnHosts: [new URL(options.cdnBaseUrl).host],
|
|
3445
|
+
apiHosts: [new URL(options.apiBaseUrl).host]
|
|
3446
|
+
});
|
|
3447
|
+
this.securityManager = new SecurityManager({
|
|
3448
|
+
enableSRI: options.enableSRI,
|
|
3449
|
+
cdnDomains: [new URL(options.cdnBaseUrl).host],
|
|
3450
|
+
apiDomains: [new URL(options.apiBaseUrl).host],
|
|
3451
|
+
logger: this.logger
|
|
3452
|
+
});
|
|
3453
|
+
this.log("info", "Runtime created");
|
|
3454
|
+
}
|
|
3455
|
+
/**
|
|
3456
|
+
* 初始化运行时
|
|
3457
|
+
*/
|
|
3458
|
+
async init() {
|
|
3459
|
+
this.log("info", "Initializing runtime");
|
|
3460
|
+
const startTime = performance.now();
|
|
3461
|
+
try {
|
|
3462
|
+
this.container = this.resolveContainer();
|
|
3463
|
+
this.assetLoader.preconnectAll();
|
|
3464
|
+
this.pageLoader.preconnect();
|
|
3465
|
+
registerFallbackComponents();
|
|
3466
|
+
this.stateManager.setPhase("resolving");
|
|
3467
|
+
const initTime = performance.now() - startTime;
|
|
3468
|
+
this.log("info", `Runtime initialized in ${initTime.toFixed(2)}ms`);
|
|
3469
|
+
} catch (error) {
|
|
3470
|
+
this.handleError(error);
|
|
3471
|
+
throw error;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
/**
|
|
3475
|
+
* 加载页面
|
|
3476
|
+
*/
|
|
3477
|
+
async load() {
|
|
3478
|
+
var _a;
|
|
3479
|
+
this.log("info", "Loading page:", this.options.pageUid);
|
|
3480
|
+
const startTime = performance.now();
|
|
3481
|
+
try {
|
|
3482
|
+
this.stateManager.setPhase("resolving");
|
|
3483
|
+
const page = await this.pageLoader.resolve(this.options.pageUid, {
|
|
3484
|
+
uid: this.options.userId,
|
|
3485
|
+
deviceId: this.options.deviceId
|
|
3486
|
+
});
|
|
3487
|
+
this.stateManager.setPage(page);
|
|
3488
|
+
this.telemetryManager = new TelemetryManager({
|
|
3489
|
+
pageVersionId: page.pageVersionId,
|
|
3490
|
+
debug: this.options.debug,
|
|
3491
|
+
logger: this.logger,
|
|
3492
|
+
onMetric: this.options.onMetric
|
|
3493
|
+
});
|
|
3494
|
+
if (page.runtimeConfig) {
|
|
3495
|
+
this.securityManager.updateBlockedList(
|
|
3496
|
+
page.runtimeConfig.blockedComponents || [],
|
|
3497
|
+
page.runtimeConfig.killSwitches || []
|
|
3498
|
+
);
|
|
3499
|
+
this.componentLoader.updateBlockedList(
|
|
3500
|
+
((_a = page.runtimeConfig.blockedComponents) == null ? void 0 : _a.map((c) => `${c.name}@${c.version}`)) || []
|
|
3501
|
+
);
|
|
3502
|
+
}
|
|
3503
|
+
this.stateManager.setPhase("loading");
|
|
3504
|
+
this.componentLoader.preload(page.manifest.components);
|
|
3505
|
+
const componentResults = await this.componentLoader.loadAll(page.manifest);
|
|
3506
|
+
componentResults.forEach((result, key) => {
|
|
3507
|
+
this.stateManager.setComponentStatus(key, result);
|
|
3508
|
+
this.telemetryManager.recordComponentLoad(
|
|
3509
|
+
result.name,
|
|
3510
|
+
result.version,
|
|
3511
|
+
result.loadTime || 0,
|
|
3512
|
+
result.status === "loaded"
|
|
3513
|
+
);
|
|
3514
|
+
});
|
|
3515
|
+
this.initHostApi(page);
|
|
3516
|
+
this.initRenderer();
|
|
3517
|
+
const loadTime = performance.now() - startTime;
|
|
3518
|
+
this.telemetryManager.recordPageLoad(loadTime);
|
|
3519
|
+
this.log("info", `Page loaded in ${loadTime.toFixed(2)}ms`);
|
|
3520
|
+
this.emitEvent("page:loaded", { page, loadTime });
|
|
3521
|
+
return page;
|
|
3522
|
+
} catch (error) {
|
|
3523
|
+
this.stateManager.setPhase("error");
|
|
3524
|
+
this.handleError(error);
|
|
3525
|
+
throw error;
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
/**
|
|
3529
|
+
* 渲染页面
|
|
3530
|
+
*/
|
|
3531
|
+
async render() {
|
|
3532
|
+
const state = this.stateManager.getState();
|
|
3533
|
+
if (!state.page || !this.container) {
|
|
3534
|
+
throw new PageLoadError("Page not loaded");
|
|
3535
|
+
}
|
|
3536
|
+
this.log("info", "Rendering page");
|
|
3537
|
+
const startTime = performance.now();
|
|
3538
|
+
try {
|
|
3539
|
+
this.stateManager.setPhase("rendering");
|
|
3540
|
+
this.renderer.updateContext({
|
|
3541
|
+
state: state.variables,
|
|
3542
|
+
query: state.queries,
|
|
3543
|
+
context: {
|
|
3544
|
+
userId: this.options.userId,
|
|
3545
|
+
deviceId: this.options.deviceId,
|
|
3546
|
+
channel: this.options.channel,
|
|
3547
|
+
pageVersionId: state.page.pageVersionId
|
|
3548
|
+
}
|
|
3549
|
+
});
|
|
3550
|
+
this.renderer.render(state.page.pageJson, this.container);
|
|
3551
|
+
this.stateManager.setPhase("ready");
|
|
3552
|
+
const renderTime = performance.now() - startTime;
|
|
3553
|
+
this.telemetryManager.recordFirstRender(renderTime);
|
|
3554
|
+
this.log("info", `Page rendered in ${renderTime.toFixed(2)}ms`);
|
|
3555
|
+
} catch (error) {
|
|
3556
|
+
this.stateManager.setPhase("error");
|
|
3557
|
+
this.handleError(error);
|
|
3558
|
+
throw error;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
/**
|
|
3562
|
+
* 获取 Host API
|
|
3563
|
+
*/
|
|
3564
|
+
getHostApi() {
|
|
3565
|
+
return this.hostApi;
|
|
3566
|
+
}
|
|
3567
|
+
/**
|
|
3568
|
+
* 获取当前状态
|
|
3569
|
+
*/
|
|
3570
|
+
getState() {
|
|
3571
|
+
return this.stateManager.getState();
|
|
3572
|
+
}
|
|
3573
|
+
/**
|
|
3574
|
+
* 订阅状态变更
|
|
3575
|
+
*/
|
|
3576
|
+
onStateChange(listener) {
|
|
3577
|
+
return this.stateManager.subscribe(listener);
|
|
3578
|
+
}
|
|
3579
|
+
/**
|
|
3580
|
+
* 订阅事件
|
|
3581
|
+
*/
|
|
3582
|
+
on(type, handler) {
|
|
3583
|
+
return this.eventBus.on(type, handler);
|
|
3584
|
+
}
|
|
3585
|
+
/**
|
|
3586
|
+
* 更新组件
|
|
3587
|
+
*/
|
|
3588
|
+
updateComponent(componentId, props) {
|
|
3589
|
+
this.renderer.updateComponent(componentId, props);
|
|
3590
|
+
}
|
|
3591
|
+
/**
|
|
3592
|
+
* 设置变量
|
|
3593
|
+
*/
|
|
3594
|
+
setVariable(key, value) {
|
|
3595
|
+
this.stateManager.setVariable(key, value);
|
|
3596
|
+
const state = this.stateManager.getState();
|
|
3597
|
+
if (state.page && this.container) {
|
|
3598
|
+
this.renderer.updateContext({
|
|
3599
|
+
state: state.variables
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* 刷新数据
|
|
3605
|
+
*/
|
|
3606
|
+
async refreshData(queryId) {
|
|
3607
|
+
const result = await this.hostApi.requestData(queryId);
|
|
3608
|
+
if (result.success) {
|
|
3609
|
+
this.stateManager.setQuery(queryId, result.data);
|
|
3610
|
+
const state = this.stateManager.getState();
|
|
3611
|
+
this.renderer.updateContext({
|
|
3612
|
+
query: state.queries
|
|
3613
|
+
});
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
/**
|
|
3617
|
+
* 销毁运行时
|
|
3618
|
+
*/
|
|
3619
|
+
destroy() {
|
|
3620
|
+
var _a, _b;
|
|
3621
|
+
this.log("info", "Destroying runtime");
|
|
3622
|
+
(_a = this.telemetryManager) == null ? void 0 : _a.flush();
|
|
3623
|
+
(_b = this.renderer) == null ? void 0 : _b.destroy();
|
|
3624
|
+
this.eventBus.clear();
|
|
3625
|
+
this.stateManager.setDestroyed();
|
|
3626
|
+
if (this.container) {
|
|
3627
|
+
this.container.innerHTML = "";
|
|
3628
|
+
}
|
|
3629
|
+
this.log("info", "Runtime destroyed");
|
|
3630
|
+
}
|
|
3631
|
+
// ==================== 私有方法 ====================
|
|
3632
|
+
resolveContainer() {
|
|
3633
|
+
const { container } = this.options;
|
|
3634
|
+
if (typeof container === "string") {
|
|
3635
|
+
const element = document.querySelector(container);
|
|
3636
|
+
if (!element) {
|
|
3637
|
+
throw new Error(`Container not found: ${container}`);
|
|
3638
|
+
}
|
|
3639
|
+
return element;
|
|
3640
|
+
}
|
|
3641
|
+
return container;
|
|
3642
|
+
}
|
|
3643
|
+
initHostApi(page) {
|
|
3644
|
+
const context = {
|
|
3645
|
+
pageUid: page.pageUid,
|
|
3646
|
+
pageVersionId: page.pageVersionId,
|
|
3647
|
+
runtimeVersion: "0.1.0",
|
|
3648
|
+
userId: this.options.userId,
|
|
3649
|
+
deviceId: this.options.deviceId,
|
|
3650
|
+
channel: this.options.channel,
|
|
3651
|
+
isEditMode: false,
|
|
3652
|
+
isPreviewMode: page.isPreview || false
|
|
3653
|
+
};
|
|
3654
|
+
this.hostApi = new HostAPIImpl({
|
|
3655
|
+
apiBaseUrl: this.options.apiBaseUrl,
|
|
3656
|
+
authToken: this.options.authToken,
|
|
3657
|
+
headers: this.options.headers,
|
|
3658
|
+
stateManager: this.stateManager,
|
|
3659
|
+
eventBus: this.eventBus,
|
|
3660
|
+
expressionEngine: this.expressionEngine,
|
|
3661
|
+
context,
|
|
3662
|
+
debug: this.options.debug,
|
|
3663
|
+
logger: this.logger
|
|
3664
|
+
});
|
|
3665
|
+
}
|
|
3666
|
+
initRenderer() {
|
|
3667
|
+
const components = /* @__PURE__ */ new Map();
|
|
3668
|
+
const state = this.stateManager.getState();
|
|
3669
|
+
state.components.forEach((result, key) => {
|
|
3670
|
+
if (result.status === "loaded" && result.component) {
|
|
3671
|
+
const [name, version] = key.split("@");
|
|
3672
|
+
components.set(name, {
|
|
3673
|
+
name,
|
|
3674
|
+
version,
|
|
3675
|
+
Component: result.component,
|
|
3676
|
+
loadTime: result.loadTime || 0
|
|
3677
|
+
});
|
|
3678
|
+
}
|
|
3679
|
+
});
|
|
3680
|
+
this.renderer = new BaseRenderer({
|
|
3681
|
+
expressionEngine: this.expressionEngine,
|
|
3682
|
+
components,
|
|
3683
|
+
injectHostApi: (element, componentId) => {
|
|
3684
|
+
element.hostApi = this.hostApi;
|
|
3685
|
+
element.componentId = componentId;
|
|
3686
|
+
},
|
|
3687
|
+
debug: this.options.debug,
|
|
3688
|
+
logger: this.logger,
|
|
3689
|
+
onRenderError: (componentId, error) => {
|
|
3690
|
+
this.log("error", `Render error in ${componentId}:`, error);
|
|
3691
|
+
this.emitEvent("component:error", { componentId, error: error.message });
|
|
3692
|
+
return createFallbackElement("error", error.message);
|
|
3693
|
+
}
|
|
3694
|
+
});
|
|
3695
|
+
this.renderer.init();
|
|
3696
|
+
}
|
|
3697
|
+
handleError(error) {
|
|
3698
|
+
var _a;
|
|
3699
|
+
const runtimeError = error instanceof DjvlcRuntimeError ? error : { type: "LOAD_ERROR", message: error.message, timestamp: Date.now() };
|
|
3700
|
+
this.stateManager.setError(runtimeError);
|
|
3701
|
+
(_a = this.telemetryManager) == null ? void 0 : _a.recordError(error);
|
|
3702
|
+
this.emitEvent("page:error", { error: error.message });
|
|
3703
|
+
if (this.options.onError) {
|
|
3704
|
+
this.options.onError(runtimeError);
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
emitEvent(type, data) {
|
|
3708
|
+
var _a;
|
|
3709
|
+
const event = EventBus.createEvent(type, data, (_a = this.telemetryManager) == null ? void 0 : _a.getTraceId());
|
|
3710
|
+
this.eventBus.emit(event);
|
|
3711
|
+
if (this.options.onEvent) {
|
|
3712
|
+
this.options.onEvent(event);
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
createLogger() {
|
|
3716
|
+
const prefix = "[DJVLC]";
|
|
3717
|
+
return {
|
|
3718
|
+
debug: (...args) => {
|
|
3719
|
+
if (this.options.debug) console.debug(prefix, ...args);
|
|
3720
|
+
},
|
|
3721
|
+
info: (...args) => console.info(prefix, ...args),
|
|
3722
|
+
warn: (...args) => console.warn(prefix, ...args),
|
|
3723
|
+
error: (...args) => console.error(prefix, ...args)
|
|
3724
|
+
};
|
|
3725
|
+
}
|
|
3726
|
+
log(level, message, ...args) {
|
|
3727
|
+
this.logger[level](message, ...args);
|
|
3728
|
+
}
|
|
3729
|
+
};
|
|
3730
|
+
const RUNTIME_VERSION = "0.1.0";
|
|
3731
|
+
async function mount(container, options) {
|
|
3732
|
+
var _a, _b;
|
|
3733
|
+
const runtimeOptions = {
|
|
3734
|
+
container,
|
|
3735
|
+
pageUid: options.pageUid,
|
|
3736
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
3737
|
+
cdnBaseUrl: options.cdnBaseUrl,
|
|
3738
|
+
channel: options.channel,
|
|
3739
|
+
userId: options.userId,
|
|
3740
|
+
deviceId: options.deviceId,
|
|
3741
|
+
authToken: options.authToken,
|
|
3742
|
+
previewToken: options.previewToken,
|
|
3743
|
+
debug: options.debug,
|
|
3744
|
+
enableSRI: options.enableSRI,
|
|
3745
|
+
headers: options.headers,
|
|
3746
|
+
onError: options.onError ? (error) => {
|
|
3747
|
+
var _a2;
|
|
3748
|
+
return (_a2 = options.onError) == null ? void 0 : _a2.call(options, new Error(error.message));
|
|
3749
|
+
} : void 0,
|
|
3750
|
+
onMetric: options.onMetric
|
|
3751
|
+
};
|
|
3752
|
+
const runtime = createRuntime(runtimeOptions);
|
|
3753
|
+
try {
|
|
3754
|
+
await runtime.init();
|
|
3755
|
+
await runtime.load();
|
|
3756
|
+
await runtime.render();
|
|
3757
|
+
(_a = options.onLoad) == null ? void 0 : _a.call(options, runtime.getState());
|
|
3758
|
+
} catch (error) {
|
|
3759
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
3760
|
+
(_b = options.onError) == null ? void 0 : _b.call(options, err);
|
|
3761
|
+
throw err;
|
|
3762
|
+
}
|
|
3763
|
+
return {
|
|
3764
|
+
runtime,
|
|
3765
|
+
destroy: () => runtime.destroy()
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3768
|
+
function preconnect(hosts) {
|
|
3769
|
+
hosts.forEach((host) => {
|
|
3770
|
+
const link = document.createElement("link");
|
|
3771
|
+
link.rel = "preconnect";
|
|
3772
|
+
link.href = host.startsWith("http") ? host : `https://${host}`;
|
|
3773
|
+
link.crossOrigin = "anonymous";
|
|
3774
|
+
document.head.appendChild(link);
|
|
3775
|
+
});
|
|
3776
|
+
}
|
|
3777
|
+
function preloadAssets(cdnBaseUrl, version = RUNTIME_VERSION) {
|
|
3778
|
+
const assets = [
|
|
3779
|
+
`${cdnBaseUrl}/runtime/${version}/runtime.esm.js`,
|
|
3780
|
+
`${cdnBaseUrl}/runtime/${version}/runtime.css`
|
|
3781
|
+
];
|
|
3782
|
+
assets.forEach((href) => {
|
|
3783
|
+
const link = document.createElement("link");
|
|
3784
|
+
link.rel = "preload";
|
|
3785
|
+
link.href = href;
|
|
3786
|
+
link.as = href.endsWith(".js") ? "script" : "style";
|
|
3787
|
+
if (href.endsWith(".js")) {
|
|
3788
|
+
link.crossOrigin = "anonymous";
|
|
3789
|
+
}
|
|
3790
|
+
document.head.appendChild(link);
|
|
3791
|
+
});
|
|
3792
|
+
}
|
|
3793
|
+
function getDeviceId() {
|
|
3794
|
+
const key = "djv_device_id";
|
|
3795
|
+
let deviceId = localStorage.getItem(key);
|
|
3796
|
+
if (!deviceId) {
|
|
3797
|
+
deviceId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
3798
|
+
try {
|
|
3799
|
+
localStorage.setItem(key, deviceId);
|
|
3800
|
+
} catch {
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
return deviceId;
|
|
3804
|
+
}
|
|
3805
|
+
function generateTraceId() {
|
|
3806
|
+
const randomHex = (len) => Array.from({ length: len }, () => Math.floor(Math.random() * 16).toString(16)).join("");
|
|
3807
|
+
return randomHex(32);
|
|
3808
|
+
}
|
|
3809
|
+
function generateSpanId() {
|
|
3810
|
+
const randomHex = (len) => Array.from({ length: len }, () => Math.floor(Math.random() * 16).toString(16)).join("");
|
|
3811
|
+
return randomHex(16);
|
|
3812
|
+
}
|
|
3813
|
+
function generateTraceparent() {
|
|
3814
|
+
const traceId = generateTraceId();
|
|
3815
|
+
const spanId = generateSpanId();
|
|
3816
|
+
return `00-${traceId}-${spanId}-01`;
|
|
3817
|
+
}
|
|
3818
|
+
const index = {
|
|
3819
|
+
mount,
|
|
3820
|
+
preconnect,
|
|
3821
|
+
preloadAssets,
|
|
3822
|
+
getDeviceId,
|
|
3823
|
+
generateTraceId,
|
|
3824
|
+
generateSpanId,
|
|
3825
|
+
generateTraceparent,
|
|
3826
|
+
createRuntime,
|
|
3827
|
+
RUNTIME_VERSION
|
|
3828
|
+
};
|
|
3829
|
+
export {
|
|
3830
|
+
RUNTIME_VERSION,
|
|
3831
|
+
createRuntime,
|
|
3832
|
+
index as default,
|
|
3833
|
+
generateSpanId,
|
|
3834
|
+
generateTraceId,
|
|
3835
|
+
generateTraceparent,
|
|
3836
|
+
getDeviceId,
|
|
3837
|
+
mount,
|
|
3838
|
+
preconnect,
|
|
3839
|
+
preloadAssets
|
|
3840
|
+
};
|
|
3841
|
+
//# sourceMappingURL=runtime.esm.js.map
|