@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 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 | サイト名(同意UIに表示) |
38
- | `consent.position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | yes | 同意UIの表示位置 |
39
- | `consent.accentColor` | `string` | no | 同意UIのアクセントカラー |
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. 未同意の場合、Shadow DOM の同意バナーを表示
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) => void;
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;AAK5D,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;AA2RD,eAAO,MAAM,cAAc,GAAI,QAAQ,UAAU,SAsBhD,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;gBAEf,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,IAAI;IAKhF,OAAO,CAAC,MAAM;CA+Cf"}
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":"AAWA,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,eAAO,MAAM,WAAW,QAAO,IAG9B,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"}
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
@@ -1,3 +1,4 @@
1
1
  import { initFlaxiaNode } from './client/SignalingClient';
2
2
  export { initFlaxiaNode };
3
+ export type { ConsentControls, ConsentState, FlaxiaNodeController } from '@flaxia/sdk';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -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: n, position: r, accentColor: i = "#6366f1" } = e, [a, o] = r.split("-"), s = document.createElement("style");
9
- s.textContent = `
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: ${i};
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 c = document.createElement("div");
35
- c.className = "overlay", c.innerHTML = `
36
- <h3>${n}</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
- `, c.querySelector("#consent-btn")?.addEventListener("click", () => {
40
- c.remove(), t();
41
- }), this.shadow.appendChild(s), this.shadow.appendChild(c);
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, o = /* @__PURE__ */ new Map();
44
- function s(e) {
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 o.get(e) ?? null;
87
+ return s.get(e) ?? null;
49
88
  }
50
89
  }
51
- function c(e, t) {
90
+ function l(e, t) {
52
91
  try {
53
92
  localStorage.setItem(e, t);
54
93
  } catch {
55
- o.set(e, t);
94
+ s.set(e, t);
56
95
  }
57
96
  }
58
- function l(e) {
97
+ function u(e) {
59
98
  try {
60
99
  localStorage.removeItem(e);
61
100
  } catch {
62
- o.delete(e);
101
+ s.delete(e);
63
102
  }
64
103
  }
65
- var u = () => {
66
- if (s(r) !== "true") return !1;
67
- let e = s(i);
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 l(r), l(i), !1;
109
+ if (!Number.isFinite(t) || Date.now() > t) return u(r), u(i), !1;
71
110
  }
72
111
  return !0;
73
- }, d = () => {
74
- c(r, "true"), c(i, String(Date.now() + a));
75
- }, f = s, p = c, m = () => {
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
- }, h = (...e) => console.log("[flaxia-node]", ...e), g = class {
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 ? (h(`task queued id=${e} workload=${t} (active task running)`), this.queue.push(c)) : c();
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
- h(`task start id=${e} workload=${t} timeoutMs=${l}`);
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" ? (h(`task done id=${e} workload=${t} durationMs=${Math.round(performance.now() - d)}`), f(() => o(c))) : a === "error" ? (h(`task failed id=${e} workload=${t} error=${l}`), f(() => s(Error(l)))) : (h(`task unknown-message id=${e} type=${String(a)}`), f(() => s(/* @__PURE__ */ Error(`Unknown message type: ${String(a)}`))));
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), g = setTimeout(() => {
133
- h(`task timeout id=${e} workload=${t} (worker terminated after ${l}ms)`), this.cleanupWorker(), f(() => s(/* @__PURE__ */ Error("TIMEOUT")));
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: g,
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
- h(`worker error error=${e.message}`);
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
- }, _ = (...e) => console.log("[flaxia-node]", ...e), v = (...e) => console.error("[flaxia-node]", ...e), y = "flaxia_node_id", b = "flaxia_node_token", x = class {
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 (_(`connect nodeId=${this.nodeId}`), this.ws) {
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
- v("token acquisition failed; scheduling reconnect"), this.scheduleReconnect();
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, _(`signal connected ${n.toString()}`);
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
- _(`task received taskId=${e.taskId} workload=${e.workload} timeoutMs=${e.timeoutMs ?? "default"}`);
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
- v(`task failed taskId=${e.taskId} workload=${e.workload} error=${r}`), this.send({
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 && (_(`signal disconnected; scheduling reconnect attempt=${this.reconnectAttempts + 1}`), this.scheduleReconnect()));
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
- _(`disconnect nodeId=${this.nodeId}`), this.destroyed = !0, this.suspended = !1, this.removeVisibilityHandler();
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[C];
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 (_("suspended (visibility or manual)"), this.suspended = !0, this.ws) {
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 || (_("resumed"), this.suspended = !1, this.workerPool.resume(), this.connect());
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 = f(b);
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 _(`token cached nodeId=${e.nodeId} expiresIn=${Math.round((e.expiresAt - Date.now()) / 1e3)}s`), e;
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
- _(`register capability probe capable=${i} wasmMemoryBytes=${r} capabilities=${JSON.stringify(a)}`);
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 v(`node register failed HTTP ${s.status} (${o}/crowd/nodes/register)`), null;
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 v("node register returned no token"), null;
291
- _(`node registered nodeId=${this.nodeId} expiresIn=${Math.round((c.expiresAt - Date.now()) / 1e3)}s`);
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
- p(b, JSON.stringify(c));
336
+ v(E, JSON.stringify(c));
294
337
  } catch {}
295
338
  return c;
296
339
  } catch (e) {
297
- return v("node register threw", e instanceof Error ? e.message : e), null;
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
- }), _(`task result sent taskId=${e.taskId} workload=${e.workload} durationMs=${Math.round(performance.now() - t)}`);
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
- }, S = "__flaxia_node_signal_client", C = "__flaxia_node_init_started", w = (e) => {
333
- let t = window[S];
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 g(n), i = f(y);
336
- i || (i = m(), p(y, i));
337
- let a = new x(e, r, i);
338
- window[S] = a, a.connect();
339
- }, T = (e) => {
340
- if (window[C]) return;
341
- if (window[C] = !0, u()) {
342
- w(e);
343
- return;
344
- }
345
- let t = document.createElement("div");
346
- t.id = "flaxia-consent-container", document.body.appendChild(t), new n(t, e.consent, () => {
347
- d(), t.remove(), w(e);
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 { T as initFlaxiaNode };
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 r(e) {
13
+ function i(e) {
4
14
  return new Promise((t) => setTimeout(t, e));
5
15
  }
6
- var i = 3e4, a = 3e4, o = class {
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 > i && (this.windowStart = t, this.windowBusyMs = 0), this.windowBusyMs += Math.max(0, e), this.lastLoad = this.estimate();
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 = a) {
31
+ async waitForSlot(e = o) {
22
32
  let t = performance.now();
23
- for (; await this.shouldPause() && !(performance.now() - t > e);) await r(500);
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
- }, s = null, c = new Set([
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: r, payload: i, config: a } = e.data, l = performance.now();
81
+ let { id: t, workload: i, payload: a, config: o } = e.data, l = performance.now();
79
82
  try {
80
- if (s ||= new o(a?.maxCpuLoad), console.log(`[flaxia-node:worker] task start id=${t} workload=${r}`), !d) throw Error(`node incapable: insufficient WASM memory (${u} bytes < ${n}); rejecting all tasks`);
81
- if (c.has(r) && !f()) throw Error(`${r}: insufficient WASM memory (${u} bytes < ${n})`);
82
- await s.waitForSlot();
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: s?.lastMeasuredLoad ?? 0
90
+ cpuLoad: c?.lastMeasuredLoad ?? 0
88
91
  });
89
92
  }, 1e4), p = performance.now();
90
93
  try {
91
- let e = await _(r, i, (e) => {
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=${r} durationMs=${Math.round(performance.now() - l)}`);
105
+ }), console.log(`[flaxia-node:worker] task done id=${t} workload=${i} durationMs=${Math.round(performance.now() - l)}`);
103
106
  } finally {
104
- clearInterval(e), s.markTaskComplete(performance.now() - p), g();
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=${r} error=${n}`), self.postMessage({
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.4",
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.3.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"