@lark-apaas/coding-html-devserver 0.1.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/LICENSE +13 -0
- package/README.md +54 -0
- package/bin/coding-html-devserver.js +2 -0
- package/lib/build.d.ts +23 -0
- package/lib/build.d.ts.map +1 -0
- package/lib/build.js +50 -0
- package/lib/build.js.map +1 -0
- package/lib/cli.d.ts +2 -0
- package/lib/cli.d.ts.map +1 -0
- package/lib/cli.js +46 -0
- package/lib/cli.js.map +1 -0
- package/lib/dev.d.ts +8 -0
- package/lib/dev.d.ts.map +1 -0
- package/lib/dev.js +23 -0
- package/lib/dev.js.map +1 -0
- package/lib/flags.d.ts +10 -0
- package/lib/flags.d.ts.map +1 -0
- package/lib/flags.js +31 -0
- package/lib/flags.js.map +1 -0
- package/lib/html-tags.d.ts +25 -0
- package/lib/html-tags.d.ts.map +1 -0
- package/lib/html-tags.js +122 -0
- package/lib/html-tags.js.map +1 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +37 -0
- package/lib/index.js.map +1 -0
- package/lib/minify.d.ts +11 -0
- package/lib/minify.d.ts.map +1 -0
- package/lib/minify.js +32 -0
- package/lib/minify.js.map +1 -0
- package/lib/plugins/health.d.ts +10 -0
- package/lib/plugins/health.d.ts.map +1 -0
- package/lib/plugins/health.js +38 -0
- package/lib/plugins/health.js.map +1 -0
- package/lib/plugins/inject.d.ts +15 -0
- package/lib/plugins/inject.d.ts.map +1 -0
- package/lib/plugins/inject.js +34 -0
- package/lib/plugins/inject.js.map +1 -0
- package/lib/plugins/vite-client-patch.d.ts +4 -0
- package/lib/plugins/vite-client-patch.d.ts.map +1 -0
- package/lib/plugins/vite-client-patch.js +55 -0
- package/lib/plugins/vite-client-patch.js.map +1 -0
- package/lib/plugins/ws-watchdog.d.ts +25 -0
- package/lib/plugins/ws-watchdog.d.ts.map +1 -0
- package/lib/plugins/ws-watchdog.js +103 -0
- package/lib/plugins/ws-watchdog.js.map +1 -0
- package/lib/transform/index.d.ts +38 -0
- package/lib/transform/index.d.ts.map +1 -0
- package/lib/transform/index.js +45 -0
- package/lib/transform/index.js.map +1 -0
- package/lib/transform/og-meta.d.ts +18 -0
- package/lib/transform/og-meta.d.ts.map +1 -0
- package/lib/transform/og-meta.js +54 -0
- package/lib/transform/og-meta.js.map +1 -0
- package/lib/transform/preview-bridge.d.ts +11 -0
- package/lib/transform/preview-bridge.d.ts.map +1 -0
- package/lib/transform/preview-bridge.js +16 -0
- package/lib/transform/preview-bridge.js.map +1 -0
- package/lib/transform/slardar.d.ts +17 -0
- package/lib/transform/slardar.d.ts.map +1 -0
- package/lib/transform/slardar.js +116 -0
- package/lib/transform/slardar.js.map +1 -0
- package/lib/transform/view-context.d.ts +27 -0
- package/lib/transform/view-context.d.ts.map +1 -0
- package/lib/transform/view-context.js +120 -0
- package/lib/transform/view-context.js.map +1 -0
- package/lib/vite-config.d.ts +24 -0
- package/lib/vite-config.d.ts.map +1 -0
- package/lib/vite-config.js +74 -0
- package/lib/vite-config.js.map +1 -0
- package/package.json +46 -0
- package/src/runtime/ws-watchdog-client.mjs +148 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.slardarTags = slardarTags;
|
|
4
|
+
/**
|
|
5
|
+
* HTML 解析时同步执行的内联脚本(早于任何外部资源加载):建 KSlardarWeb 命令队列
|
|
6
|
+
* stub + 立即注册 capture 阶段的资源/运行时错误监听,SDK 就绪前入 buffer,onload 后 flush。
|
|
7
|
+
*/
|
|
8
|
+
function buildEarlyResourceErrorScript(globalName) {
|
|
9
|
+
return `(function(g){
|
|
10
|
+
if(!window[g]){var q=[];window[g]=function(){q.push([].slice.call(arguments));};window[g].q=q;}
|
|
11
|
+
window.__slardarErrBuf=window.__slardarErrBuf||[];
|
|
12
|
+
var ref=document.referrer||'';
|
|
13
|
+
function _cap(err){
|
|
14
|
+
if(window[g]&&!window[g].q){
|
|
15
|
+
window[g]('captureException',err instanceof Error?err:new Error(String(err)),{source:'runtime-error',referrer:ref});
|
|
16
|
+
}else{
|
|
17
|
+
(window.__slardarErrBuf=window.__slardarErrBuf||[]).push(err);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
window.addEventListener('error',function(e){
|
|
21
|
+
if(e.error==null&&e.target instanceof Element){
|
|
22
|
+
var t=e.target,url=t.src||t.href||'';
|
|
23
|
+
if(url)window[g]('captureException',new Error('Resource load failed: '+url),{team:'miaoda-sdk',source:'resource-error',resourceType:t.tagName.toLowerCase(),resourceUrl:url,referrer:ref});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if(e.error!=null){
|
|
27
|
+
var err=e.error;
|
|
28
|
+
if(e.filename&&typeof err.stack==='string'&&err.stack.indexOf(e.filename)<0){
|
|
29
|
+
try{err.stack+='\\n at '+e.filename+':'+(e.lineno||0)+':'+(e.colno||0);}catch(ex){}
|
|
30
|
+
}
|
|
31
|
+
_cap(err);
|
|
32
|
+
}else{
|
|
33
|
+
var msg=e.message||'Script error';
|
|
34
|
+
var err=new Error(msg);
|
|
35
|
+
if(e.filename)err.stack=msg+'\\n at '+e.filename+':'+(e.lineno||0)+':'+(e.colno||0);
|
|
36
|
+
_cap(err);
|
|
37
|
+
}
|
|
38
|
+
},true);
|
|
39
|
+
window.addEventListener('unhandledrejection',function(e){
|
|
40
|
+
if(e.reason!=null)_cap(e.reason instanceof Error?e.reason:new Error(String(e.reason)));
|
|
41
|
+
});
|
|
42
|
+
})('${globalName}');`;
|
|
43
|
+
}
|
|
44
|
+
function buildSlardarScript(bid, globalName) {
|
|
45
|
+
return `
|
|
46
|
+
const slardarScript = document.createElement('script');
|
|
47
|
+
slardarScript.src = 'https://lf3-short.ibytedapm.com/slardar/fe/sdk-web/browser.cn.js?bid=${bid}&globalName=${globalName}';
|
|
48
|
+
slardarScript.crossOrigin = 'anonymous';
|
|
49
|
+
slardarScript.onload = function() {
|
|
50
|
+
if (window.${globalName}) {
|
|
51
|
+
window.${globalName}('context.merge', {
|
|
52
|
+
tenantId: window.tenantId ?? '',
|
|
53
|
+
appId: window.appId ?? '',
|
|
54
|
+
});
|
|
55
|
+
window.${globalName}('init', {
|
|
56
|
+
bid: '${bid}',
|
|
57
|
+
env: window.ENVIRONMENT || 'online',
|
|
58
|
+
userId: window.userId ?? '',
|
|
59
|
+
});
|
|
60
|
+
window.${globalName}('start');
|
|
61
|
+
// Flush errors captured before the SDK finished loading
|
|
62
|
+
var buf = window.__slardarErrBuf || [];
|
|
63
|
+
window.__slardarErrBuf = [];
|
|
64
|
+
buf.forEach(function(err) {
|
|
65
|
+
window.${globalName}('captureException', err instanceof Error ? err : new Error(String(err)), { source: 'pre-sdk-error', referrer: document.referrer || '' });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
slardarScript.onerror = function() {
|
|
70
|
+
console.warn('Failed to load Slardar script');
|
|
71
|
+
};
|
|
72
|
+
document.head.appendChild(slardarScript);
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
function buildTeaScript() {
|
|
76
|
+
return `
|
|
77
|
+
(function (win, export_obj) {
|
|
78
|
+
win['LogAnalyticsObject'] = export_obj;
|
|
79
|
+
if (!win[export_obj]) {
|
|
80
|
+
function _collect() { _collect.q.push(arguments); }
|
|
81
|
+
_collect.q = _collect.q || [];
|
|
82
|
+
win[export_obj] = _collect;
|
|
83
|
+
}
|
|
84
|
+
win[export_obj].l = +new Date();
|
|
85
|
+
})(window, 'collectEvent');
|
|
86
|
+
|
|
87
|
+
const teaScript = document.createElement('script');
|
|
88
|
+
teaScript.src = 'https://lf3-cdn-tos.bytescm.com/obj/static/log-sdk/collect/5.1/collect.js';
|
|
89
|
+
teaScript.crossOrigin = 'anonymous';
|
|
90
|
+
teaScript.async = true;
|
|
91
|
+
teaScript.onerror = function() {
|
|
92
|
+
console.warn('Failed to load Tea script');
|
|
93
|
+
};
|
|
94
|
+
document.head.appendChild(teaScript);
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
/** Slardar + Performance + Tea 监控埋点的 head-prepend script 清单。 */
|
|
98
|
+
function slardarTags(options = {}) {
|
|
99
|
+
const { bid = 'apaas_miaoda', globalName = 'KSlardarWeb' } = options;
|
|
100
|
+
return [
|
|
101
|
+
// 必须最先:内联资源错误捕获 + KSlardarWeb 命令队列 stub
|
|
102
|
+
{ tag: 'script', children: buildEarlyResourceErrorScript(globalName), injectTo: 'head-prepend' },
|
|
103
|
+
{ tag: 'script', children: buildSlardarScript(bid, globalName), injectTo: 'head-prepend' },
|
|
104
|
+
{
|
|
105
|
+
tag: 'script',
|
|
106
|
+
attrs: {
|
|
107
|
+
src: 'https://sf3-scmcdn-cn.feishucdn.com/obj/unpkg/byted/performance/0.1.2/dist/performance.iife.js',
|
|
108
|
+
crossorigin: 'anonymous',
|
|
109
|
+
defer: true,
|
|
110
|
+
},
|
|
111
|
+
injectTo: 'head-prepend',
|
|
112
|
+
},
|
|
113
|
+
{ tag: 'script', children: buildTeaScript(), injectTo: 'head-prepend' },
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=slardar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slardar.js","sourceRoot":"","sources":["../../src/transform/slardar.ts"],"names":[],"mappings":";;AAiHA,kCAiBC;AAlHD;;;GAGG;AACH,SAAS,6BAA6B,CAAC,UAAkB;IACvD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAiCH,UAAU,KAAK,CAAC;AACtB,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAW,EAAE,UAAkB;IACzD,OAAO;;gGAEuF,GAAG,eAAe,UAAU;;;mBAGzG,UAAU;iBACZ,UAAU;;;;iBAIV,UAAU;kBACT,GAAG;;;;iBAIJ,UAAU;;;;;mBAKR,UAAU;;;;;;;;GAQ1B,CAAC;AACJ,CAAC;AAED,SAAS,cAAc;IACrB,OAAO;;;;;;;;;;;;;;;;;;;GAmBN,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,SAAgB,WAAW,CAAC,UAA0B,EAAE;IACtD,MAAM,EAAE,GAAG,GAAG,cAAc,EAAE,UAAU,GAAG,aAAa,EAAE,GAAG,OAAO,CAAC;IACrE,OAAO;QACL,wCAAwC;QACxC,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,6BAA6B,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE;QAChG,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,kBAAkB,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE;QAC1F;YACE,GAAG,EAAE,QAAQ;YACb,KAAK,EAAE;gBACL,GAAG,EAAE,gGAAgG;gBACrG,WAAW,EAAE,WAAW;gBACxB,KAAK,EAAE,IAAI;aACZ;YACD,QAAQ,EAAE,cAAc;SACzB;QACD,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE;KACxE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import type { IncomingMessage } from 'node:http';
|
|
3
|
+
import type { HtmlTagDescriptor } from '../html-tags';
|
|
4
|
+
export interface RequestRenderContext {
|
|
5
|
+
userId: string;
|
|
6
|
+
tenantId: number | string;
|
|
7
|
+
userName: string;
|
|
8
|
+
csrfToken: string;
|
|
9
|
+
environment: string;
|
|
10
|
+
}
|
|
11
|
+
/** dev 期把每个请求的 render context 绑到这条异步链,transformIndexHtml 读出。 */
|
|
12
|
+
export declare const requestContext: AsyncLocalStorage<RequestRenderContext>;
|
|
13
|
+
/** 从 incoming request 抽出 server 端能拿到的 view-context 字段。 */
|
|
14
|
+
export declare function buildRenderContext(req: IncomingMessage): RequestRenderContext;
|
|
15
|
+
export interface ViewContextOptions {
|
|
16
|
+
/** appId override;未设则 dev 走 IIFE 从 URL 解析(prod/无 base path 时用)。 */
|
|
17
|
+
appId?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* view-context inline 脚本 tag。
|
|
21
|
+
* - build:HBS 占位符(vefaas 渲染替真值)
|
|
22
|
+
* - dev + ctx:请求上下文真值字面量
|
|
23
|
+
* - dev 无 ctx:IIFE fallback
|
|
24
|
+
* head-prepend 且排在 slardar 之前 —— 保证 slardar onload 时 window.tenantId 已就绪。
|
|
25
|
+
*/
|
|
26
|
+
export declare function viewContextTag(mode: 'build' | 'dev', opts?: ViewContextOptions, ctx?: RequestRenderContext): HtmlTagDescriptor;
|
|
27
|
+
//# sourceMappingURL=view-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-context.d.ts","sourceRoot":"","sources":["../../src/transform/view-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAqBtD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAElB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,gEAAgE;AAChE,eAAO,MAAM,cAAc,yCAAgD,CAAC;AA0B5E,0DAA0D;AAC1D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,GAAG,oBAAoB,CAS7E;AAED,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwDD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,OAAO,GAAG,KAAK,EACrB,IAAI,GAAE,kBAAuB,EAC7B,GAAG,CAAC,EAAE,oBAAoB,GACzB,iBAAiB,CAQnB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestContext = void 0;
|
|
4
|
+
exports.buildRenderContext = buildRenderContext;
|
|
5
|
+
exports.viewContextTag = viewContextTag;
|
|
6
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
7
|
+
/**
|
|
8
|
+
* 启动期上下文注入:把 appId/userId/tenantId 等挂到 `window`,供业务代码与 slardar 读。
|
|
9
|
+
* 能力对齐 `@lark-apaas/coding-preset-vite-react` 的 view-context 插件。
|
|
10
|
+
*
|
|
11
|
+
* 三条路径:
|
|
12
|
+
* - build:emit `{{tenantId}}` 等 HBS 占位符,部署运行时 (vefaas) 替真值
|
|
13
|
+
* - dev + request ctx:解析 suda-webuser header / cookie 拼真值字面量
|
|
14
|
+
* - dev 无 ctx:IIFE fallback(csrfToken 从 cookie 读、id 类全空)
|
|
15
|
+
*/
|
|
16
|
+
const SUDA_HEADER = 'x-larkgw-suda-webuser';
|
|
17
|
+
/** dev 期把每个请求的 render context 绑到这条异步链,transformIndexHtml 读出。 */
|
|
18
|
+
exports.requestContext = new node_async_hooks_1.AsyncLocalStorage();
|
|
19
|
+
function parseSudaWebUser(header) {
|
|
20
|
+
if (typeof header !== 'string' || !header)
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(decodeURIComponent(header));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function parseCsrfToken(cookieHeader) {
|
|
30
|
+
if (!cookieHeader)
|
|
31
|
+
return '';
|
|
32
|
+
const m = cookieHeader.match(/(?:^|;\s*)suda-csrf-token=([^;]+)/);
|
|
33
|
+
return m ? decodeURIComponent(m[1]) : '';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* dev 期 environment 来源:`process.env.FORCE_FRAMEWORK_ENVIRONMENT`,未设兜底 'online'。
|
|
37
|
+
* 不感知域名——技术栈不应依赖 host 推断部署环境。
|
|
38
|
+
*/
|
|
39
|
+
function readDevEnvironment() {
|
|
40
|
+
const v = process.env.FORCE_FRAMEWORK_ENVIRONMENT;
|
|
41
|
+
return v && v.trim() ? v.trim() : 'online';
|
|
42
|
+
}
|
|
43
|
+
/** 从 incoming request 抽出 server 端能拿到的 view-context 字段。 */
|
|
44
|
+
function buildRenderContext(req) {
|
|
45
|
+
const user = parseSudaWebUser(req.headers[SUDA_HEADER]);
|
|
46
|
+
return {
|
|
47
|
+
userId: user?.user_id ?? '',
|
|
48
|
+
tenantId: user?.tenant_id ?? '',
|
|
49
|
+
userName: user?.user_name?.zh_cn ?? '',
|
|
50
|
+
csrfToken: parseCsrfToken(req.headers.cookie),
|
|
51
|
+
environment: readDevEnvironment(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function appIdExpr(appId) {
|
|
55
|
+
if (appId)
|
|
56
|
+
return JSON.stringify(appId);
|
|
57
|
+
return `(function () {
|
|
58
|
+
var p = location.pathname.match(/\\/app\\/(app_[a-z0-9]+)/i);
|
|
59
|
+
if (p) return p[1];
|
|
60
|
+
var h = location.hostname.match(/app_[a-z0-9]+/i);
|
|
61
|
+
return h ? h[0] : '';
|
|
62
|
+
})()`;
|
|
63
|
+
}
|
|
64
|
+
/** dev 有 request context:全字面量真值。 */
|
|
65
|
+
function viewContextFromRequest(opts, ctx) {
|
|
66
|
+
return `
|
|
67
|
+
window.appId = ${appIdExpr(opts.appId)};
|
|
68
|
+
window.userId = ${JSON.stringify(ctx.userId)};
|
|
69
|
+
window.tenantId = ${JSON.stringify(ctx.tenantId)};
|
|
70
|
+
window.userName = ${JSON.stringify(ctx.userName)};
|
|
71
|
+
window.csrfToken = ${JSON.stringify(ctx.csrfToken)};
|
|
72
|
+
window.ENVIRONMENT = ${JSON.stringify(ctx.environment)};
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* build:emit HBS 占位符,由部署运行时 (vefaas) 替换为真值。
|
|
77
|
+
*
|
|
78
|
+
* 与 preset 的差异:html stack **不** emit `window._appInfo`(preset 给 React 业务代码用,
|
|
79
|
+
* html stack 不消费),appName/appDescription/appAvatar 由 og-meta 注入到 meta 标签即可。
|
|
80
|
+
*/
|
|
81
|
+
function viewContextPlaceholders() {
|
|
82
|
+
return `
|
|
83
|
+
window.appId = "{{appId}}";
|
|
84
|
+
window.userId = "{{userId}}";
|
|
85
|
+
window.tenantId = "{{tenantId}}";
|
|
86
|
+
window.userName = "{{userName}}";
|
|
87
|
+
window.csrfToken = "{{csrfToken}}";
|
|
88
|
+
window.ENVIRONMENT = "{{environment}}";
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
/** dev 无 request context(极少触发):IIFE fallback,id 类全空。 */
|
|
92
|
+
function viewContextDevFallback(opts) {
|
|
93
|
+
return `
|
|
94
|
+
window.appId = ${appIdExpr(opts.appId)};
|
|
95
|
+
window.userId = '';
|
|
96
|
+
window.tenantId = '';
|
|
97
|
+
window.userName = '';
|
|
98
|
+
window.csrfToken = (function () {
|
|
99
|
+
var m = document.cookie.match(/(?:^|;\\s*)suda-csrf-token=([^;]+)/);
|
|
100
|
+
return m ? decodeURIComponent(m[1]) : '';
|
|
101
|
+
})();
|
|
102
|
+
window.ENVIRONMENT = ${JSON.stringify(readDevEnvironment())};
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* view-context inline 脚本 tag。
|
|
107
|
+
* - build:HBS 占位符(vefaas 渲染替真值)
|
|
108
|
+
* - dev + ctx:请求上下文真值字面量
|
|
109
|
+
* - dev 无 ctx:IIFE fallback
|
|
110
|
+
* head-prepend 且排在 slardar 之前 —— 保证 slardar onload 时 window.tenantId 已就绪。
|
|
111
|
+
*/
|
|
112
|
+
function viewContextTag(mode, opts = {}, ctx) {
|
|
113
|
+
const children = mode === 'build'
|
|
114
|
+
? viewContextPlaceholders()
|
|
115
|
+
: ctx
|
|
116
|
+
? viewContextFromRequest(opts, ctx)
|
|
117
|
+
: viewContextDevFallback(opts);
|
|
118
|
+
return { tag: 'script', children, injectTo: 'head-prepend' };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=view-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-context.js","sourceRoot":"","sources":["../../src/transform/view-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { InlineConfig } from 'vite';
|
|
2
|
+
export interface HtmlViteOptions {
|
|
3
|
+
/** 静态根目录(相对 cwd),默认 'src'。 */
|
|
4
|
+
root?: string;
|
|
5
|
+
/** dev server 端口,默认 `CLIENT_DEV_PORT` env 或 8001。 */
|
|
6
|
+
port?: number;
|
|
7
|
+
/** 绑定 host,默认 '0.0.0.0'。 */
|
|
8
|
+
host?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* html stack 的 vite dev 配置(仅 dev 用,build 不走 vite)。
|
|
12
|
+
*
|
|
13
|
+
* - 绑 0.0.0.0、`allowedHosts: true`(= 网关任意 Host)、不设 `base`(prefix 由网关剥)。
|
|
14
|
+
* - HMR 走 vite 的 WS:`MIAODA_WS_HOST` 存在时切独立 WS 域名(对齐 vite-react 网关方案)。
|
|
15
|
+
* - `appType: 'mpa'`:纯静态多页,不做 SPA history fallback。
|
|
16
|
+
* - 内置 health + 平台注入 + WS 韧性插件(vite-client-patch / ws-watchdog)。
|
|
17
|
+
*
|
|
18
|
+
* 沙箱 WS 韧性(dev only,对齐 coding-preset-vite-react):
|
|
19
|
+
* - `viteClientPatchPlugin`:删 WS 重连后的 `location.reload()`(休眠唤醒白屏),
|
|
20
|
+
* 并在 `MIAODA_HMR_WS_TOKEN` 存在时给 HMR 长链 URL 追加 `sandbox_token`(穿网关鉴权)。
|
|
21
|
+
* - `wsWatchdogPlugin`:检测僵尸 WS,判死后 postMessage 通知沙箱外壳。
|
|
22
|
+
*/
|
|
23
|
+
export declare function createHtmlViteConfig(opts?: HtmlViteOptions): InlineConfig;
|
|
24
|
+
//# sourceMappingURL=vite-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-config.d.ts","sourceRoot":"","sources":["../src/vite-config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAkBzC,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,eAAoB,GAAG,YAAY,CAoC7E"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createHtmlViteConfig = createHtmlViteConfig;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const health_1 = require("./plugins/health");
|
|
9
|
+
const inject_1 = require("./plugins/inject");
|
|
10
|
+
const vite_client_patch_1 = require("./plugins/vite-client-patch");
|
|
11
|
+
const ws_watchdog_1 = require("./plugins/ws-watchdog");
|
|
12
|
+
/**
|
|
13
|
+
* 标准化 base path(路径语义,强制 `/` 前缀、去尾斜杠):
|
|
14
|
+
* '' / undefined / '/' → '';'foo' → '/foo';'/app/x/' → '/app/x'。
|
|
15
|
+
* 语义对齐 `@lark-apaas/coding-preset-vite-react` 的 normalizeBasePath。
|
|
16
|
+
*/
|
|
17
|
+
function normalizeBasePath(input) {
|
|
18
|
+
if (!input)
|
|
19
|
+
return '';
|
|
20
|
+
const p = input.trim();
|
|
21
|
+
if (!p || p === '/')
|
|
22
|
+
return '';
|
|
23
|
+
return (p.startsWith('/') ? p : '/' + p).replace(/\/+$/, '');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* html stack 的 vite dev 配置(仅 dev 用,build 不走 vite)。
|
|
27
|
+
*
|
|
28
|
+
* - 绑 0.0.0.0、`allowedHosts: true`(= 网关任意 Host)、不设 `base`(prefix 由网关剥)。
|
|
29
|
+
* - HMR 走 vite 的 WS:`MIAODA_WS_HOST` 存在时切独立 WS 域名(对齐 vite-react 网关方案)。
|
|
30
|
+
* - `appType: 'mpa'`:纯静态多页,不做 SPA history fallback。
|
|
31
|
+
* - 内置 health + 平台注入 + WS 韧性插件(vite-client-patch / ws-watchdog)。
|
|
32
|
+
*
|
|
33
|
+
* 沙箱 WS 韧性(dev only,对齐 coding-preset-vite-react):
|
|
34
|
+
* - `viteClientPatchPlugin`:删 WS 重连后的 `location.reload()`(休眠唤醒白屏),
|
|
35
|
+
* 并在 `MIAODA_HMR_WS_TOKEN` 存在时给 HMR 长链 URL 追加 `sandbox_token`(穿网关鉴权)。
|
|
36
|
+
* - `wsWatchdogPlugin`:检测僵尸 WS,判死后 postMessage 通知沙箱外壳。
|
|
37
|
+
*/
|
|
38
|
+
function createHtmlViteConfig(opts = {}) {
|
|
39
|
+
const root = node_path_1.default.resolve(opts.root ?? 'src');
|
|
40
|
+
// Number.isFinite 兜底:?? 只挡 null/undefined,挡不住 NaN(防 opts.port 传入脏值)
|
|
41
|
+
const port = Number.isFinite(opts.port) ? opts.port : Number(process.env.CLIENT_DEV_PORT) || 8001;
|
|
42
|
+
const host = opts.host ?? '0.0.0.0';
|
|
43
|
+
// 沙箱网关把 `/app/<id>` 前缀透传给 vite 时,watchdog 的 <script src>、middleware
|
|
44
|
+
// 匹配路径、以及客户端 import 的 `/@vite/client` 都必须带同一前缀;空串退化到裸路径。
|
|
45
|
+
const clientBasePath = normalizeBasePath(process.env.CLIENT_BASE_PATH);
|
|
46
|
+
// 从 `/app/<appId>` 形态的 base path 提取 appId 给 view-context 拼真值;非该形态则
|
|
47
|
+
// 留空,view-context dev 退化为从 URL 解析。
|
|
48
|
+
const appId = /^\/app\/[^/]/.test(clientBasePath)
|
|
49
|
+
? clientBasePath.split('/').pop()
|
|
50
|
+
: undefined;
|
|
51
|
+
return {
|
|
52
|
+
root,
|
|
53
|
+
appType: 'mpa',
|
|
54
|
+
clearScreen: false,
|
|
55
|
+
server: {
|
|
56
|
+
host,
|
|
57
|
+
port,
|
|
58
|
+
strictPort: true,
|
|
59
|
+
cors: true,
|
|
60
|
+
allowedHosts: true,
|
|
61
|
+
hmr: process.env.MIAODA_WS_HOST
|
|
62
|
+
? { protocol: 'wss', host: process.env.MIAODA_WS_HOST, clientPort: 443, path: '/ws' }
|
|
63
|
+
: { path: '/ws' },
|
|
64
|
+
},
|
|
65
|
+
plugins: [
|
|
66
|
+
(0, health_1.healthPlugin)(),
|
|
67
|
+
(0, inject_1.platformInjectPlugin)({ appId }),
|
|
68
|
+
// dev only:apply:'serve' 自带,build 不挂
|
|
69
|
+
(0, vite_client_patch_1.viteClientPatchPlugin)(),
|
|
70
|
+
(0, ws_watchdog_1.wsWatchdogPlugin)({ clientBasePath }),
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=vite-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-config.js","sourceRoot":"","sources":["../src/vite-config.ts"],"names":[],"mappings":";;;;;AAyCA,oDAoCC;AA7ED,0DAA6B;AAE7B,6CAAgD;AAChD,6CAAwD;AACxD,mEAAoE;AACpE,uDAAyD;AAEzD;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,KAAyB;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC;IAC/B,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC/D,CAAC;AAWD;;;;;;;;;;;;GAYG;AACH,SAAgB,oBAAoB,CAAC,OAAwB,EAAE;IAC7D,MAAM,IAAI,GAAG,mBAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC;IAC9C,oEAAoE;IACpE,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,IAAe,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC;IAC9G,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC;IACpC,oEAAoE;IACpE,yDAAyD;IACzD,MAAM,cAAc,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACvE,mEAAmE;IACnE,mCAAmC;IACnC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC;QAC/C,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE;QACjC,CAAC,CAAC,SAAS,CAAC;IAEd,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,KAAK;QAClB,MAAM,EAAE;YACN,IAAI;YACJ,IAAI;YACJ,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,IAAI;YACV,YAAY,EAAE,IAAI;YAClB,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;gBAC7B,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE;gBACrF,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE;SACpB;QACD,OAAO,EAAE;YACP,IAAA,qBAAY,GAAE;YACd,IAAA,6BAAoB,EAAC,EAAE,KAAK,EAAE,CAAC;YAC/B,qCAAqC;YACrC,IAAA,yCAAqB,GAAE;YACvB,IAAA,8BAAgB,EAAC,EAAE,cAAc,EAAE,CAAC;SACrC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lark-apaas/coding-html-devserver",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Dev/build toolchain for the miaoda-coding html (pure HTML) stack. Wraps vite (dev + HMR) behind a bin so the template never sees vite; build is pure-copy + platform-script injection + optional minify.",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"types": "./lib/index.d.ts",
|
|
7
|
+
"bin": "./bin/coding-html-devserver.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"lib",
|
|
10
|
+
"bin",
|
|
11
|
+
"src/runtime"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"watch": "tsc --watch",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"html-minifier-terser": "^7.2.0",
|
|
23
|
+
"vite": "^8.0.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/html-minifier-terser": "^7.0.2",
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"typescript": "^5.6.0",
|
|
29
|
+
"vitest": "^3.0.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"registry": "https://registry.npmjs.org/"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"miaoda",
|
|
37
|
+
"coding",
|
|
38
|
+
"devserver",
|
|
39
|
+
"html",
|
|
40
|
+
"vite"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* eslint-env browser */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WebSocket Watchdog Client。在 Vite 沙箱 iframe 内自启动;
|
|
5
|
+
* 通过 createHotContext 走 HMR 通道发 ping/收 pong;判死后 postMessage 给 parent。
|
|
6
|
+
*
|
|
7
|
+
* 设计 spec: docs/superpowers/specs/2026-04-27-sandbox-ws-watchdog-design.md
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} WatchdogDeps
|
|
12
|
+
* @property {(event: string, data: unknown) => void} sendCustom
|
|
13
|
+
* @property {(event: string, cb: (data: unknown) => void) => void} onCustom
|
|
14
|
+
* @property {(status: 'connected' | 'disconnected') => void} postStatus
|
|
15
|
+
* @property {(handler: () => void, ms: number) => number} setInterval
|
|
16
|
+
* @property {(handle: number) => void} clearInterval
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} WatchdogOptions
|
|
21
|
+
* @property {number} [intervalMs] 默认 5000
|
|
22
|
+
* @property {number} [missThreshold] 默认 2
|
|
23
|
+
* @property {number} [echoFreshnessMs] 默认 15000
|
|
24
|
+
* @property {string} [pingEvent] 默认 'ws-watchdog:ping'
|
|
25
|
+
* @property {string} [pongEvent] 默认 'ws-watchdog:pong'
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 不返回 dispose——watchdog 跟 iframe 同生命周期,无独立销毁路径。
|
|
30
|
+
* @param {WatchdogDeps} deps
|
|
31
|
+
* @param {WatchdogOptions} [options]
|
|
32
|
+
*/
|
|
33
|
+
export function createWatchdog(deps, options = {}) {
|
|
34
|
+
const intervalMs = options.intervalMs ?? 5000;
|
|
35
|
+
const missThreshold = options.missThreshold ?? 2;
|
|
36
|
+
const echoFreshnessMs = options.echoFreshnessMs ?? 15000;
|
|
37
|
+
const pingEvent = options.pingEvent ?? 'ws-watchdog:ping';
|
|
38
|
+
const pongEvent = options.pongEvent ?? 'ws-watchdog:pong';
|
|
39
|
+
|
|
40
|
+
let missed = 0;
|
|
41
|
+
/** @type {'connected' | 'disconnected'} */
|
|
42
|
+
let reportedStatus = 'connected';
|
|
43
|
+
let pingSeq = 0;
|
|
44
|
+
/** @type {Map<number, number>} seq → wall-clock 时间戳 */
|
|
45
|
+
const recentPings = new Map();
|
|
46
|
+
|
|
47
|
+
function onTick() {
|
|
48
|
+
// missed++ 必须先于 sendCustom:发送侧抛错时 missed 已累加,下一 tick 仍能判死。
|
|
49
|
+
missed += 1;
|
|
50
|
+
const seq = ++pingSeq;
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
recentPings.set(seq, now);
|
|
53
|
+
for (const [s, ts] of recentPings) {
|
|
54
|
+
if (now - ts > echoFreshnessMs) recentPings.delete(s);
|
|
55
|
+
}
|
|
56
|
+
deps.sendCustom(pingEvent, { t: seq });
|
|
57
|
+
if (missed >= missThreshold && reportedStatus !== 'disconnected') {
|
|
58
|
+
deps.postStatus('disconnected');
|
|
59
|
+
reportedStatus = 'disconnected';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
deps.onCustom(pongEvent, (raw) => {
|
|
64
|
+
const data = /** @type {{ echo?: number } | undefined} */ (raw);
|
|
65
|
+
if (typeof data?.echo !== 'number' || !recentPings.has(data.echo)) return;
|
|
66
|
+
recentPings.delete(data.echo);
|
|
67
|
+
missed = 0;
|
|
68
|
+
if (reportedStatus !== 'connected') {
|
|
69
|
+
deps.postStatus('connected');
|
|
70
|
+
reportedStatus = 'connected';
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
deps.setInterval(onTick, intervalMs);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// postMessage 的 targetOrigin 必须显式指定,禁止用 '*'。
|
|
78
|
+
|
|
79
|
+
const PARENT_ORIGIN_KEY = '__parentOrigin';
|
|
80
|
+
|
|
81
|
+
function getParentOriginFromParams() {
|
|
82
|
+
try {
|
|
83
|
+
const origin = new URLSearchParams(window.location.search).get(PARENT_ORIGIN_KEY);
|
|
84
|
+
if (origin) {
|
|
85
|
+
try { sessionStorage.setItem(PARENT_ORIGIN_KEY, origin); } catch { /* 忽略 */ }
|
|
86
|
+
return origin;
|
|
87
|
+
}
|
|
88
|
+
} catch { /* 忽略 URL 解析失败 */ }
|
|
89
|
+
try { return sessionStorage.getItem(PARENT_ORIGIN_KEY) || undefined; }
|
|
90
|
+
catch { return undefined; }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getLegacyParentOrigin() {
|
|
94
|
+
const { origin } = window.location;
|
|
95
|
+
if (origin.includes('force.feishuapp.net')) return 'https://force.feishu.cn';
|
|
96
|
+
if (origin.includes('force-pre.feishuapp.net')) return 'https://force.feishu-pre.cn';
|
|
97
|
+
if (origin.includes('force.byted.org')) return 'https://force.feishu-boe.cn';
|
|
98
|
+
if (origin.includes('feishuapp.cn') || origin.includes('miaoda.feishuapp.net')) return 'https://miaoda.feishu.cn';
|
|
99
|
+
if (origin.includes('fsapp.kundou.cn') || origin.includes('miaoda-pre.feishuapp.net')) return 'https://miaoda.feishu-pre.cn';
|
|
100
|
+
return 'https://miaoda.feishu-boe.cn';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resolveParentOrigin() {
|
|
104
|
+
const paramOrigin = getParentOriginFromParams();
|
|
105
|
+
if (paramOrigin) return paramOrigin;
|
|
106
|
+
// plugin middleware 在送出脚本时把这个 token 编译期替换成字符串字面量。
|
|
107
|
+
return process.env.FORCE_FRAMEWORK_DOMAIN_MAIN || getLegacyParentOrigin();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function postStatus(status) {
|
|
111
|
+
try {
|
|
112
|
+
const origin = resolveParentOrigin();
|
|
113
|
+
if (!origin) return;
|
|
114
|
+
window.parent.postMessage(
|
|
115
|
+
{ type: 'DevServerMessage', data: { type: 'devServer-status', status } },
|
|
116
|
+
origin,
|
|
117
|
+
);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.error('[fullstack-ws-watchdog] postMessage error:', e);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 仅在 iframe 内自启动;jsdom(vitest)下 window.parent === window 自然跳过。
|
|
124
|
+
// 直出的 raw module 拿不到 import.meta.hot,所以借 createHotContext 走 HMR 通道。
|
|
125
|
+
// CLIENT_BASE_PATH 由 plugin 编译期替换;缺前缀会被网关 404。
|
|
126
|
+
const CLIENT_BASE_PATH = process.env.CLIENT_BASE_PATH || '';
|
|
127
|
+
|
|
128
|
+
if (typeof window !== 'undefined' && window.parent !== window) {
|
|
129
|
+
(async () => {
|
|
130
|
+
try {
|
|
131
|
+
const { createHotContext } = await import(CLIENT_BASE_PATH + '/@vite/client');
|
|
132
|
+
const hot = createHotContext(CLIENT_BASE_PATH + '/@ws-watchdog.js');
|
|
133
|
+
createWatchdog({
|
|
134
|
+
sendCustom: (event, data) => {
|
|
135
|
+
// 断连时 hot.send 可能抛;吞掉即可——missed 已在 onTick 顶部累加。
|
|
136
|
+
try { hot.send(event, data); }
|
|
137
|
+
catch (e) { console.warn('[fullstack-ws-watchdog] send failed:', e); }
|
|
138
|
+
},
|
|
139
|
+
onCustom: (event, cb) => hot.on(event, cb),
|
|
140
|
+
postStatus,
|
|
141
|
+
setInterval: (handler, ms) => window.setInterval(handler, ms),
|
|
142
|
+
clearInterval: (handle) => window.clearInterval(handle),
|
|
143
|
+
});
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.warn('[fullstack-ws-watchdog] bootstrap failed:', e);
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
}
|