@flaxia/node 0.3.4 → 0.3.6
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/README.md +51 -5
- package/dist/client/SignalingClient.d.ts +2 -2
- package/dist/client/SignalingClient.d.ts.map +1 -1
- package/dist/consent/ConsentUI.d.ts +1 -1
- package/dist/consent/ConsentUI.d.ts.map +1 -1
- package/dist/consent/storage.d.ts +12 -0
- package/dist/consent/storage.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +154 -72
- package/dist/worker.js +25 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npm install @flaxia/node
|
|
|
14
14
|
```typescript
|
|
15
15
|
import { initFlaxiaNode } from '@flaxia/node'
|
|
16
16
|
|
|
17
|
-
initFlaxiaNode({
|
|
17
|
+
const controller = initFlaxiaNode({
|
|
18
18
|
orchestratorUrl: 'https://crowd.flaxia.app',
|
|
19
19
|
siteId: 'your-site-id',
|
|
20
20
|
consent: {
|
|
@@ -34,11 +34,56 @@ initFlaxiaNode({
|
|
|
34
34
|
| `orchestratorUrl` | `string` | yes | オーケストレーターのURL |
|
|
35
35
|
| `siteId` | `string` | yes | サイト固有の識別子 |
|
|
36
36
|
| `consent` | `ConsentConfig` | yes | 同意UIの設定 |
|
|
37
|
-
| `consent.brandName` | `string` | yes |
|
|
38
|
-
| `consent.position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | yes |
|
|
39
|
-
| `consent.accentColor` | `string` | no |
|
|
37
|
+
| `consent.brandName` | `string` | yes | サイト名(内蔵UIに表示) |
|
|
38
|
+
| `consent.position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | yes | 内蔵UIの表示位置 |
|
|
39
|
+
| `consent.accentColor` | `string` | no | 内蔵UIのアクセントカラー |
|
|
40
|
+
| `consent.onConsentRequired` | `(controls: ConsentControls) => void` | no | ホスト独自の同意UIを使う場合に指定。指定すると内蔵バナーは表示されない |
|
|
40
41
|
| `maxCpuLoad` | `number` | no | CPU負荷制限(未実装) |
|
|
41
42
|
|
|
43
|
+
### ホスト独自の同意UI
|
|
44
|
+
|
|
45
|
+
`consent.onConsentRequired` を渡すと、内蔵の Shadow DOM バナーは使われず、
|
|
46
|
+
同意状態が `unset` のときだけホストが呼び出されます。ホストは `controls.accept()` /
|
|
47
|
+
`controls.reject()` を呼ぶだけで、保存とノードの起動・停止はSDK側が行います。
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
initFlaxiaNode({
|
|
51
|
+
orchestratorUrl: 'https://crowd.flaxia.app',
|
|
52
|
+
siteId: 'your-site-id',
|
|
53
|
+
consent: {
|
|
54
|
+
brandName: 'あなたのサービス名',
|
|
55
|
+
position: 'bottom-right',
|
|
56
|
+
onConsentRequired: ({ state, accept, reject }) => {
|
|
57
|
+
// 自前のモーダルを表示し、ユーザーの選択に応じて accept() / reject()
|
|
58
|
+
showMyConsentModal({
|
|
59
|
+
onAccept: accept,
|
|
60
|
+
onReject: reject,
|
|
61
|
+
})
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### コントローラ(設定画面からの変更)
|
|
68
|
+
|
|
69
|
+
`initFlaxiaNode()` は `FlaxiaNodeController` を返します。同一モジュール内で一度だけ
|
|
70
|
+
状態を保持するため、設定画面から同意の付与・取消をリロードなしで行えます。
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const controller = initFlaxiaNode({ /* ... */ })
|
|
74
|
+
|
|
75
|
+
controller.getConsentState() // 'unset' | 'granted' | 'denied'
|
|
76
|
+
controller.grant() // 同意を保存(拒否を消去)
|
|
77
|
+
controller.deny() // 拒否を保存して停止(同意を消去)
|
|
78
|
+
controller.start() // ノード起動
|
|
79
|
+
controller.stop() // ノード停止 + Web Worker 解放
|
|
80
|
+
controller.isRunning() // 稼働中か
|
|
81
|
+
controller.clearConsent() // 同意・拒否・失効時刻をすべて消去
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
同意は localStorage に保存され、**30日**で失効します。`grant` と `deny` は排他で、
|
|
85
|
+
片方を保存するともう片方は消去されます。
|
|
86
|
+
|
|
42
87
|
## アーキテクチャ
|
|
43
88
|
|
|
44
89
|
```
|
|
@@ -93,11 +138,12 @@ initFlaxiaNode({
|
|
|
93
138
|
## フロー
|
|
94
139
|
|
|
95
140
|
1. `initFlaxiaNode()` が呼ばれると、localStorage の同意状態を確認
|
|
96
|
-
2.
|
|
141
|
+
2. 未同意の場合、内蔵の Shadow DOM 同意バナー、または `onConsentRequired` でホストのUIを表示
|
|
97
142
|
3. 同意後、WebSocket でオーケストレーターに接続(`/crowd/signal`)
|
|
98
143
|
4. タスクを受信すると WebWorker で実行
|
|
99
144
|
5. 結果・中間トークンを WebSocket で返送
|
|
100
145
|
6. 切断時は指数バックオフ付き自動再接続(最大30秒)
|
|
146
|
+
7. `FlaxiaNodeController` 経由で設定画面から同意の付与・取消が可能
|
|
101
147
|
|
|
102
148
|
## 開発
|
|
103
149
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NodeConfig, WorkloadType } from '@flaxia/sdk';
|
|
1
|
+
import type { FlaxiaNodeController, NodeConfig, WorkloadType } from '@flaxia/sdk';
|
|
2
2
|
export interface TaskMessage {
|
|
3
3
|
type: 'task';
|
|
4
4
|
taskId: string;
|
|
@@ -6,5 +6,5 @@ export interface TaskMessage {
|
|
|
6
6
|
payload: unknown;
|
|
7
7
|
timeoutMs?: number;
|
|
8
8
|
}
|
|
9
|
-
export declare const initFlaxiaNode: (config: NodeConfig) =>
|
|
9
|
+
export declare const initFlaxiaNode: (config: NodeConfig) => FlaxiaNodeController;
|
|
10
10
|
//# sourceMappingURL=SignalingClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignalingClient.d.ts","sourceRoot":"","sources":["../../src/client/SignalingClient.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"SignalingClient.d.ts","sourceRoot":"","sources":["../../src/client/SignalingClient.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAgB,oBAAoB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhG,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2VD,eAAO,MAAM,cAAc,GAAI,QAAQ,UAAU,KAAG,oBAwDnD,CAAC"}
|
|
@@ -5,7 +5,7 @@ interface ConsentConfig {
|
|
|
5
5
|
}
|
|
6
6
|
export declare class ConsentUI {
|
|
7
7
|
private shadow;
|
|
8
|
-
constructor(container: HTMLElement, config: ConsentConfig, onConsent: () => void);
|
|
8
|
+
constructor(container: HTMLElement, config: ConsentConfig, onConsent: () => void, onReject?: () => void);
|
|
9
9
|
private render;
|
|
10
10
|
}
|
|
11
11
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsentUI.d.ts","sourceRoot":"","sources":["../../src/consent/ConsentUI.ts"],"names":[],"mappings":"AAAA,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAa;
|
|
1
|
+
{"version":3,"file":"ConsentUI.d.ts","sourceRoot":"","sources":["../../src/consent/ConsentUI.ts"],"names":[],"mappings":"AAAA,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAa;gBAGzB,SAAS,EAAE,WAAW,EACtB,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,IAAI,EACrB,QAAQ,CAAC,EAAE,MAAM,IAAI;IAMvB,OAAO,CAAC,MAAM;CAyFf"}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
import type { ConsentState } from '@flaxia/sdk';
|
|
1
2
|
declare function safeGet(key: string): string | null;
|
|
2
3
|
declare function safeSet(key: string, value: string): void;
|
|
3
4
|
declare function safeRemove(key: string): void;
|
|
4
5
|
export declare const hasConsent: () => boolean;
|
|
6
|
+
/** Persist consent. Clears any previous denial so the two never co-exist. */
|
|
5
7
|
export declare const saveConsent: () => void;
|
|
8
|
+
export declare const hasDenial: () => boolean;
|
|
9
|
+
/** Persist denial. Clears any previous consent/expiry so the two never co-exist. */
|
|
10
|
+
export declare const saveDenial: () => void;
|
|
11
|
+
/** Forget the consent decision entirely (granted + expiry + denial). */
|
|
12
|
+
export declare const clearConsent: () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the persisted consent state. A stored grant wins over a stored
|
|
15
|
+
* denial, but the two are kept mutually exclusive by the save helpers.
|
|
16
|
+
*/
|
|
17
|
+
export declare const getConsentState: () => ConsentState;
|
|
6
18
|
export declare const safeLocalStorageGet: typeof safeGet;
|
|
7
19
|
export declare const safeLocalStorageSet: typeof safeSet;
|
|
8
20
|
export declare const safeLocalStorageRemove: typeof safeRemove;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/consent/storage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/consent/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAahD,iBAAS,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3C;AAED,iBAAS,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAMjD;AAED,iBAAS,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAMrC;AAED,eAAO,MAAM,UAAU,QAAO,OAgB7B,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,WAAW,QAAO,IAI9B,CAAC;AAEF,eAAO,MAAM,SAAS,QAAO,OAE5B,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,UAAU,QAAO,IAI7B,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,YAAY,QAAO,IAI/B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,QAAO,YAIlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,gBAAU,CAAC;AAC3C,eAAO,MAAM,mBAAmB,gBAAU,CAAC;AAC3C,eAAO,MAAM,sBAAsB,mBAAa,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,cAAc,QAAO,MAajC,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { r as e, t } from "./assets/memoryProbe.js";
|
|
2
2
|
//#region src/consent/ConsentUI.ts
|
|
3
3
|
var n = class {
|
|
4
|
-
constructor(e, t, n) {
|
|
5
|
-
this.shadow = e.attachShadow({ mode: "open" }), this.render(t, n);
|
|
4
|
+
constructor(e, t, n, r) {
|
|
5
|
+
this.shadow = e.attachShadow({ mode: "open" }), this.render(t, n, r);
|
|
6
6
|
}
|
|
7
|
-
render(e, t) {
|
|
8
|
-
let { brandName:
|
|
9
|
-
|
|
7
|
+
render(e, t, n) {
|
|
8
|
+
let { brandName: r, position: i, accentColor: a = "#6366f1" } = e, [o, s] = i.split("-"), c = document.createElement("style");
|
|
9
|
+
c.textContent = `
|
|
10
10
|
.overlay {
|
|
11
11
|
position: fixed;
|
|
12
|
-
${a}: 20px;
|
|
13
12
|
${o}: 20px;
|
|
13
|
+
${s}: 20px;
|
|
14
14
|
padding: 20px;
|
|
15
15
|
background: #fff;
|
|
16
|
+
color: #1f2937;
|
|
16
17
|
border: 1px solid #ccc;
|
|
17
18
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
18
19
|
z-index: 9999;
|
|
@@ -20,8 +21,19 @@ var n = class {
|
|
|
20
21
|
max-width: 300px;
|
|
21
22
|
font-family: sans-serif;
|
|
22
23
|
}
|
|
24
|
+
.overlay h3 {
|
|
25
|
+
margin: 0 0 8px;
|
|
26
|
+
color: #111827;
|
|
27
|
+
font-size: 16px;
|
|
28
|
+
}
|
|
29
|
+
.overlay p {
|
|
30
|
+
margin: 0 0 12px;
|
|
31
|
+
color: #4b5563;
|
|
32
|
+
font-size: 14px;
|
|
33
|
+
line-height: 1.4;
|
|
34
|
+
}
|
|
23
35
|
button {
|
|
24
|
-
background: ${
|
|
36
|
+
background: ${a};
|
|
25
37
|
color: white;
|
|
26
38
|
border: none;
|
|
27
39
|
padding: 10px 15px;
|
|
@@ -29,50 +41,81 @@ var n = class {
|
|
|
29
41
|
cursor: pointer;
|
|
30
42
|
margin-top: 10px;
|
|
31
43
|
width: 100%;
|
|
44
|
+
font-size: 14px;
|
|
45
|
+
}
|
|
46
|
+
button.reject {
|
|
47
|
+
background: transparent;
|
|
48
|
+
color: #4b5563;
|
|
49
|
+
border: 1px solid #ccc;
|
|
50
|
+
}
|
|
51
|
+
@media (prefers-color-scheme: dark) {
|
|
52
|
+
.overlay {
|
|
53
|
+
background: #1f2937;
|
|
54
|
+
color: #f9fafb;
|
|
55
|
+
border-color: #374151;
|
|
56
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.4);
|
|
57
|
+
}
|
|
58
|
+
.overlay h3 {
|
|
59
|
+
color: #f9fafb;
|
|
60
|
+
}
|
|
61
|
+
.overlay p {
|
|
62
|
+
color: #d1d5db;
|
|
63
|
+
}
|
|
64
|
+
button.reject {
|
|
65
|
+
color: #d1d5db;
|
|
66
|
+
border-color: #4b5563;
|
|
67
|
+
}
|
|
32
68
|
}
|
|
33
69
|
`;
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
<h3>${
|
|
70
|
+
let l = document.createElement("div");
|
|
71
|
+
l.className = "overlay", l.innerHTML = `
|
|
72
|
+
<h3>${r}</h3>
|
|
37
73
|
<p>サイトのパフォーマンス向上にご協力ください。</p>
|
|
38
74
|
<button id="consent-btn">同意して開始</button>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
75
|
+
<button id="reject-btn" class="reject">拒否</button>
|
|
76
|
+
`, l.querySelector("#consent-btn")?.addEventListener("click", () => {
|
|
77
|
+
l.remove(), t();
|
|
78
|
+
}), l.querySelector("#reject-btn")?.addEventListener("click", () => {
|
|
79
|
+
l.remove(), n?.();
|
|
80
|
+
}), this.shadow.appendChild(c), this.shadow.appendChild(l);
|
|
42
81
|
}
|
|
43
|
-
}, r = "flaxia_consent_granted", i = "flaxia_consent_expiry", a = 720 * 60 * 60 * 1e3,
|
|
44
|
-
function
|
|
82
|
+
}, r = "flaxia_consent_granted", i = "flaxia_consent_expiry", a = "flaxia_consent_denied", o = 720 * 60 * 60 * 1e3, s = /* @__PURE__ */ new Map();
|
|
83
|
+
function c(e) {
|
|
45
84
|
try {
|
|
46
85
|
return localStorage.getItem(e);
|
|
47
86
|
} catch {
|
|
48
|
-
return
|
|
87
|
+
return s.get(e) ?? null;
|
|
49
88
|
}
|
|
50
89
|
}
|
|
51
|
-
function
|
|
90
|
+
function l(e, t) {
|
|
52
91
|
try {
|
|
53
92
|
localStorage.setItem(e, t);
|
|
54
93
|
} catch {
|
|
55
|
-
|
|
94
|
+
s.set(e, t);
|
|
56
95
|
}
|
|
57
96
|
}
|
|
58
|
-
function
|
|
97
|
+
function u(e) {
|
|
59
98
|
try {
|
|
60
99
|
localStorage.removeItem(e);
|
|
61
100
|
} catch {
|
|
62
|
-
|
|
101
|
+
s.delete(e);
|
|
63
102
|
}
|
|
64
103
|
}
|
|
65
|
-
var
|
|
66
|
-
if (
|
|
67
|
-
let e =
|
|
104
|
+
var d = () => {
|
|
105
|
+
if (c(r) !== "true") return !1;
|
|
106
|
+
let e = c(i);
|
|
68
107
|
if (e) {
|
|
69
108
|
let t = parseInt(e, 10);
|
|
70
|
-
if (!Number.isFinite(t) || Date.now() > t) return
|
|
109
|
+
if (!Number.isFinite(t) || Date.now() > t) return u(r), u(i), !1;
|
|
71
110
|
}
|
|
72
111
|
return !0;
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
},
|
|
112
|
+
}, f = () => {
|
|
113
|
+
u(a), l(r, "true"), l(i, String(Date.now() + o));
|
|
114
|
+
}, p = () => c(a) === "true", m = () => {
|
|
115
|
+
u(r), u(i), l(a, "true");
|
|
116
|
+
}, h = () => {
|
|
117
|
+
u(r), u(i), u(a);
|
|
118
|
+
}, g = () => d() ? "granted" : p() ? "denied" : "unset", _ = c, v = l, y = u, b = () => {
|
|
76
119
|
try {
|
|
77
120
|
if (typeof crypto < "u" && typeof crypto.randomUUID == "function") return crypto.randomUUID();
|
|
78
121
|
} catch {}
|
|
@@ -80,7 +123,7 @@ var u = () => {
|
|
|
80
123
|
let t = Math.random() * 16 | 0;
|
|
81
124
|
return (e === "x" ? t : t & 3 | 8).toString(16);
|
|
82
125
|
});
|
|
83
|
-
},
|
|
126
|
+
}, x = (...e) => console.log("[flaxia-node]", ...e), S = class {
|
|
84
127
|
constructor(e, t = 3e5) {
|
|
85
128
|
this.worker = null, this.queue = [], this.active = null, this._lastCpuLoad = 0, this.workerUrl = e || "/worker.js", this.defaultTimeoutMs = t, this.initWorker();
|
|
86
129
|
}
|
|
@@ -100,7 +143,7 @@ var u = () => {
|
|
|
100
143
|
return;
|
|
101
144
|
}
|
|
102
145
|
let c = () => this.execute(e, t, n, r, i, a, o, s);
|
|
103
|
-
this.active ? (
|
|
146
|
+
this.active ? (x(`task queued id=${e} workload=${t} (active task running)`), this.queue.push(c)) : c();
|
|
104
147
|
});
|
|
105
148
|
}
|
|
106
149
|
execute(e, t, n, r, i, a, o, s) {
|
|
@@ -110,7 +153,7 @@ var u = () => {
|
|
|
110
153
|
return;
|
|
111
154
|
}
|
|
112
155
|
let l = r ?? this.defaultTimeoutMs, u = !1, d = performance.now();
|
|
113
|
-
|
|
156
|
+
x(`task start id=${e} workload=${t} timeoutMs=${l}`);
|
|
114
157
|
let f = (e) => {
|
|
115
158
|
if (u) return;
|
|
116
159
|
u = !0;
|
|
@@ -127,16 +170,16 @@ var u = () => {
|
|
|
127
170
|
typeof p == "number" && Number.isFinite(p) && (this._lastCpuLoad = Math.min(1, Math.max(0, p)));
|
|
128
171
|
return;
|
|
129
172
|
}
|
|
130
|
-
a === "done" ? (
|
|
173
|
+
a === "done" ? (x(`task done id=${e} workload=${t} durationMs=${Math.round(performance.now() - d)}`), f(() => o(c))) : a === "error" ? (x(`task failed id=${e} workload=${t} error=${l}`), f(() => s(Error(l)))) : (x(`task unknown-message id=${e} type=${String(a)}`), f(() => s(/* @__PURE__ */ Error(`Unknown message type: ${String(a)}`))));
|
|
131
174
|
}
|
|
132
|
-
}, m = () => c.removeEventListener("message", p),
|
|
133
|
-
|
|
175
|
+
}, m = () => c.removeEventListener("message", p), h = setTimeout(() => {
|
|
176
|
+
x(`task timeout id=${e} workload=${t} (worker terminated after ${l}ms)`), this.cleanupWorker(), f(() => s(/* @__PURE__ */ Error("TIMEOUT")));
|
|
134
177
|
}, l);
|
|
135
178
|
this.active = {
|
|
136
179
|
id: e,
|
|
137
180
|
resolve: o,
|
|
138
181
|
reject: s,
|
|
139
|
-
timer:
|
|
182
|
+
timer: h,
|
|
140
183
|
worker: c,
|
|
141
184
|
removeListener: m
|
|
142
185
|
}, c.addEventListener("message", p);
|
|
@@ -153,7 +196,7 @@ var u = () => {
|
|
|
153
196
|
}
|
|
154
197
|
}
|
|
155
198
|
handleWorkerError(e) {
|
|
156
|
-
|
|
199
|
+
x(`worker error error=${e.message}`);
|
|
157
200
|
let t = this.active;
|
|
158
201
|
this.cleanupWorker(), t && (clearTimeout(t.timer), this.active = null, t.reject(e), this.dequeue());
|
|
159
202
|
}
|
|
@@ -173,13 +216,13 @@ var u = () => {
|
|
|
173
216
|
get lastCpuLoad() {
|
|
174
217
|
return this._lastCpuLoad;
|
|
175
218
|
}
|
|
176
|
-
},
|
|
219
|
+
}, C = (...e) => console.log("[flaxia-node]", ...e), w = (...e) => console.error("[flaxia-node]", ...e), T = "flaxia_node_id", E = "flaxia_node_token", D = class {
|
|
177
220
|
constructor(e, t, n) {
|
|
178
221
|
this.config = e, this.workerPool = t, this.nodeId = n, this.ws = null, this.reconnectAttempts = 0, this.MAX_RECONNECT_DELAY = 3e4, this.destroyed = !1, this.suspended = !1, this.visibilityHandler = null, this.inflightTasks = /* @__PURE__ */ new Set();
|
|
179
222
|
}
|
|
180
223
|
async connect() {
|
|
181
224
|
if (this.destroyed) return;
|
|
182
|
-
if (
|
|
225
|
+
if (C(`connect nodeId=${this.nodeId}`), this.ws) {
|
|
183
226
|
let e = this.ws;
|
|
184
227
|
e.onclose = null;
|
|
185
228
|
try {
|
|
@@ -190,14 +233,14 @@ var u = () => {
|
|
|
190
233
|
this.setupVisibilityHandler();
|
|
191
234
|
let e = await this.obtainToken();
|
|
192
235
|
if (!e) {
|
|
193
|
-
|
|
236
|
+
w("token acquisition failed; scheduling reconnect"), this.scheduleReconnect();
|
|
194
237
|
return;
|
|
195
238
|
}
|
|
196
239
|
let t = this.config.orchestratorUrl.replace(/\/+$/, "").replace(/^http/, "ws"), n = new URL(`${t}/crowd/signal`);
|
|
197
240
|
n.searchParams.set("token", e.token);
|
|
198
241
|
let r = new WebSocket(n.toString());
|
|
199
242
|
this.ws = r, r.onopen = () => {
|
|
200
|
-
this.reconnectAttempts = 0,
|
|
243
|
+
this.reconnectAttempts = 0, C(`signal connected ${n.toString()}`);
|
|
201
244
|
}, r.onmessage = async (e) => {
|
|
202
245
|
let t;
|
|
203
246
|
try {
|
|
@@ -214,12 +257,12 @@ var u = () => {
|
|
|
214
257
|
}
|
|
215
258
|
if (t.type === "task") {
|
|
216
259
|
let e = t;
|
|
217
|
-
|
|
260
|
+
C(`task received taskId=${e.taskId} workload=${e.workload} timeoutMs=${e.timeoutMs ?? "default"}`);
|
|
218
261
|
try {
|
|
219
262
|
await this.handleTask(e);
|
|
220
263
|
} catch (n) {
|
|
221
264
|
let r = n instanceof Error ? n.message : String(n);
|
|
222
|
-
|
|
265
|
+
w(`task failed taskId=${e.taskId} workload=${e.workload} error=${r}`), this.send({
|
|
223
266
|
type: "error",
|
|
224
267
|
taskId: String(t.taskId ?? ""),
|
|
225
268
|
error: r
|
|
@@ -227,7 +270,7 @@ var u = () => {
|
|
|
227
270
|
}
|
|
228
271
|
}
|
|
229
272
|
}, r.onclose = () => {
|
|
230
|
-
r.onclose = null, this.ws === r && (this.ws = null, !this.destroyed && (
|
|
273
|
+
r.onclose = null, this.ws === r && (this.ws = null, !this.destroyed && (C(`signal disconnected; scheduling reconnect attempt=${this.reconnectAttempts + 1}`), this.scheduleReconnect()));
|
|
231
274
|
};
|
|
232
275
|
}
|
|
233
276
|
scheduleReconnect() {
|
|
@@ -237,13 +280,13 @@ var u = () => {
|
|
|
237
280
|
}, e);
|
|
238
281
|
}
|
|
239
282
|
disconnect() {
|
|
240
|
-
|
|
283
|
+
C(`disconnect nodeId=${this.nodeId}`), this.destroyed = !0, this.suspended = !1, this.removeVisibilityHandler();
|
|
241
284
|
try {
|
|
242
285
|
this.ws?.close();
|
|
243
286
|
} catch {}
|
|
244
287
|
this.ws = null;
|
|
245
288
|
try {
|
|
246
|
-
delete window[
|
|
289
|
+
delete window[k];
|
|
247
290
|
} catch {}
|
|
248
291
|
try {
|
|
249
292
|
this.workerPool.terminate();
|
|
@@ -251,7 +294,7 @@ var u = () => {
|
|
|
251
294
|
}
|
|
252
295
|
suspend() {
|
|
253
296
|
if (!(this.suspended || this.destroyed)) {
|
|
254
|
-
if (
|
|
297
|
+
if (C("suspended (visibility or manual)"), this.suspended = !0, this.ws) {
|
|
255
298
|
let e = this.ws;
|
|
256
299
|
e.onclose = null;
|
|
257
300
|
try {
|
|
@@ -263,17 +306,17 @@ var u = () => {
|
|
|
263
306
|
}
|
|
264
307
|
}
|
|
265
308
|
resume() {
|
|
266
|
-
!this.suspended || this.destroyed || (
|
|
309
|
+
!this.suspended || this.destroyed || (C("resumed"), this.suspended = !1, this.workerPool.resume(), this.connect());
|
|
267
310
|
}
|
|
268
311
|
async obtainToken() {
|
|
269
312
|
try {
|
|
270
|
-
let n =
|
|
313
|
+
let n = _(E);
|
|
271
314
|
if (n) try {
|
|
272
315
|
let e = JSON.parse(n);
|
|
273
|
-
if (e.token && e.nodeId && e.expiresAt > Date.now() + 6e4) return
|
|
316
|
+
if (e.token && e.nodeId && e.expiresAt > Date.now() + 6e4) return C(`token cached nodeId=${e.nodeId} expiresIn=${Math.round((e.expiresAt - Date.now()) / 1e3)}s`), e;
|
|
274
317
|
} catch {}
|
|
275
318
|
let r = e(), i = r >= t, a = i ? this.config.capabilities ?? ["ai-inference", "image-process"] : [];
|
|
276
|
-
|
|
319
|
+
C(`register capability probe capable=${i} wasmMemoryBytes=${r} capabilities=${JSON.stringify(a)}`);
|
|
277
320
|
let o = this.config.orchestratorUrl.replace(/\/+$/, ""), s = await fetch(`${o}/crowd/nodes/register`, {
|
|
278
321
|
method: "POST",
|
|
279
322
|
headers: { "Content-Type": "application/json" },
|
|
@@ -285,16 +328,16 @@ var u = () => {
|
|
|
285
328
|
deviceMemory: typeof navigator.deviceMemory == "number" ? navigator.deviceMemory : null
|
|
286
329
|
})
|
|
287
330
|
});
|
|
288
|
-
if (!s.ok) return
|
|
331
|
+
if (!s.ok) return w(`node register failed HTTP ${s.status} (${o}/crowd/nodes/register)`), null;
|
|
289
332
|
let c = await s.json();
|
|
290
|
-
if (!c.token) return
|
|
291
|
-
|
|
333
|
+
if (!c.token) return w("node register returned no token"), null;
|
|
334
|
+
C(`node registered nodeId=${this.nodeId} expiresIn=${Math.round((c.expiresAt - Date.now()) / 1e3)}s`);
|
|
292
335
|
try {
|
|
293
|
-
|
|
336
|
+
v(E, JSON.stringify(c));
|
|
294
337
|
} catch {}
|
|
295
338
|
return c;
|
|
296
339
|
} catch (e) {
|
|
297
|
-
return
|
|
340
|
+
return w("node register threw", e instanceof Error ? e.message : e), null;
|
|
298
341
|
}
|
|
299
342
|
}
|
|
300
343
|
send(e) {
|
|
@@ -316,7 +359,7 @@ var u = () => {
|
|
|
316
359
|
type: "result",
|
|
317
360
|
taskId: e.taskId,
|
|
318
361
|
payload: r
|
|
319
|
-
}),
|
|
362
|
+
}), C(`task result sent taskId=${e.taskId} workload=${e.workload} durationMs=${Math.round(performance.now() - t)}`);
|
|
320
363
|
} finally {
|
|
321
364
|
this.inflightTasks.delete(e.taskId);
|
|
322
365
|
}
|
|
@@ -329,23 +372,62 @@ var u = () => {
|
|
|
329
372
|
removeVisibilityHandler() {
|
|
330
373
|
this.visibilityHandler &&= (document.removeEventListener("visibilitychange", this.visibilityHandler), null);
|
|
331
374
|
}
|
|
332
|
-
},
|
|
333
|
-
let t = window[
|
|
375
|
+
}, O = "__flaxia_node_signal_client", k = "__flaxia_node_init_started", A = "__flaxia_node_controller", j = (e) => {
|
|
376
|
+
let t = window[O];
|
|
334
377
|
t && t.disconnect();
|
|
335
|
-
let n = new URL("./worker.js", "" + import.meta.url).href, r = new
|
|
336
|
-
i || (i =
|
|
337
|
-
let a = new
|
|
338
|
-
window[
|
|
339
|
-
},
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
378
|
+
let n = new URL("./worker.js", "" + import.meta.url).href, r = new S(n), i = _(T);
|
|
379
|
+
i || (i = b(), v(T, i));
|
|
380
|
+
let a = new D(e, r, i);
|
|
381
|
+
window[O] = a, a.connect();
|
|
382
|
+
}, M = class {
|
|
383
|
+
constructor(e) {
|
|
384
|
+
this.config = e;
|
|
385
|
+
}
|
|
386
|
+
start() {
|
|
387
|
+
this.isRunning() || j(this.config);
|
|
388
|
+
}
|
|
389
|
+
stop() {
|
|
390
|
+
let e = window[O];
|
|
391
|
+
e && e.disconnect(), delete window[O], delete window[k];
|
|
392
|
+
}
|
|
393
|
+
isRunning() {
|
|
394
|
+
return !!window[O];
|
|
395
|
+
}
|
|
396
|
+
getConsentState() {
|
|
397
|
+
return g();
|
|
398
|
+
}
|
|
399
|
+
grant() {
|
|
400
|
+
f();
|
|
401
|
+
}
|
|
402
|
+
deny() {
|
|
403
|
+
m(), this.stop(), y(E);
|
|
404
|
+
}
|
|
405
|
+
clearConsent() {
|
|
406
|
+
h(), this.stop(), y(E);
|
|
407
|
+
}
|
|
408
|
+
}, N = (e) => {
|
|
409
|
+
let t = window[A];
|
|
410
|
+
if (t) return t;
|
|
411
|
+
let r = new M(e);
|
|
412
|
+
window[A] = r, window[k] = !0;
|
|
413
|
+
let i = g();
|
|
414
|
+
if (i === "granted") return r.start(), r;
|
|
415
|
+
if (i === "denied") return r;
|
|
416
|
+
if (e.consent.onConsentRequired) return e.consent.onConsentRequired({
|
|
417
|
+
state: i,
|
|
418
|
+
accept: () => {
|
|
419
|
+
f(), r.start();
|
|
420
|
+
},
|
|
421
|
+
reject: () => {
|
|
422
|
+
m();
|
|
423
|
+
}
|
|
424
|
+
}), r;
|
|
425
|
+
let a = document.createElement("div");
|
|
426
|
+
return a.id = "flaxia-consent-container", document.body.appendChild(a), new n(a, e.consent, () => {
|
|
427
|
+
f(), a.remove(), r.start();
|
|
428
|
+
}, () => {
|
|
429
|
+
m(), a.remove();
|
|
430
|
+
}), r;
|
|
349
431
|
};
|
|
350
432
|
//#endregion
|
|
351
|
-
export {
|
|
433
|
+
export { N as initFlaxiaNode };
|
package/dist/worker.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import { n as e, r as t, t as n } from "./assets/memoryProbe.js";
|
|
2
|
+
//#region ../sdk/dist/index.js
|
|
3
|
+
var r = new Set([
|
|
4
|
+
"ai-inference",
|
|
5
|
+
"image-process",
|
|
6
|
+
"container",
|
|
7
|
+
"vector-embed",
|
|
8
|
+
"vector-query",
|
|
9
|
+
"nudenet"
|
|
10
|
+
]);
|
|
11
|
+
//#endregion
|
|
2
12
|
//#region src/executor/throttle.ts
|
|
3
|
-
function
|
|
13
|
+
function i(e) {
|
|
4
14
|
return new Promise((t) => setTimeout(t, e));
|
|
5
15
|
}
|
|
6
|
-
var
|
|
16
|
+
var a = 3e4, o = 3e4, s = class {
|
|
7
17
|
constructor(e = .15) {
|
|
8
18
|
this.windowStart = performance.now(), this.windowBusyMs = 0, this.lastLoad = 0, this.maxLoad = Math.max(.05, Math.min(.3, e));
|
|
9
19
|
}
|
|
10
20
|
markTaskComplete(e) {
|
|
11
21
|
let t = performance.now();
|
|
12
|
-
t - this.windowStart >
|
|
22
|
+
t - this.windowStart > a && (this.windowStart = t, this.windowBusyMs = 0), this.windowBusyMs += Math.max(0, e), this.lastLoad = this.estimate();
|
|
13
23
|
}
|
|
14
24
|
estimate() {
|
|
15
25
|
let e = performance.now() - this.windowStart;
|
|
@@ -18,9 +28,9 @@ var i = 3e4, a = 3e4, o = class {
|
|
|
18
28
|
async shouldPause() {
|
|
19
29
|
return this.estimate() > this.maxLoad;
|
|
20
30
|
}
|
|
21
|
-
async waitForSlot(e =
|
|
31
|
+
async waitForSlot(e = o) {
|
|
22
32
|
let t = performance.now();
|
|
23
|
-
for (; await this.shouldPause() && !(performance.now() - t > e);) await
|
|
33
|
+
for (; await this.shouldPause() && !(performance.now() - t > e);) await i(500);
|
|
24
34
|
}
|
|
25
35
|
get lastMeasuredLoad() {
|
|
26
36
|
return this.lastLoad;
|
|
@@ -28,14 +38,7 @@ var i = 3e4, a = 3e4, o = class {
|
|
|
28
38
|
get maxLoadValue() {
|
|
29
39
|
return this.maxLoad;
|
|
30
40
|
}
|
|
31
|
-
},
|
|
32
|
-
"ai-inference",
|
|
33
|
-
"vector-embed",
|
|
34
|
-
"vector-query",
|
|
35
|
-
"nudenet",
|
|
36
|
-
"image-process",
|
|
37
|
-
"container"
|
|
38
|
-
]), l = 6e4, u = t(), d = u >= n;
|
|
41
|
+
}, c = null, l = 6e4, u = t(), d = u >= n;
|
|
39
42
|
console.log(`[flaxia-node:worker] startup memory probe wasmMemoryBytes=${u} capable=${d}`);
|
|
40
43
|
function f() {
|
|
41
44
|
return e();
|
|
@@ -75,20 +78,20 @@ async function _(e, t, n) {
|
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
self.onmessage = async (e) => {
|
|
78
|
-
let { id: t, workload:
|
|
81
|
+
let { id: t, workload: i, payload: a, config: o } = e.data, l = performance.now();
|
|
79
82
|
try {
|
|
80
|
-
if (
|
|
81
|
-
if (
|
|
82
|
-
await
|
|
83
|
+
if (c ||= new s(o?.maxCpuLoad), console.log(`[flaxia-node:worker] task start id=${t} workload=${i}`), !d) throw Error(`node incapable: insufficient WASM memory (${u} bytes < ${n}); rejecting all tasks`);
|
|
84
|
+
if (r.has(i) && !f()) throw Error(`${i}: insufficient WASM memory (${u} bytes < ${n})`);
|
|
85
|
+
await c.waitForSlot();
|
|
83
86
|
let e = setInterval(() => {
|
|
84
87
|
self.postMessage({
|
|
85
88
|
id: t,
|
|
86
89
|
type: "heartbeat",
|
|
87
|
-
cpuLoad:
|
|
90
|
+
cpuLoad: c?.lastMeasuredLoad ?? 0
|
|
88
91
|
});
|
|
89
92
|
}, 1e4), p = performance.now();
|
|
90
93
|
try {
|
|
91
|
-
let e = await _(
|
|
94
|
+
let e = await _(i, a, (e) => {
|
|
92
95
|
self.postMessage({
|
|
93
96
|
id: t,
|
|
94
97
|
type: "token",
|
|
@@ -99,13 +102,13 @@ self.onmessage = async (e) => {
|
|
|
99
102
|
id: t,
|
|
100
103
|
type: "done",
|
|
101
104
|
result: e
|
|
102
|
-
}), console.log(`[flaxia-node:worker] task done id=${t} workload=${
|
|
105
|
+
}), console.log(`[flaxia-node:worker] task done id=${t} workload=${i} durationMs=${Math.round(performance.now() - l)}`);
|
|
103
106
|
} finally {
|
|
104
|
-
clearInterval(e),
|
|
107
|
+
clearInterval(e), c.markTaskComplete(performance.now() - p), g();
|
|
105
108
|
}
|
|
106
109
|
} catch (e) {
|
|
107
110
|
let n = e instanceof Error ? e.message : String(e);
|
|
108
|
-
console.error(`[flaxia-node:worker] task error id=${t} workload=${
|
|
111
|
+
console.error(`[flaxia-node:worker] task error id=${t} workload=${i} error=${n}`), self.postMessage({
|
|
109
112
|
id: t,
|
|
110
113
|
type: "error",
|
|
111
114
|
error: n
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flaxia/node",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@bjorn3/browser_wasi_shim": "^0.4.2",
|
|
24
|
-
"@flaxia/sdk": "^0.
|
|
24
|
+
"@flaxia/sdk": "^0.4.0",
|
|
25
25
|
"@huggingface/transformers": "^4.2.0",
|
|
26
26
|
"onnxruntime-web": "1.26.0",
|
|
27
27
|
"rollup": "^4.61.0"
|