@cyodaa/plugin-sdk 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cyodaa Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ registerPlugin: () => registerPlugin
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/register.ts
38
+ var import_react2 = require("react");
39
+
40
+ // src/context.ts
41
+ var import_react = require("react");
42
+ var CyodaaSDKContext = (0, import_react.createContext)(null);
43
+ CyodaaSDKContext.displayName = "CyodaaSDKContext";
44
+ function CyodaaSDKProvider({ value, children }) {
45
+ return (0, import_react.createElement)(CyodaaSDKContext.Provider, { value }, children);
46
+ }
47
+
48
+ // src/register.ts
49
+ function registerPlugin(config) {
50
+ const instance = {
51
+ config,
52
+ root: null,
53
+ mounted: false
54
+ };
55
+ async function loadReactDOM() {
56
+ const mod = await import(
57
+ /* @vite-ignore */
58
+ "react-dom/client"
59
+ );
60
+ return mod;
61
+ }
62
+ function buildTree(ctx, components) {
63
+ const children = components.map(
64
+ (Comp, i) => (0, import_react2.createElement)(Comp, { key: i })
65
+ );
66
+ const fragment = (0, import_react2.createElement)(import_react2.Fragment, null, ...children);
67
+ return (0, import_react2.createElement)(
68
+ CyodaaSDKProvider,
69
+ { value: ctx, children: fragment }
70
+ );
71
+ }
72
+ function mount(container, ctx) {
73
+ if (instance.mounted) {
74
+ unmount();
75
+ }
76
+ void (async () => {
77
+ try {
78
+ const reactDOM = await loadReactDOM();
79
+ const root = reactDOM.createRoot(container);
80
+ instance.root = root;
81
+ instance.mounted = true;
82
+ if (config.onInit) {
83
+ await Promise.resolve(config.onInit(ctx));
84
+ }
85
+ const tree = buildTree(ctx, config.components);
86
+ root.render(tree);
87
+ } catch (err) {
88
+ const message = err instanceof Error ? err.message : "Plugin \u8F09\u5165\u5931\u6557";
89
+ container.textContent = `[plugin-sdk] ${message}`;
90
+ }
91
+ })();
92
+ }
93
+ function unmount() {
94
+ if (!instance.mounted) return;
95
+ if (config.onDestroy) {
96
+ try {
97
+ void Promise.resolve(config.onDestroy());
98
+ } catch {
99
+ }
100
+ }
101
+ try {
102
+ instance.root?.unmount();
103
+ } catch {
104
+ }
105
+ instance.root = null;
106
+ instance.mounted = false;
107
+ }
108
+ return { mount, unmount };
109
+ }
110
+ // Annotate the CommonJS export names for ESM import in node:
111
+ 0 && (module.exports = {
112
+ registerPlugin
113
+ });
114
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/register.ts","../src/context.ts"],"sourcesContent":["/**\n * @cyodaa/plugin-sdk — 主要匯出入口\n *\n * 匯出 registerPlugin 註冊函式與所有公開型別。\n */\n\n// 核心註冊函式\nexport { registerPlugin } from './register';\n\n// 型別匯出\nexport type {\n CyodaaPluginContext,\n CyodaaContext,\n CyodaaUser,\n CyodaaTenant,\n CyodaaPlatform,\n PluginModule,\n PluginConfig,\n PluginComponent,\n} from './types';\n","/**\n * registerPlugin — Plugin 註冊入口\n *\n * 接收 PluginConfig,回傳符合 PluginModule 合約的 { mount, unmount },\n * 供 PluginRenderer 的 Shadow DOM 沙箱呼叫。\n *\n * react-dom 採用動態 import,避免 SDK 直接捆綁 react-dom。\n */\n\nimport { createElement, Fragment, type ComponentType } from 'react';\nimport type { CyodaaPluginContext, PluginConfig, PluginModule } from './types';\nimport { CyodaaSDKProvider } from './context';\n\n// ============================================================\n// react-dom/client 動態載入型別\n// ============================================================\n\ninterface ReactDOMRoot {\n render: (element: React.ReactNode) => void;\n unmount: () => void;\n}\n\ninterface ReactDOMClient {\n createRoot: (container: HTMLElement) => ReactDOMRoot;\n}\n\n// ============================================================\n// 內部狀態(per-plugin instance)\n// ============================================================\n\ninterface PluginInstance {\n readonly config: PluginConfig;\n root: ReactDOMRoot | null;\n mounted: boolean;\n}\n\n// ============================================================\n// registerPlugin\n// ============================================================\n\n/**\n * 註冊 Plugin,回傳 PluginModule。\n *\n * @example\n * ```ts\n * import { registerPlugin } from '@cyodaa/plugin-sdk';\n * import { App } from './App';\n *\n * const plugin = registerPlugin({\n * id: 'my-plugin',\n * components: [App],\n * });\n *\n * export const { mount, unmount } = plugin;\n * ```\n */\nexport function registerPlugin(config: PluginConfig): PluginModule {\n const instance: PluginInstance = {\n config,\n root: null,\n mounted: false,\n };\n\n /** 動態載入 react-dom/client */\n async function loadReactDOM(): Promise<ReactDOMClient> {\n // eslint-disable-next-line @typescript-eslint/no-require-imports -- 動態載入避免 SDK 直接捆綁 react-dom\n const mod: ReactDOMClient = await import(/* @vite-ignore */ 'react-dom/client');\n return mod;\n }\n\n /** 建構 Plugin 元件樹:Provider 包裹所有 components */\n function buildTree(ctx: CyodaaPluginContext, components: readonly ComponentType[]): React.ReactNode {\n const children = components.map((Comp, i) =>\n createElement(Comp, { key: i })\n );\n\n const fragment = createElement(Fragment, null, ...children);\n\n return createElement(\n CyodaaSDKProvider,\n { value: ctx, children: fragment },\n );\n }\n\n /** mount — PluginRenderer 呼叫此函式掛載 Plugin */\n function mount(container: HTMLElement, ctx: CyodaaPluginContext): void {\n if (instance.mounted) {\n // 防止重複掛載:先卸載再重新掛載\n unmount();\n }\n\n // 同步啟動(createRoot 不需要 await)\n // 但因為 react-dom 是動態 import,改用 async IIFE\n void (async () => {\n try {\n const reactDOM = await loadReactDOM();\n const root = reactDOM.createRoot(container);\n\n instance.root = root;\n instance.mounted = true;\n\n // 觸發 onInit 回調\n if (config.onInit) {\n await Promise.resolve(config.onInit(ctx));\n }\n\n // 渲染元件樹\n const tree = buildTree(ctx, config.components);\n root.render(tree);\n } catch (err) {\n // 渲染失敗時顯示錯誤訊息在容器中\n const message = err instanceof Error ? err.message : 'Plugin 載入失敗';\n container.textContent = `[plugin-sdk] ${message}`;\n }\n })();\n }\n\n /** unmount — PluginRenderer 呼叫此函式卸載 Plugin */\n function unmount(): void {\n if (!instance.mounted) return;\n\n // 觸發 onDestroy 回調\n if (config.onDestroy) {\n try {\n void Promise.resolve(config.onDestroy());\n } catch {\n // 忽略 onDestroy 錯誤,確保卸載流程不中斷\n }\n }\n\n // 卸載 React 根節點\n try {\n instance.root?.unmount();\n } catch {\n // 忽略 unmount 錯誤\n }\n\n instance.root = null;\n instance.mounted = false;\n }\n\n return { mount, unmount };\n}\n","/**\n * SDK 內部 React Context\n *\n * 將宿主注入的 CyodaaPluginContext 透過 React Context 傳遞給\n * Plugin 元件樹,供各 hook 消費。\n */\n\nimport { createContext, useContext, createElement } from 'react';\nimport type { ReactNode } from 'react';\nimport type { CyodaaPluginContext } from './types';\n\n// ============================================================\n// Context\n// ============================================================\n\n/** SDK 內部用 Context — 不直接對外導出 */\nconst CyodaaSDKContext = createContext<CyodaaPluginContext | null>(null);\n\nCyodaaSDKContext.displayName = 'CyodaaSDKContext';\n\n// ============================================================\n// Provider\n// ============================================================\n\ninterface ProviderProps {\n readonly value: CyodaaPluginContext;\n readonly children: ReactNode;\n}\n\n/** 將平台 Context 注入 React 元件樹 */\nexport function CyodaaSDKProvider({ value, children }: ProviderProps) {\n return createElement(CyodaaSDKContext.Provider, { value }, children);\n}\n\n// ============================================================\n// 內部 Hook\n// ============================================================\n\n/**\n * 內部 hook:讀取原始 CyodaaPluginContext。\n * 僅供 SDK 內部 hook 使用,不對外導出。\n *\n * @throws {Error} 若在 CyodaaSDKProvider 外使用\n */\nexport function useSDKContext(): CyodaaPluginContext {\n const ctx = useContext(CyodaaSDKContext);\n\n if (ctx === null) {\n throw new Error(\n '[plugin-sdk] useSDKContext 必須在 CyodaaSDKProvider 內使用。' +\n '請確認 Plugin 已透過 registerPlugin() 正確註冊。'\n );\n }\n\n return ctx;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSA,IAAAA,gBAA4D;;;ACF5D,mBAAyD;AASzD,IAAM,uBAAmB,4BAA0C,IAAI;AAEvE,iBAAiB,cAAc;AAYxB,SAAS,kBAAkB,EAAE,OAAO,SAAS,GAAkB;AACpE,aAAO,4BAAc,iBAAiB,UAAU,EAAE,MAAM,GAAG,QAAQ;AACrE;;;ADwBO,SAAS,eAAe,QAAoC;AACjE,QAAM,WAA2B;AAAA,IAC/B;AAAA,IACA,MAAM;AAAA,IACN,SAAS;AAAA,EACX;AAGA,iBAAe,eAAwC;AAErD,UAAM,MAAsB,MAAM;AAAA;AAAA,MAA0B;AAAA,IAAkB;AAC9E,WAAO;AAAA,EACT;AAGA,WAAS,UAAU,KAA0B,YAAuD;AAClG,UAAM,WAAW,WAAW;AAAA,MAAI,CAAC,MAAM,UACrC,6BAAc,MAAM,EAAE,KAAK,EAAE,CAAC;AAAA,IAChC;AAEA,UAAM,eAAW,6BAAc,wBAAU,MAAM,GAAG,QAAQ;AAE1D,eAAO;AAAA,MACL;AAAA,MACA,EAAE,OAAO,KAAK,UAAU,SAAS;AAAA,IACnC;AAAA,EACF;AAGA,WAAS,MAAM,WAAwB,KAAgC;AACrE,QAAI,SAAS,SAAS;AAEpB,cAAQ;AAAA,IACV;AAIA,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,WAAW,MAAM,aAAa;AACpC,cAAM,OAAO,SAAS,WAAW,SAAS;AAE1C,iBAAS,OAAO;AAChB,iBAAS,UAAU;AAGnB,YAAI,OAAO,QAAQ;AACjB,gBAAM,QAAQ,QAAQ,OAAO,OAAO,GAAG,CAAC;AAAA,QAC1C;AAGA,cAAM,OAAO,UAAU,KAAK,OAAO,UAAU;AAC7C,aAAK,OAAO,IAAI;AAAA,MAClB,SAAS,KAAK;AAEZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,kBAAU,cAAc,gBAAgB,OAAO;AAAA,MACjD;AAAA,IACF,GAAG;AAAA,EACL;AAGA,WAAS,UAAgB;AACvB,QAAI,CAAC,SAAS,QAAS;AAGvB,QAAI,OAAO,WAAW;AACpB,UAAI;AACF,aAAK,QAAQ,QAAQ,OAAO,UAAU,CAAC;AAAA,MACzC,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI;AACF,eAAS,MAAM,QAAQ;AAAA,IACzB,QAAQ;AAAA,IAER;AAEA,aAAS,OAAO;AAChB,aAAS,UAAU;AAAA,EACrB;AAEA,SAAO,EAAE,OAAO,QAAQ;AAC1B;","names":["import_react"]}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @cyodaa/plugin-sdk 型別定義
3
+ *
4
+ * 與 PluginRenderer.tsx 定義的介面完全一致,
5
+ * 確保 Plugin 在 Shadow DOM 沙箱內正確運作。
6
+ */
7
+ /** 由宿主 PluginRenderer 注入的唯讀 Context */
8
+ interface CyodaaPluginContext {
9
+ readonly tenantId: string;
10
+ readonly userId: number | null;
11
+ readonly theme: 'light' | 'dark';
12
+ readonly tier: string;
13
+ }
14
+ /** 使用者資訊(由 SDK 從 CyodaaPluginContext 轉換) */
15
+ interface CyodaaUser {
16
+ readonly id: string;
17
+ readonly displayName: string;
18
+ readonly email: string;
19
+ readonly role: string;
20
+ }
21
+ /** 租戶資訊 */
22
+ interface CyodaaTenant {
23
+ readonly id: string;
24
+ readonly name: string;
25
+ }
26
+ /** 平台版本資訊 */
27
+ interface CyodaaPlatform {
28
+ readonly version: string;
29
+ }
30
+ /** 提供給 Plugin 開發者的豐富 Context */
31
+ interface CyodaaContext {
32
+ readonly user: CyodaaUser | null;
33
+ readonly theme: 'light' | 'dark';
34
+ readonly tenant: CyodaaTenant;
35
+ readonly locale: string;
36
+ readonly platform: CyodaaPlatform;
37
+ }
38
+ /** Plugin 必須導出的 mount/unmount 介面 */
39
+ interface PluginModule {
40
+ mount: (container: HTMLElement, ctx: CyodaaPluginContext) => void;
41
+ unmount: () => void;
42
+ }
43
+ /** React 元件型別(避免直接依賴 React 型別) */
44
+ type PluginComponent = React.ComponentType;
45
+ /** registerPlugin() 的設定參數 */
46
+ interface PluginConfig {
47
+ /** Plugin 唯一識別碼 */
48
+ readonly id: string;
49
+ /** 要渲染的 React 元件列表(依序渲染) */
50
+ readonly components: readonly PluginComponent[];
51
+ /** Plugin 初始化回調(mount 時觸發) */
52
+ readonly onInit?: (ctx: CyodaaPluginContext) => void | Promise<void>;
53
+ /** Plugin 銷毀回調(unmount 時觸發) */
54
+ readonly onDestroy?: () => void | Promise<void>;
55
+ }
56
+
57
+ /**
58
+ * registerPlugin — Plugin 註冊入口
59
+ *
60
+ * 接收 PluginConfig,回傳符合 PluginModule 合約的 { mount, unmount },
61
+ * 供 PluginRenderer 的 Shadow DOM 沙箱呼叫。
62
+ *
63
+ * react-dom 採用動態 import,避免 SDK 直接捆綁 react-dom。
64
+ */
65
+
66
+ /**
67
+ * 註冊 Plugin,回傳 PluginModule。
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { registerPlugin } from '@cyodaa/plugin-sdk';
72
+ * import { App } from './App';
73
+ *
74
+ * const plugin = registerPlugin({
75
+ * id: 'my-plugin',
76
+ * components: [App],
77
+ * });
78
+ *
79
+ * export const { mount, unmount } = plugin;
80
+ * ```
81
+ */
82
+ declare function registerPlugin(config: PluginConfig): PluginModule;
83
+
84
+ export { type CyodaaContext, type CyodaaPlatform, type CyodaaPluginContext, type CyodaaTenant, type CyodaaUser, type PluginComponent, type PluginConfig, type PluginModule, registerPlugin };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @cyodaa/plugin-sdk 型別定義
3
+ *
4
+ * 與 PluginRenderer.tsx 定義的介面完全一致,
5
+ * 確保 Plugin 在 Shadow DOM 沙箱內正確運作。
6
+ */
7
+ /** 由宿主 PluginRenderer 注入的唯讀 Context */
8
+ interface CyodaaPluginContext {
9
+ readonly tenantId: string;
10
+ readonly userId: number | null;
11
+ readonly theme: 'light' | 'dark';
12
+ readonly tier: string;
13
+ }
14
+ /** 使用者資訊(由 SDK 從 CyodaaPluginContext 轉換) */
15
+ interface CyodaaUser {
16
+ readonly id: string;
17
+ readonly displayName: string;
18
+ readonly email: string;
19
+ readonly role: string;
20
+ }
21
+ /** 租戶資訊 */
22
+ interface CyodaaTenant {
23
+ readonly id: string;
24
+ readonly name: string;
25
+ }
26
+ /** 平台版本資訊 */
27
+ interface CyodaaPlatform {
28
+ readonly version: string;
29
+ }
30
+ /** 提供給 Plugin 開發者的豐富 Context */
31
+ interface CyodaaContext {
32
+ readonly user: CyodaaUser | null;
33
+ readonly theme: 'light' | 'dark';
34
+ readonly tenant: CyodaaTenant;
35
+ readonly locale: string;
36
+ readonly platform: CyodaaPlatform;
37
+ }
38
+ /** Plugin 必須導出的 mount/unmount 介面 */
39
+ interface PluginModule {
40
+ mount: (container: HTMLElement, ctx: CyodaaPluginContext) => void;
41
+ unmount: () => void;
42
+ }
43
+ /** React 元件型別(避免直接依賴 React 型別) */
44
+ type PluginComponent = React.ComponentType;
45
+ /** registerPlugin() 的設定參數 */
46
+ interface PluginConfig {
47
+ /** Plugin 唯一識別碼 */
48
+ readonly id: string;
49
+ /** 要渲染的 React 元件列表(依序渲染) */
50
+ readonly components: readonly PluginComponent[];
51
+ /** Plugin 初始化回調(mount 時觸發) */
52
+ readonly onInit?: (ctx: CyodaaPluginContext) => void | Promise<void>;
53
+ /** Plugin 銷毀回調(unmount 時觸發) */
54
+ readonly onDestroy?: () => void | Promise<void>;
55
+ }
56
+
57
+ /**
58
+ * registerPlugin — Plugin 註冊入口
59
+ *
60
+ * 接收 PluginConfig,回傳符合 PluginModule 合約的 { mount, unmount },
61
+ * 供 PluginRenderer 的 Shadow DOM 沙箱呼叫。
62
+ *
63
+ * react-dom 採用動態 import,避免 SDK 直接捆綁 react-dom。
64
+ */
65
+
66
+ /**
67
+ * 註冊 Plugin,回傳 PluginModule。
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { registerPlugin } from '@cyodaa/plugin-sdk';
72
+ * import { App } from './App';
73
+ *
74
+ * const plugin = registerPlugin({
75
+ * id: 'my-plugin',
76
+ * components: [App],
77
+ * });
78
+ *
79
+ * export const { mount, unmount } = plugin;
80
+ * ```
81
+ */
82
+ declare function registerPlugin(config: PluginConfig): PluginModule;
83
+
84
+ export { type CyodaaContext, type CyodaaPlatform, type CyodaaPluginContext, type CyodaaTenant, type CyodaaUser, type PluginComponent, type PluginConfig, type PluginModule, registerPlugin };
package/dist/index.js ADDED
@@ -0,0 +1,77 @@
1
+ // src/register.ts
2
+ import { createElement as createElement2, Fragment } from "react";
3
+
4
+ // src/context.ts
5
+ import { createContext, useContext, createElement } from "react";
6
+ var CyodaaSDKContext = createContext(null);
7
+ CyodaaSDKContext.displayName = "CyodaaSDKContext";
8
+ function CyodaaSDKProvider({ value, children }) {
9
+ return createElement(CyodaaSDKContext.Provider, { value }, children);
10
+ }
11
+
12
+ // src/register.ts
13
+ function registerPlugin(config) {
14
+ const instance = {
15
+ config,
16
+ root: null,
17
+ mounted: false
18
+ };
19
+ async function loadReactDOM() {
20
+ const mod = await import(
21
+ /* @vite-ignore */
22
+ "react-dom/client"
23
+ );
24
+ return mod;
25
+ }
26
+ function buildTree(ctx, components) {
27
+ const children = components.map(
28
+ (Comp, i) => createElement2(Comp, { key: i })
29
+ );
30
+ const fragment = createElement2(Fragment, null, ...children);
31
+ return createElement2(
32
+ CyodaaSDKProvider,
33
+ { value: ctx, children: fragment }
34
+ );
35
+ }
36
+ function mount(container, ctx) {
37
+ if (instance.mounted) {
38
+ unmount();
39
+ }
40
+ void (async () => {
41
+ try {
42
+ const reactDOM = await loadReactDOM();
43
+ const root = reactDOM.createRoot(container);
44
+ instance.root = root;
45
+ instance.mounted = true;
46
+ if (config.onInit) {
47
+ await Promise.resolve(config.onInit(ctx));
48
+ }
49
+ const tree = buildTree(ctx, config.components);
50
+ root.render(tree);
51
+ } catch (err) {
52
+ const message = err instanceof Error ? err.message : "Plugin \u8F09\u5165\u5931\u6557";
53
+ container.textContent = `[plugin-sdk] ${message}`;
54
+ }
55
+ })();
56
+ }
57
+ function unmount() {
58
+ if (!instance.mounted) return;
59
+ if (config.onDestroy) {
60
+ try {
61
+ void Promise.resolve(config.onDestroy());
62
+ } catch {
63
+ }
64
+ }
65
+ try {
66
+ instance.root?.unmount();
67
+ } catch {
68
+ }
69
+ instance.root = null;
70
+ instance.mounted = false;
71
+ }
72
+ return { mount, unmount };
73
+ }
74
+ export {
75
+ registerPlugin
76
+ };
77
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/register.ts","../src/context.ts"],"sourcesContent":["/**\n * registerPlugin — Plugin 註冊入口\n *\n * 接收 PluginConfig,回傳符合 PluginModule 合約的 { mount, unmount },\n * 供 PluginRenderer 的 Shadow DOM 沙箱呼叫。\n *\n * react-dom 採用動態 import,避免 SDK 直接捆綁 react-dom。\n */\n\nimport { createElement, Fragment, type ComponentType } from 'react';\nimport type { CyodaaPluginContext, PluginConfig, PluginModule } from './types';\nimport { CyodaaSDKProvider } from './context';\n\n// ============================================================\n// react-dom/client 動態載入型別\n// ============================================================\n\ninterface ReactDOMRoot {\n render: (element: React.ReactNode) => void;\n unmount: () => void;\n}\n\ninterface ReactDOMClient {\n createRoot: (container: HTMLElement) => ReactDOMRoot;\n}\n\n// ============================================================\n// 內部狀態(per-plugin instance)\n// ============================================================\n\ninterface PluginInstance {\n readonly config: PluginConfig;\n root: ReactDOMRoot | null;\n mounted: boolean;\n}\n\n// ============================================================\n// registerPlugin\n// ============================================================\n\n/**\n * 註冊 Plugin,回傳 PluginModule。\n *\n * @example\n * ```ts\n * import { registerPlugin } from '@cyodaa/plugin-sdk';\n * import { App } from './App';\n *\n * const plugin = registerPlugin({\n * id: 'my-plugin',\n * components: [App],\n * });\n *\n * export const { mount, unmount } = plugin;\n * ```\n */\nexport function registerPlugin(config: PluginConfig): PluginModule {\n const instance: PluginInstance = {\n config,\n root: null,\n mounted: false,\n };\n\n /** 動態載入 react-dom/client */\n async function loadReactDOM(): Promise<ReactDOMClient> {\n // eslint-disable-next-line @typescript-eslint/no-require-imports -- 動態載入避免 SDK 直接捆綁 react-dom\n const mod: ReactDOMClient = await import(/* @vite-ignore */ 'react-dom/client');\n return mod;\n }\n\n /** 建構 Plugin 元件樹:Provider 包裹所有 components */\n function buildTree(ctx: CyodaaPluginContext, components: readonly ComponentType[]): React.ReactNode {\n const children = components.map((Comp, i) =>\n createElement(Comp, { key: i })\n );\n\n const fragment = createElement(Fragment, null, ...children);\n\n return createElement(\n CyodaaSDKProvider,\n { value: ctx, children: fragment },\n );\n }\n\n /** mount — PluginRenderer 呼叫此函式掛載 Plugin */\n function mount(container: HTMLElement, ctx: CyodaaPluginContext): void {\n if (instance.mounted) {\n // 防止重複掛載:先卸載再重新掛載\n unmount();\n }\n\n // 同步啟動(createRoot 不需要 await)\n // 但因為 react-dom 是動態 import,改用 async IIFE\n void (async () => {\n try {\n const reactDOM = await loadReactDOM();\n const root = reactDOM.createRoot(container);\n\n instance.root = root;\n instance.mounted = true;\n\n // 觸發 onInit 回調\n if (config.onInit) {\n await Promise.resolve(config.onInit(ctx));\n }\n\n // 渲染元件樹\n const tree = buildTree(ctx, config.components);\n root.render(tree);\n } catch (err) {\n // 渲染失敗時顯示錯誤訊息在容器中\n const message = err instanceof Error ? err.message : 'Plugin 載入失敗';\n container.textContent = `[plugin-sdk] ${message}`;\n }\n })();\n }\n\n /** unmount — PluginRenderer 呼叫此函式卸載 Plugin */\n function unmount(): void {\n if (!instance.mounted) return;\n\n // 觸發 onDestroy 回調\n if (config.onDestroy) {\n try {\n void Promise.resolve(config.onDestroy());\n } catch {\n // 忽略 onDestroy 錯誤,確保卸載流程不中斷\n }\n }\n\n // 卸載 React 根節點\n try {\n instance.root?.unmount();\n } catch {\n // 忽略 unmount 錯誤\n }\n\n instance.root = null;\n instance.mounted = false;\n }\n\n return { mount, unmount };\n}\n","/**\n * SDK 內部 React Context\n *\n * 將宿主注入的 CyodaaPluginContext 透過 React Context 傳遞給\n * Plugin 元件樹,供各 hook 消費。\n */\n\nimport { createContext, useContext, createElement } from 'react';\nimport type { ReactNode } from 'react';\nimport type { CyodaaPluginContext } from './types';\n\n// ============================================================\n// Context\n// ============================================================\n\n/** SDK 內部用 Context — 不直接對外導出 */\nconst CyodaaSDKContext = createContext<CyodaaPluginContext | null>(null);\n\nCyodaaSDKContext.displayName = 'CyodaaSDKContext';\n\n// ============================================================\n// Provider\n// ============================================================\n\ninterface ProviderProps {\n readonly value: CyodaaPluginContext;\n readonly children: ReactNode;\n}\n\n/** 將平台 Context 注入 React 元件樹 */\nexport function CyodaaSDKProvider({ value, children }: ProviderProps) {\n return createElement(CyodaaSDKContext.Provider, { value }, children);\n}\n\n// ============================================================\n// 內部 Hook\n// ============================================================\n\n/**\n * 內部 hook:讀取原始 CyodaaPluginContext。\n * 僅供 SDK 內部 hook 使用,不對外導出。\n *\n * @throws {Error} 若在 CyodaaSDKProvider 外使用\n */\nexport function useSDKContext(): CyodaaPluginContext {\n const ctx = useContext(CyodaaSDKContext);\n\n if (ctx === null) {\n throw new Error(\n '[plugin-sdk] useSDKContext 必須在 CyodaaSDKProvider 內使用。' +\n '請確認 Plugin 已透過 registerPlugin() 正確註冊。'\n );\n }\n\n return ctx;\n}\n"],"mappings":";AASA,SAAS,iBAAAA,gBAAe,gBAAoC;;;ACF5D,SAAS,eAAe,YAAY,qBAAqB;AASzD,IAAM,mBAAmB,cAA0C,IAAI;AAEvE,iBAAiB,cAAc;AAYxB,SAAS,kBAAkB,EAAE,OAAO,SAAS,GAAkB;AACpE,SAAO,cAAc,iBAAiB,UAAU,EAAE,MAAM,GAAG,QAAQ;AACrE;;;ADwBO,SAAS,eAAe,QAAoC;AACjE,QAAM,WAA2B;AAAA,IAC/B;AAAA,IACA,MAAM;AAAA,IACN,SAAS;AAAA,EACX;AAGA,iBAAe,eAAwC;AAErD,UAAM,MAAsB,MAAM;AAAA;AAAA,MAA0B;AAAA,IAAkB;AAC9E,WAAO;AAAA,EACT;AAGA,WAAS,UAAU,KAA0B,YAAuD;AAClG,UAAM,WAAW,WAAW;AAAA,MAAI,CAAC,MAAM,MACrCC,eAAc,MAAM,EAAE,KAAK,EAAE,CAAC;AAAA,IAChC;AAEA,UAAM,WAAWA,eAAc,UAAU,MAAM,GAAG,QAAQ;AAE1D,WAAOA;AAAA,MACL;AAAA,MACA,EAAE,OAAO,KAAK,UAAU,SAAS;AAAA,IACnC;AAAA,EACF;AAGA,WAAS,MAAM,WAAwB,KAAgC;AACrE,QAAI,SAAS,SAAS;AAEpB,cAAQ;AAAA,IACV;AAIA,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,WAAW,MAAM,aAAa;AACpC,cAAM,OAAO,SAAS,WAAW,SAAS;AAE1C,iBAAS,OAAO;AAChB,iBAAS,UAAU;AAGnB,YAAI,OAAO,QAAQ;AACjB,gBAAM,QAAQ,QAAQ,OAAO,OAAO,GAAG,CAAC;AAAA,QAC1C;AAGA,cAAM,OAAO,UAAU,KAAK,OAAO,UAAU;AAC7C,aAAK,OAAO,IAAI;AAAA,MAClB,SAAS,KAAK;AAEZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,kBAAU,cAAc,gBAAgB,OAAO;AAAA,MACjD;AAAA,IACF,GAAG;AAAA,EACL;AAGA,WAAS,UAAgB;AACvB,QAAI,CAAC,SAAS,QAAS;AAGvB,QAAI,OAAO,WAAW;AACpB,UAAI;AACF,aAAK,QAAQ,QAAQ,OAAO,UAAU,CAAC;AAAA,MACzC,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI;AACF,eAAS,MAAM,QAAQ;AAAA,IACzB,QAAQ;AAAA,IAER;AAEA,aAAS,OAAO;AAChB,aAAS,UAAU;AAAA,EACrB;AAEA,SAAO,EAAE,OAAO,QAAQ;AAC1B;","names":["createElement","createElement"]}
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ useCyodaaSDK: () => useCyodaaSDK,
24
+ usePluginAPI: () => usePluginAPI,
25
+ usePluginEvents: () => usePluginEvents,
26
+ usePluginStorage: () => usePluginStorage
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/react/useCyodaaSDK.ts
31
+ var import_react2 = require("react");
32
+
33
+ // src/context.ts
34
+ var import_react = require("react");
35
+ var CyodaaSDKContext = (0, import_react.createContext)(null);
36
+ CyodaaSDKContext.displayName = "CyodaaSDKContext";
37
+ function useSDKContext() {
38
+ const ctx = (0, import_react.useContext)(CyodaaSDKContext);
39
+ if (ctx === null) {
40
+ throw new Error(
41
+ "[plugin-sdk] useSDKContext \u5FC5\u9808\u5728 CyodaaSDKProvider \u5167\u4F7F\u7528\u3002\u8ACB\u78BA\u8A8D Plugin \u5DF2\u900F\u904E registerPlugin() \u6B63\u78BA\u8A3B\u518A\u3002"
42
+ );
43
+ }
44
+ return ctx;
45
+ }
46
+
47
+ // src/react/useCyodaaSDK.ts
48
+ var SDK_VERSION = "0.1.0";
49
+ var DEFAULT_LOCALE = "zh-TW";
50
+ function useCyodaaSDK() {
51
+ const ctx = useSDKContext();
52
+ return (0, import_react2.useMemo)(() => {
53
+ const user = ctx.userId !== null ? {
54
+ id: String(ctx.userId),
55
+ displayName: "",
56
+ email: "",
57
+ role: ""
58
+ } : null;
59
+ return {
60
+ user,
61
+ theme: ctx.theme,
62
+ tenant: {
63
+ id: ctx.tenantId,
64
+ name: ctx.tenantId
65
+ },
66
+ locale: DEFAULT_LOCALE,
67
+ platform: {
68
+ version: SDK_VERSION
69
+ }
70
+ };
71
+ }, [ctx.tenantId, ctx.userId, ctx.theme]);
72
+ }
73
+
74
+ // src/react/usePluginAPI.ts
75
+ var import_react3 = require("react");
76
+ async function handleResponse(response) {
77
+ if (!response.ok) {
78
+ const text = await response.text().catch(() => "");
79
+ throw new Error(
80
+ `[plugin-sdk] API \u8ACB\u6C42\u5931\u6557\uFF1A${response.status} ${response.statusText}` + (text ? ` \u2014 ${text}` : "")
81
+ );
82
+ }
83
+ return response.json();
84
+ }
85
+ function getHeaders() {
86
+ return {
87
+ "Content-Type": "application/json",
88
+ "Accept": "application/json"
89
+ };
90
+ }
91
+ function usePluginAPI() {
92
+ const call = (0, import_react3.useCallback)(async (service, method, params) => {
93
+ const payload = { service, method, params };
94
+ const response = await fetch("/api/v1/plugins/rpc", {
95
+ method: "POST",
96
+ headers: getHeaders(),
97
+ body: JSON.stringify(payload)
98
+ });
99
+ return handleResponse(response);
100
+ }, []);
101
+ const get = (0, import_react3.useCallback)(async (path) => {
102
+ const response = await fetch(path, {
103
+ method: "GET",
104
+ headers: getHeaders()
105
+ });
106
+ return handleResponse(response);
107
+ }, []);
108
+ const post = (0, import_react3.useCallback)(async (path, body) => {
109
+ const response = await fetch(path, {
110
+ method: "POST",
111
+ headers: getHeaders(),
112
+ body: body !== void 0 ? JSON.stringify(body) : void 0
113
+ });
114
+ return handleResponse(response);
115
+ }, []);
116
+ return (0, import_react3.useMemo)(() => ({ call, get, post }), [call, get, post]);
117
+ }
118
+
119
+ // src/react/usePluginEvents.ts
120
+ var import_react4 = require("react");
121
+ function prefixedEventName(event) {
122
+ return `cyodaa:${event}`;
123
+ }
124
+ function usePluginEvents() {
125
+ const on = (0, import_react4.useCallback)((event, handler) => {
126
+ const eventName = prefixedEventName(event);
127
+ const listener = (e) => {
128
+ const customEvent = e;
129
+ handler(customEvent.detail);
130
+ };
131
+ window.addEventListener(eventName, listener);
132
+ return () => {
133
+ window.removeEventListener(eventName, listener);
134
+ };
135
+ }, []);
136
+ const emit = (0, import_react4.useCallback)((event, data) => {
137
+ const eventName = prefixedEventName(event);
138
+ const customEvent = new CustomEvent(eventName, {
139
+ detail: data,
140
+ bubbles: false,
141
+ cancelable: false
142
+ });
143
+ window.dispatchEvent(customEvent);
144
+ }, []);
145
+ return (0, import_react4.useMemo)(() => ({ on, emit }), [on, emit]);
146
+ }
147
+
148
+ // src/react/usePluginStorage.ts
149
+ var import_react5 = require("react");
150
+ function usePluginStorage(pluginId) {
151
+ const fullKey = (0, import_react5.useCallback)(
152
+ (key) => `plugin:${pluginId}:${key}`,
153
+ [pluginId]
154
+ );
155
+ const prefix = (0, import_react5.useMemo)(() => `plugin:${pluginId}:`, [pluginId]);
156
+ const get = (0, import_react5.useCallback)((key) => {
157
+ try {
158
+ const raw = localStorage.getItem(fullKey(key));
159
+ if (raw === null) return null;
160
+ return JSON.parse(raw);
161
+ } catch {
162
+ return null;
163
+ }
164
+ }, [fullKey]);
165
+ const set = (0, import_react5.useCallback)((key, value) => {
166
+ try {
167
+ const serialized = JSON.stringify(value);
168
+ localStorage.setItem(fullKey(key), serialized);
169
+ } catch {
170
+ }
171
+ }, [fullKey]);
172
+ const deleteKey = (0, import_react5.useCallback)((key) => {
173
+ localStorage.removeItem(fullKey(key));
174
+ }, [fullKey]);
175
+ const keys = (0, import_react5.useCallback)(() => {
176
+ const result = [];
177
+ const len = localStorage.length;
178
+ for (let i = 0; i < len; i++) {
179
+ const k = localStorage.key(i);
180
+ if (k !== null && k.startsWith(prefix)) {
181
+ result.push(k.slice(prefix.length));
182
+ }
183
+ }
184
+ return result;
185
+ }, [prefix]);
186
+ const clear = (0, import_react5.useCallback)(() => {
187
+ const keysToRemove = [];
188
+ const len = localStorage.length;
189
+ for (let i = 0; i < len; i++) {
190
+ const k = localStorage.key(i);
191
+ if (k !== null && k.startsWith(prefix)) {
192
+ keysToRemove.push(k);
193
+ }
194
+ }
195
+ for (const k of keysToRemove) {
196
+ localStorage.removeItem(k);
197
+ }
198
+ }, [prefix]);
199
+ return (0, import_react5.useMemo)(
200
+ () => ({ get, set, delete: deleteKey, keys, clear }),
201
+ [get, set, deleteKey, keys, clear]
202
+ );
203
+ }
204
+ // Annotate the CommonJS export names for ESM import in node:
205
+ 0 && (module.exports = {
206
+ useCyodaaSDK,
207
+ usePluginAPI,
208
+ usePluginEvents,
209
+ usePluginStorage
210
+ });
211
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.ts","../../src/react/useCyodaaSDK.ts","../../src/context.ts","../../src/react/usePluginAPI.ts","../../src/react/usePluginEvents.ts","../../src/react/usePluginStorage.ts"],"sourcesContent":["/**\n * @cyodaa/plugin-sdk/react — React Hooks 匯出\n *\n * 提供 Plugin 開發者在 React 元件中使用的所有 hooks。\n */\n\nexport { useCyodaaSDK } from './useCyodaaSDK';\nexport { usePluginAPI } from './usePluginAPI';\nexport { usePluginEvents } from './usePluginEvents';\nexport { usePluginStorage } from './usePluginStorage';\n","/**\n * useCyodaaSDK — 取得豐富的平台 Context\n *\n * 將宿主注入的 CyodaaPluginContext 轉換為\n * 開發者友善的 CyodaaContext 格式。\n */\n\nimport { useMemo } from 'react';\nimport { useSDKContext } from '../context';\nimport type { CyodaaContext, CyodaaUser } from '../types';\n\n/** SDK 版本(與 package.json 同步) */\nconst SDK_VERSION = '0.1.0';\n\n/** 預設語系 */\nconst DEFAULT_LOCALE = 'zh-TW';\n\n/**\n * 取得豐富的 Cyodaa 平台 Context。\n *\n * @example\n * ```tsx\n * import { useCyodaaSDK } from '@cyodaa/plugin-sdk/react';\n *\n * function MyComponent() {\n * const { user, theme, tenant } = useCyodaaSDK();\n * return <div>Hi, {user?.displayName ?? 'Guest'}</div>;\n * }\n * ```\n */\nexport function useCyodaaSDK(): CyodaaContext {\n const ctx = useSDKContext();\n\n return useMemo<CyodaaContext>(() => {\n // 將 userId 轉換為 CyodaaUser(null-safe)\n const user: CyodaaUser | null = ctx.userId !== null\n ? {\n id: String(ctx.userId),\n displayName: '',\n email: '',\n role: '',\n }\n : null;\n\n return {\n user,\n theme: ctx.theme,\n tenant: {\n id: ctx.tenantId,\n name: ctx.tenantId,\n },\n locale: DEFAULT_LOCALE,\n platform: {\n version: SDK_VERSION,\n },\n };\n }, [ctx.tenantId, ctx.userId, ctx.theme]);\n}\n","/**\n * SDK 內部 React Context\n *\n * 將宿主注入的 CyodaaPluginContext 透過 React Context 傳遞給\n * Plugin 元件樹,供各 hook 消費。\n */\n\nimport { createContext, useContext, createElement } from 'react';\nimport type { ReactNode } from 'react';\nimport type { CyodaaPluginContext } from './types';\n\n// ============================================================\n// Context\n// ============================================================\n\n/** SDK 內部用 Context — 不直接對外導出 */\nconst CyodaaSDKContext = createContext<CyodaaPluginContext | null>(null);\n\nCyodaaSDKContext.displayName = 'CyodaaSDKContext';\n\n// ============================================================\n// Provider\n// ============================================================\n\ninterface ProviderProps {\n readonly value: CyodaaPluginContext;\n readonly children: ReactNode;\n}\n\n/** 將平台 Context 注入 React 元件樹 */\nexport function CyodaaSDKProvider({ value, children }: ProviderProps) {\n return createElement(CyodaaSDKContext.Provider, { value }, children);\n}\n\n// ============================================================\n// 內部 Hook\n// ============================================================\n\n/**\n * 內部 hook:讀取原始 CyodaaPluginContext。\n * 僅供 SDK 內部 hook 使用,不對外導出。\n *\n * @throws {Error} 若在 CyodaaSDKProvider 外使用\n */\nexport function useSDKContext(): CyodaaPluginContext {\n const ctx = useContext(CyodaaSDKContext);\n\n if (ctx === null) {\n throw new Error(\n '[plugin-sdk] useSDKContext 必須在 CyodaaSDKProvider 內使用。' +\n '請確認 Plugin 已透過 registerPlugin() 正確註冊。'\n );\n }\n\n return ctx;\n}\n","/**\n * usePluginAPI — Plugin HTTP 通訊 hook\n *\n * 提供 RPC 呼叫與通用 HTTP 方法,\n * 全部使用相對 URL(same-origin)。\n */\n\nimport { useCallback, useMemo } from 'react';\n\n// ============================================================\n// 型別\n// ============================================================\n\n/** RPC 呼叫的請求格式 */\ninterface RPCPayload {\n readonly service: string;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\n/** API hook 回傳介面 */\ninterface PluginAPI {\n /** 呼叫後端 RPC 端點 */\n readonly call: <T = unknown>(\n service: string,\n method: string,\n params?: Record<string, unknown>,\n ) => Promise<T>;\n\n /** GET 請求 */\n readonly get: <T = unknown>(path: string) => Promise<T>;\n\n /** POST 請求 */\n readonly post: <T = unknown>(\n path: string,\n body?: unknown,\n ) => Promise<T>;\n}\n\n// ============================================================\n// 內部工具\n// ============================================================\n\n/** 統一 fetch 錯誤處理 */\nasync function handleResponse<T>(response: Response): Promise<T> {\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(\n `[plugin-sdk] API 請求失敗:${response.status} ${response.statusText}` +\n (text ? ` — ${text}` : ''),\n );\n }\n\n return response.json() as Promise<T>;\n}\n\n/** 取得通用 headers */\nfunction getHeaders(): HeadersInit {\n return {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n };\n}\n\n// ============================================================\n// Hook\n// ============================================================\n\n/**\n * 取得 Plugin API 呼叫工具。\n *\n * @example\n * ```tsx\n * import { usePluginAPI } from '@cyodaa/plugin-sdk/react';\n *\n * function MyComponent() {\n * const api = usePluginAPI();\n *\n * const handleClick = async () => {\n * const result = await api.call('kpi', 'getSummary', { period: '2026-Q1' });\n * };\n * }\n * ```\n */\nexport function usePluginAPI(): PluginAPI {\n /** RPC 呼叫:POST /api/v1/plugins/rpc */\n const call = useCallback(async <T = unknown>(\n service: string,\n method: string,\n params?: Record<string, unknown>,\n ): Promise<T> => {\n const payload: RPCPayload = { service, method, params };\n\n const response = await fetch('/api/v1/plugins/rpc', {\n method: 'POST',\n headers: getHeaders(),\n body: JSON.stringify(payload),\n });\n\n return handleResponse<T>(response);\n }, []);\n\n /** GET 請求 */\n const get = useCallback(async <T = unknown>(path: string): Promise<T> => {\n const response = await fetch(path, {\n method: 'GET',\n headers: getHeaders(),\n });\n\n return handleResponse<T>(response);\n }, []);\n\n /** POST 請求 */\n const post = useCallback(async <T = unknown>(\n path: string,\n body?: unknown,\n ): Promise<T> => {\n const response = await fetch(path, {\n method: 'POST',\n headers: getHeaders(),\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n return handleResponse<T>(response);\n }, []);\n\n return useMemo<PluginAPI>(() => ({ call, get, post }), [call, get, post]);\n}\n","/**\n * usePluginEvents — Plugin 事件通訊 hook\n *\n * 透過 window 上的 CustomEvent 實現\n * Plugin 與宿主平台之間的事件訂閱/發布。\n *\n * 事件名稱統一加上 `cyodaa:` 前綴避免衝突。\n */\n\nimport { useCallback, useMemo } from 'react';\n\n// ============================================================\n// 型別\n// ============================================================\n\n/** 事件處理器 */\ntype EventHandler<T = unknown> = (data: T) => void;\n\n/** 取消訂閱函式 */\ntype Unsubscribe = () => void;\n\n/** 事件 hook 回傳介面 */\ninterface PluginEvents {\n /** 訂閱事件,回傳取消訂閱函式 */\n readonly on: <T = unknown>(event: string, handler: EventHandler<T>) => Unsubscribe;\n\n /** 發送事件 */\n readonly emit: (event: string, data?: unknown) => void;\n}\n\n// ============================================================\n// 內部工具\n// ============================================================\n\n/** 產生帶前綴的事件名稱 */\nfunction prefixedEventName(event: string): string {\n return `cyodaa:${event}`;\n}\n\n// ============================================================\n// Hook\n// ============================================================\n\n/**\n * 取得 Plugin 事件通訊工具。\n *\n * @example\n * ```tsx\n * import { usePluginEvents } from '@cyodaa/plugin-sdk/react';\n *\n * function MyComponent() {\n * const events = usePluginEvents();\n *\n * useEffect(() => {\n * const unsub = events.on('theme:changed', (data) => {\n * // 處理主題切換\n * });\n * return unsub;\n * }, [events]);\n *\n * const handleNotify = () => {\n * events.emit('plugin:action', { type: 'refresh' });\n * };\n * }\n * ```\n */\nexport function usePluginEvents(): PluginEvents {\n /** 訂閱事件 */\n const on = useCallback(<T = unknown>(\n event: string,\n handler: EventHandler<T>,\n ): Unsubscribe => {\n const eventName = prefixedEventName(event);\n\n const listener = (e: Event) => {\n const customEvent = e as CustomEvent<T>;\n handler(customEvent.detail);\n };\n\n window.addEventListener(eventName, listener);\n\n // 回傳取消訂閱函式\n return () => {\n window.removeEventListener(eventName, listener);\n };\n }, []);\n\n /** 發送事件 */\n const emit = useCallback((event: string, data?: unknown): void => {\n const eventName = prefixedEventName(event);\n\n const customEvent = new CustomEvent(eventName, {\n detail: data,\n bubbles: false,\n cancelable: false,\n });\n\n window.dispatchEvent(customEvent);\n }, []);\n\n return useMemo<PluginEvents>(() => ({ on, emit }), [on, emit]);\n}\n","/**\n * usePluginStorage — Plugin 本地儲存 hook\n *\n * 基於 localStorage,以 `plugin:{pluginId}:` 為前綴隔離各 Plugin 資料。\n * pluginId 透過 SDK Context 內的 tenantId 命名空間進一步隔離。\n */\n\nimport { useCallback, useMemo } from 'react';\n\n// ============================================================\n// 型別\n// ============================================================\n\n/** 儲存 hook 回傳介面 */\ninterface PluginStorage {\n /** 讀取值(JSON 反序列化) */\n readonly get: <T = unknown>(key: string) => T | null;\n\n /** 寫入值(JSON 序列化) */\n readonly set: (key: string, value: unknown) => void;\n\n /** 刪除指定 key */\n readonly delete: (key: string) => void;\n\n /** 列出此 Plugin 所有 key(不含前綴) */\n readonly keys: () => readonly string[];\n\n /** 清除此 Plugin 所有資料 */\n readonly clear: () => void;\n}\n\n// ============================================================\n// Hook\n// ============================================================\n\n/**\n * 取得隔離的 Plugin 本地儲存工具。\n *\n * @param pluginId — Plugin 唯一識別碼\n *\n * @example\n * ```tsx\n * import { usePluginStorage } from '@cyodaa/plugin-sdk/react';\n *\n * function MyComponent() {\n * const storage = usePluginStorage('my-plugin');\n *\n * // 讀取\n * const settings = storage.get<{ darkMode: boolean }>('settings');\n *\n * // 寫入\n * storage.set('settings', { darkMode: true });\n *\n * // 列出所有 key\n * const allKeys = storage.keys();\n * }\n * ```\n */\nexport function usePluginStorage(pluginId: string): PluginStorage {\n /** 產生帶前綴的完整 key */\n const fullKey = useCallback(\n (key: string): string => `plugin:${pluginId}:${key}`,\n [pluginId],\n );\n\n /** 前綴字串(用於 keys/clear) */\n const prefix = useMemo(() => `plugin:${pluginId}:`, [pluginId]);\n\n /** 讀取值 */\n const get = useCallback(<T = unknown>(key: string): T | null => {\n try {\n const raw = localStorage.getItem(fullKey(key));\n if (raw === null) return null;\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n }, [fullKey]);\n\n /** 寫入值 */\n const set = useCallback((key: string, value: unknown): void => {\n try {\n const serialized = JSON.stringify(value);\n localStorage.setItem(fullKey(key), serialized);\n } catch {\n // localStorage 空間不足時靜默失敗\n }\n }, [fullKey]);\n\n /** 刪除指定 key */\n const deleteKey = useCallback((key: string): void => {\n localStorage.removeItem(fullKey(key));\n }, [fullKey]);\n\n /** 列出所有屬於此 Plugin 的 key */\n const keys = useCallback((): readonly string[] => {\n const result: string[] = [];\n const len = localStorage.length;\n\n for (let i = 0; i < len; i++) {\n const k = localStorage.key(i);\n if (k !== null && k.startsWith(prefix)) {\n result.push(k.slice(prefix.length));\n }\n }\n\n return result;\n }, [prefix]);\n\n /** 清除此 Plugin 所有資料 */\n const clear = useCallback((): void => {\n // 先收集所有匹配 key,再批次刪除(避免遍歷時修改集合)\n const keysToRemove: string[] = [];\n const len = localStorage.length;\n\n for (let i = 0; i < len; i++) {\n const k = localStorage.key(i);\n if (k !== null && k.startsWith(prefix)) {\n keysToRemove.push(k);\n }\n }\n\n for (const k of keysToRemove) {\n localStorage.removeItem(k);\n }\n }, [prefix]);\n\n return useMemo<PluginStorage>(\n () => ({ get, set, delete: deleteKey, keys, clear }),\n [get, set, deleteKey, keys, clear],\n );\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react"]}
@@ -0,0 +1,169 @@
1
+ /** 使用者資訊(由 SDK 從 CyodaaPluginContext 轉換) */
2
+ interface CyodaaUser {
3
+ readonly id: string;
4
+ readonly displayName: string;
5
+ readonly email: string;
6
+ readonly role: string;
7
+ }
8
+ /** 租戶資訊 */
9
+ interface CyodaaTenant {
10
+ readonly id: string;
11
+ readonly name: string;
12
+ }
13
+ /** 平台版本資訊 */
14
+ interface CyodaaPlatform {
15
+ readonly version: string;
16
+ }
17
+ /** 提供給 Plugin 開發者的豐富 Context */
18
+ interface CyodaaContext {
19
+ readonly user: CyodaaUser | null;
20
+ readonly theme: 'light' | 'dark';
21
+ readonly tenant: CyodaaTenant;
22
+ readonly locale: string;
23
+ readonly platform: CyodaaPlatform;
24
+ }
25
+
26
+ /**
27
+ * useCyodaaSDK — 取得豐富的平台 Context
28
+ *
29
+ * 將宿主注入的 CyodaaPluginContext 轉換為
30
+ * 開發者友善的 CyodaaContext 格式。
31
+ */
32
+
33
+ /**
34
+ * 取得豐富的 Cyodaa 平台 Context。
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * import { useCyodaaSDK } from '@cyodaa/plugin-sdk/react';
39
+ *
40
+ * function MyComponent() {
41
+ * const { user, theme, tenant } = useCyodaaSDK();
42
+ * return <div>Hi, {user?.displayName ?? 'Guest'}</div>;
43
+ * }
44
+ * ```
45
+ */
46
+ declare function useCyodaaSDK(): CyodaaContext;
47
+
48
+ /**
49
+ * usePluginAPI — Plugin HTTP 通訊 hook
50
+ *
51
+ * 提供 RPC 呼叫與通用 HTTP 方法,
52
+ * 全部使用相對 URL(same-origin)。
53
+ */
54
+ /** API hook 回傳介面 */
55
+ interface PluginAPI {
56
+ /** 呼叫後端 RPC 端點 */
57
+ readonly call: <T = unknown>(service: string, method: string, params?: Record<string, unknown>) => Promise<T>;
58
+ /** GET 請求 */
59
+ readonly get: <T = unknown>(path: string) => Promise<T>;
60
+ /** POST 請求 */
61
+ readonly post: <T = unknown>(path: string, body?: unknown) => Promise<T>;
62
+ }
63
+ /**
64
+ * 取得 Plugin API 呼叫工具。
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * import { usePluginAPI } from '@cyodaa/plugin-sdk/react';
69
+ *
70
+ * function MyComponent() {
71
+ * const api = usePluginAPI();
72
+ *
73
+ * const handleClick = async () => {
74
+ * const result = await api.call('kpi', 'getSummary', { period: '2026-Q1' });
75
+ * };
76
+ * }
77
+ * ```
78
+ */
79
+ declare function usePluginAPI(): PluginAPI;
80
+
81
+ /**
82
+ * usePluginEvents — Plugin 事件通訊 hook
83
+ *
84
+ * 透過 window 上的 CustomEvent 實現
85
+ * Plugin 與宿主平台之間的事件訂閱/發布。
86
+ *
87
+ * 事件名稱統一加上 `cyodaa:` 前綴避免衝突。
88
+ */
89
+ /** 事件處理器 */
90
+ type EventHandler<T = unknown> = (data: T) => void;
91
+ /** 取消訂閱函式 */
92
+ type Unsubscribe = () => void;
93
+ /** 事件 hook 回傳介面 */
94
+ interface PluginEvents {
95
+ /** 訂閱事件,回傳取消訂閱函式 */
96
+ readonly on: <T = unknown>(event: string, handler: EventHandler<T>) => Unsubscribe;
97
+ /** 發送事件 */
98
+ readonly emit: (event: string, data?: unknown) => void;
99
+ }
100
+ /**
101
+ * 取得 Plugin 事件通訊工具。
102
+ *
103
+ * @example
104
+ * ```tsx
105
+ * import { usePluginEvents } from '@cyodaa/plugin-sdk/react';
106
+ *
107
+ * function MyComponent() {
108
+ * const events = usePluginEvents();
109
+ *
110
+ * useEffect(() => {
111
+ * const unsub = events.on('theme:changed', (data) => {
112
+ * // 處理主題切換
113
+ * });
114
+ * return unsub;
115
+ * }, [events]);
116
+ *
117
+ * const handleNotify = () => {
118
+ * events.emit('plugin:action', { type: 'refresh' });
119
+ * };
120
+ * }
121
+ * ```
122
+ */
123
+ declare function usePluginEvents(): PluginEvents;
124
+
125
+ /**
126
+ * usePluginStorage — Plugin 本地儲存 hook
127
+ *
128
+ * 基於 localStorage,以 `plugin:{pluginId}:` 為前綴隔離各 Plugin 資料。
129
+ * pluginId 透過 SDK Context 內的 tenantId 命名空間進一步隔離。
130
+ */
131
+ /** 儲存 hook 回傳介面 */
132
+ interface PluginStorage {
133
+ /** 讀取值(JSON 反序列化) */
134
+ readonly get: <T = unknown>(key: string) => T | null;
135
+ /** 寫入值(JSON 序列化) */
136
+ readonly set: (key: string, value: unknown) => void;
137
+ /** 刪除指定 key */
138
+ readonly delete: (key: string) => void;
139
+ /** 列出此 Plugin 所有 key(不含前綴) */
140
+ readonly keys: () => readonly string[];
141
+ /** 清除此 Plugin 所有資料 */
142
+ readonly clear: () => void;
143
+ }
144
+ /**
145
+ * 取得隔離的 Plugin 本地儲存工具。
146
+ *
147
+ * @param pluginId — Plugin 唯一識別碼
148
+ *
149
+ * @example
150
+ * ```tsx
151
+ * import { usePluginStorage } from '@cyodaa/plugin-sdk/react';
152
+ *
153
+ * function MyComponent() {
154
+ * const storage = usePluginStorage('my-plugin');
155
+ *
156
+ * // 讀取
157
+ * const settings = storage.get<{ darkMode: boolean }>('settings');
158
+ *
159
+ * // 寫入
160
+ * storage.set('settings', { darkMode: true });
161
+ *
162
+ * // 列出所有 key
163
+ * const allKeys = storage.keys();
164
+ * }
165
+ * ```
166
+ */
167
+ declare function usePluginStorage(pluginId: string): PluginStorage;
168
+
169
+ export { useCyodaaSDK, usePluginAPI, usePluginEvents, usePluginStorage };
@@ -0,0 +1,169 @@
1
+ /** 使用者資訊(由 SDK 從 CyodaaPluginContext 轉換) */
2
+ interface CyodaaUser {
3
+ readonly id: string;
4
+ readonly displayName: string;
5
+ readonly email: string;
6
+ readonly role: string;
7
+ }
8
+ /** 租戶資訊 */
9
+ interface CyodaaTenant {
10
+ readonly id: string;
11
+ readonly name: string;
12
+ }
13
+ /** 平台版本資訊 */
14
+ interface CyodaaPlatform {
15
+ readonly version: string;
16
+ }
17
+ /** 提供給 Plugin 開發者的豐富 Context */
18
+ interface CyodaaContext {
19
+ readonly user: CyodaaUser | null;
20
+ readonly theme: 'light' | 'dark';
21
+ readonly tenant: CyodaaTenant;
22
+ readonly locale: string;
23
+ readonly platform: CyodaaPlatform;
24
+ }
25
+
26
+ /**
27
+ * useCyodaaSDK — 取得豐富的平台 Context
28
+ *
29
+ * 將宿主注入的 CyodaaPluginContext 轉換為
30
+ * 開發者友善的 CyodaaContext 格式。
31
+ */
32
+
33
+ /**
34
+ * 取得豐富的 Cyodaa 平台 Context。
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * import { useCyodaaSDK } from '@cyodaa/plugin-sdk/react';
39
+ *
40
+ * function MyComponent() {
41
+ * const { user, theme, tenant } = useCyodaaSDK();
42
+ * return <div>Hi, {user?.displayName ?? 'Guest'}</div>;
43
+ * }
44
+ * ```
45
+ */
46
+ declare function useCyodaaSDK(): CyodaaContext;
47
+
48
+ /**
49
+ * usePluginAPI — Plugin HTTP 通訊 hook
50
+ *
51
+ * 提供 RPC 呼叫與通用 HTTP 方法,
52
+ * 全部使用相對 URL(same-origin)。
53
+ */
54
+ /** API hook 回傳介面 */
55
+ interface PluginAPI {
56
+ /** 呼叫後端 RPC 端點 */
57
+ readonly call: <T = unknown>(service: string, method: string, params?: Record<string, unknown>) => Promise<T>;
58
+ /** GET 請求 */
59
+ readonly get: <T = unknown>(path: string) => Promise<T>;
60
+ /** POST 請求 */
61
+ readonly post: <T = unknown>(path: string, body?: unknown) => Promise<T>;
62
+ }
63
+ /**
64
+ * 取得 Plugin API 呼叫工具。
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * import { usePluginAPI } from '@cyodaa/plugin-sdk/react';
69
+ *
70
+ * function MyComponent() {
71
+ * const api = usePluginAPI();
72
+ *
73
+ * const handleClick = async () => {
74
+ * const result = await api.call('kpi', 'getSummary', { period: '2026-Q1' });
75
+ * };
76
+ * }
77
+ * ```
78
+ */
79
+ declare function usePluginAPI(): PluginAPI;
80
+
81
+ /**
82
+ * usePluginEvents — Plugin 事件通訊 hook
83
+ *
84
+ * 透過 window 上的 CustomEvent 實現
85
+ * Plugin 與宿主平台之間的事件訂閱/發布。
86
+ *
87
+ * 事件名稱統一加上 `cyodaa:` 前綴避免衝突。
88
+ */
89
+ /** 事件處理器 */
90
+ type EventHandler<T = unknown> = (data: T) => void;
91
+ /** 取消訂閱函式 */
92
+ type Unsubscribe = () => void;
93
+ /** 事件 hook 回傳介面 */
94
+ interface PluginEvents {
95
+ /** 訂閱事件,回傳取消訂閱函式 */
96
+ readonly on: <T = unknown>(event: string, handler: EventHandler<T>) => Unsubscribe;
97
+ /** 發送事件 */
98
+ readonly emit: (event: string, data?: unknown) => void;
99
+ }
100
+ /**
101
+ * 取得 Plugin 事件通訊工具。
102
+ *
103
+ * @example
104
+ * ```tsx
105
+ * import { usePluginEvents } from '@cyodaa/plugin-sdk/react';
106
+ *
107
+ * function MyComponent() {
108
+ * const events = usePluginEvents();
109
+ *
110
+ * useEffect(() => {
111
+ * const unsub = events.on('theme:changed', (data) => {
112
+ * // 處理主題切換
113
+ * });
114
+ * return unsub;
115
+ * }, [events]);
116
+ *
117
+ * const handleNotify = () => {
118
+ * events.emit('plugin:action', { type: 'refresh' });
119
+ * };
120
+ * }
121
+ * ```
122
+ */
123
+ declare function usePluginEvents(): PluginEvents;
124
+
125
+ /**
126
+ * usePluginStorage — Plugin 本地儲存 hook
127
+ *
128
+ * 基於 localStorage,以 `plugin:{pluginId}:` 為前綴隔離各 Plugin 資料。
129
+ * pluginId 透過 SDK Context 內的 tenantId 命名空間進一步隔離。
130
+ */
131
+ /** 儲存 hook 回傳介面 */
132
+ interface PluginStorage {
133
+ /** 讀取值(JSON 反序列化) */
134
+ readonly get: <T = unknown>(key: string) => T | null;
135
+ /** 寫入值(JSON 序列化) */
136
+ readonly set: (key: string, value: unknown) => void;
137
+ /** 刪除指定 key */
138
+ readonly delete: (key: string) => void;
139
+ /** 列出此 Plugin 所有 key(不含前綴) */
140
+ readonly keys: () => readonly string[];
141
+ /** 清除此 Plugin 所有資料 */
142
+ readonly clear: () => void;
143
+ }
144
+ /**
145
+ * 取得隔離的 Plugin 本地儲存工具。
146
+ *
147
+ * @param pluginId — Plugin 唯一識別碼
148
+ *
149
+ * @example
150
+ * ```tsx
151
+ * import { usePluginStorage } from '@cyodaa/plugin-sdk/react';
152
+ *
153
+ * function MyComponent() {
154
+ * const storage = usePluginStorage('my-plugin');
155
+ *
156
+ * // 讀取
157
+ * const settings = storage.get<{ darkMode: boolean }>('settings');
158
+ *
159
+ * // 寫入
160
+ * storage.set('settings', { darkMode: true });
161
+ *
162
+ * // 列出所有 key
163
+ * const allKeys = storage.keys();
164
+ * }
165
+ * ```
166
+ */
167
+ declare function usePluginStorage(pluginId: string): PluginStorage;
168
+
169
+ export { useCyodaaSDK, usePluginAPI, usePluginEvents, usePluginStorage };
@@ -0,0 +1,181 @@
1
+ // src/react/useCyodaaSDK.ts
2
+ import { useMemo } from "react";
3
+
4
+ // src/context.ts
5
+ import { createContext, useContext, createElement } from "react";
6
+ var CyodaaSDKContext = createContext(null);
7
+ CyodaaSDKContext.displayName = "CyodaaSDKContext";
8
+ function useSDKContext() {
9
+ const ctx = useContext(CyodaaSDKContext);
10
+ if (ctx === null) {
11
+ throw new Error(
12
+ "[plugin-sdk] useSDKContext \u5FC5\u9808\u5728 CyodaaSDKProvider \u5167\u4F7F\u7528\u3002\u8ACB\u78BA\u8A8D Plugin \u5DF2\u900F\u904E registerPlugin() \u6B63\u78BA\u8A3B\u518A\u3002"
13
+ );
14
+ }
15
+ return ctx;
16
+ }
17
+
18
+ // src/react/useCyodaaSDK.ts
19
+ var SDK_VERSION = "0.1.0";
20
+ var DEFAULT_LOCALE = "zh-TW";
21
+ function useCyodaaSDK() {
22
+ const ctx = useSDKContext();
23
+ return useMemo(() => {
24
+ const user = ctx.userId !== null ? {
25
+ id: String(ctx.userId),
26
+ displayName: "",
27
+ email: "",
28
+ role: ""
29
+ } : null;
30
+ return {
31
+ user,
32
+ theme: ctx.theme,
33
+ tenant: {
34
+ id: ctx.tenantId,
35
+ name: ctx.tenantId
36
+ },
37
+ locale: DEFAULT_LOCALE,
38
+ platform: {
39
+ version: SDK_VERSION
40
+ }
41
+ };
42
+ }, [ctx.tenantId, ctx.userId, ctx.theme]);
43
+ }
44
+
45
+ // src/react/usePluginAPI.ts
46
+ import { useCallback, useMemo as useMemo2 } from "react";
47
+ async function handleResponse(response) {
48
+ if (!response.ok) {
49
+ const text = await response.text().catch(() => "");
50
+ throw new Error(
51
+ `[plugin-sdk] API \u8ACB\u6C42\u5931\u6557\uFF1A${response.status} ${response.statusText}` + (text ? ` \u2014 ${text}` : "")
52
+ );
53
+ }
54
+ return response.json();
55
+ }
56
+ function getHeaders() {
57
+ return {
58
+ "Content-Type": "application/json",
59
+ "Accept": "application/json"
60
+ };
61
+ }
62
+ function usePluginAPI() {
63
+ const call = useCallback(async (service, method, params) => {
64
+ const payload = { service, method, params };
65
+ const response = await fetch("/api/v1/plugins/rpc", {
66
+ method: "POST",
67
+ headers: getHeaders(),
68
+ body: JSON.stringify(payload)
69
+ });
70
+ return handleResponse(response);
71
+ }, []);
72
+ const get = useCallback(async (path) => {
73
+ const response = await fetch(path, {
74
+ method: "GET",
75
+ headers: getHeaders()
76
+ });
77
+ return handleResponse(response);
78
+ }, []);
79
+ const post = useCallback(async (path, body) => {
80
+ const response = await fetch(path, {
81
+ method: "POST",
82
+ headers: getHeaders(),
83
+ body: body !== void 0 ? JSON.stringify(body) : void 0
84
+ });
85
+ return handleResponse(response);
86
+ }, []);
87
+ return useMemo2(() => ({ call, get, post }), [call, get, post]);
88
+ }
89
+
90
+ // src/react/usePluginEvents.ts
91
+ import { useCallback as useCallback2, useMemo as useMemo3 } from "react";
92
+ function prefixedEventName(event) {
93
+ return `cyodaa:${event}`;
94
+ }
95
+ function usePluginEvents() {
96
+ const on = useCallback2((event, handler) => {
97
+ const eventName = prefixedEventName(event);
98
+ const listener = (e) => {
99
+ const customEvent = e;
100
+ handler(customEvent.detail);
101
+ };
102
+ window.addEventListener(eventName, listener);
103
+ return () => {
104
+ window.removeEventListener(eventName, listener);
105
+ };
106
+ }, []);
107
+ const emit = useCallback2((event, data) => {
108
+ const eventName = prefixedEventName(event);
109
+ const customEvent = new CustomEvent(eventName, {
110
+ detail: data,
111
+ bubbles: false,
112
+ cancelable: false
113
+ });
114
+ window.dispatchEvent(customEvent);
115
+ }, []);
116
+ return useMemo3(() => ({ on, emit }), [on, emit]);
117
+ }
118
+
119
+ // src/react/usePluginStorage.ts
120
+ import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
121
+ function usePluginStorage(pluginId) {
122
+ const fullKey = useCallback3(
123
+ (key) => `plugin:${pluginId}:${key}`,
124
+ [pluginId]
125
+ );
126
+ const prefix = useMemo4(() => `plugin:${pluginId}:`, [pluginId]);
127
+ const get = useCallback3((key) => {
128
+ try {
129
+ const raw = localStorage.getItem(fullKey(key));
130
+ if (raw === null) return null;
131
+ return JSON.parse(raw);
132
+ } catch {
133
+ return null;
134
+ }
135
+ }, [fullKey]);
136
+ const set = useCallback3((key, value) => {
137
+ try {
138
+ const serialized = JSON.stringify(value);
139
+ localStorage.setItem(fullKey(key), serialized);
140
+ } catch {
141
+ }
142
+ }, [fullKey]);
143
+ const deleteKey = useCallback3((key) => {
144
+ localStorage.removeItem(fullKey(key));
145
+ }, [fullKey]);
146
+ const keys = useCallback3(() => {
147
+ const result = [];
148
+ const len = localStorage.length;
149
+ for (let i = 0; i < len; i++) {
150
+ const k = localStorage.key(i);
151
+ if (k !== null && k.startsWith(prefix)) {
152
+ result.push(k.slice(prefix.length));
153
+ }
154
+ }
155
+ return result;
156
+ }, [prefix]);
157
+ const clear = useCallback3(() => {
158
+ const keysToRemove = [];
159
+ const len = localStorage.length;
160
+ for (let i = 0; i < len; i++) {
161
+ const k = localStorage.key(i);
162
+ if (k !== null && k.startsWith(prefix)) {
163
+ keysToRemove.push(k);
164
+ }
165
+ }
166
+ for (const k of keysToRemove) {
167
+ localStorage.removeItem(k);
168
+ }
169
+ }, [prefix]);
170
+ return useMemo4(
171
+ () => ({ get, set, delete: deleteKey, keys, clear }),
172
+ [get, set, deleteKey, keys, clear]
173
+ );
174
+ }
175
+ export {
176
+ useCyodaaSDK,
177
+ usePluginAPI,
178
+ usePluginEvents,
179
+ usePluginStorage
180
+ };
181
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/useCyodaaSDK.ts","../../src/context.ts","../../src/react/usePluginAPI.ts","../../src/react/usePluginEvents.ts","../../src/react/usePluginStorage.ts"],"sourcesContent":["/**\n * useCyodaaSDK — 取得豐富的平台 Context\n *\n * 將宿主注入的 CyodaaPluginContext 轉換為\n * 開發者友善的 CyodaaContext 格式。\n */\n\nimport { useMemo } from 'react';\nimport { useSDKContext } from '../context';\nimport type { CyodaaContext, CyodaaUser } from '../types';\n\n/** SDK 版本(與 package.json 同步) */\nconst SDK_VERSION = '0.1.0';\n\n/** 預設語系 */\nconst DEFAULT_LOCALE = 'zh-TW';\n\n/**\n * 取得豐富的 Cyodaa 平台 Context。\n *\n * @example\n * ```tsx\n * import { useCyodaaSDK } from '@cyodaa/plugin-sdk/react';\n *\n * function MyComponent() {\n * const { user, theme, tenant } = useCyodaaSDK();\n * return <div>Hi, {user?.displayName ?? 'Guest'}</div>;\n * }\n * ```\n */\nexport function useCyodaaSDK(): CyodaaContext {\n const ctx = useSDKContext();\n\n return useMemo<CyodaaContext>(() => {\n // 將 userId 轉換為 CyodaaUser(null-safe)\n const user: CyodaaUser | null = ctx.userId !== null\n ? {\n id: String(ctx.userId),\n displayName: '',\n email: '',\n role: '',\n }\n : null;\n\n return {\n user,\n theme: ctx.theme,\n tenant: {\n id: ctx.tenantId,\n name: ctx.tenantId,\n },\n locale: DEFAULT_LOCALE,\n platform: {\n version: SDK_VERSION,\n },\n };\n }, [ctx.tenantId, ctx.userId, ctx.theme]);\n}\n","/**\n * SDK 內部 React Context\n *\n * 將宿主注入的 CyodaaPluginContext 透過 React Context 傳遞給\n * Plugin 元件樹,供各 hook 消費。\n */\n\nimport { createContext, useContext, createElement } from 'react';\nimport type { ReactNode } from 'react';\nimport type { CyodaaPluginContext } from './types';\n\n// ============================================================\n// Context\n// ============================================================\n\n/** SDK 內部用 Context — 不直接對外導出 */\nconst CyodaaSDKContext = createContext<CyodaaPluginContext | null>(null);\n\nCyodaaSDKContext.displayName = 'CyodaaSDKContext';\n\n// ============================================================\n// Provider\n// ============================================================\n\ninterface ProviderProps {\n readonly value: CyodaaPluginContext;\n readonly children: ReactNode;\n}\n\n/** 將平台 Context 注入 React 元件樹 */\nexport function CyodaaSDKProvider({ value, children }: ProviderProps) {\n return createElement(CyodaaSDKContext.Provider, { value }, children);\n}\n\n// ============================================================\n// 內部 Hook\n// ============================================================\n\n/**\n * 內部 hook:讀取原始 CyodaaPluginContext。\n * 僅供 SDK 內部 hook 使用,不對外導出。\n *\n * @throws {Error} 若在 CyodaaSDKProvider 外使用\n */\nexport function useSDKContext(): CyodaaPluginContext {\n const ctx = useContext(CyodaaSDKContext);\n\n if (ctx === null) {\n throw new Error(\n '[plugin-sdk] useSDKContext 必須在 CyodaaSDKProvider 內使用。' +\n '請確認 Plugin 已透過 registerPlugin() 正確註冊。'\n );\n }\n\n return ctx;\n}\n","/**\n * usePluginAPI — Plugin HTTP 通訊 hook\n *\n * 提供 RPC 呼叫與通用 HTTP 方法,\n * 全部使用相對 URL(same-origin)。\n */\n\nimport { useCallback, useMemo } from 'react';\n\n// ============================================================\n// 型別\n// ============================================================\n\n/** RPC 呼叫的請求格式 */\ninterface RPCPayload {\n readonly service: string;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\n/** API hook 回傳介面 */\ninterface PluginAPI {\n /** 呼叫後端 RPC 端點 */\n readonly call: <T = unknown>(\n service: string,\n method: string,\n params?: Record<string, unknown>,\n ) => Promise<T>;\n\n /** GET 請求 */\n readonly get: <T = unknown>(path: string) => Promise<T>;\n\n /** POST 請求 */\n readonly post: <T = unknown>(\n path: string,\n body?: unknown,\n ) => Promise<T>;\n}\n\n// ============================================================\n// 內部工具\n// ============================================================\n\n/** 統一 fetch 錯誤處理 */\nasync function handleResponse<T>(response: Response): Promise<T> {\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(\n `[plugin-sdk] API 請求失敗:${response.status} ${response.statusText}` +\n (text ? ` — ${text}` : ''),\n );\n }\n\n return response.json() as Promise<T>;\n}\n\n/** 取得通用 headers */\nfunction getHeaders(): HeadersInit {\n return {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n };\n}\n\n// ============================================================\n// Hook\n// ============================================================\n\n/**\n * 取得 Plugin API 呼叫工具。\n *\n * @example\n * ```tsx\n * import { usePluginAPI } from '@cyodaa/plugin-sdk/react';\n *\n * function MyComponent() {\n * const api = usePluginAPI();\n *\n * const handleClick = async () => {\n * const result = await api.call('kpi', 'getSummary', { period: '2026-Q1' });\n * };\n * }\n * ```\n */\nexport function usePluginAPI(): PluginAPI {\n /** RPC 呼叫:POST /api/v1/plugins/rpc */\n const call = useCallback(async <T = unknown>(\n service: string,\n method: string,\n params?: Record<string, unknown>,\n ): Promise<T> => {\n const payload: RPCPayload = { service, method, params };\n\n const response = await fetch('/api/v1/plugins/rpc', {\n method: 'POST',\n headers: getHeaders(),\n body: JSON.stringify(payload),\n });\n\n return handleResponse<T>(response);\n }, []);\n\n /** GET 請求 */\n const get = useCallback(async <T = unknown>(path: string): Promise<T> => {\n const response = await fetch(path, {\n method: 'GET',\n headers: getHeaders(),\n });\n\n return handleResponse<T>(response);\n }, []);\n\n /** POST 請求 */\n const post = useCallback(async <T = unknown>(\n path: string,\n body?: unknown,\n ): Promise<T> => {\n const response = await fetch(path, {\n method: 'POST',\n headers: getHeaders(),\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n return handleResponse<T>(response);\n }, []);\n\n return useMemo<PluginAPI>(() => ({ call, get, post }), [call, get, post]);\n}\n","/**\n * usePluginEvents — Plugin 事件通訊 hook\n *\n * 透過 window 上的 CustomEvent 實現\n * Plugin 與宿主平台之間的事件訂閱/發布。\n *\n * 事件名稱統一加上 `cyodaa:` 前綴避免衝突。\n */\n\nimport { useCallback, useMemo } from 'react';\n\n// ============================================================\n// 型別\n// ============================================================\n\n/** 事件處理器 */\ntype EventHandler<T = unknown> = (data: T) => void;\n\n/** 取消訂閱函式 */\ntype Unsubscribe = () => void;\n\n/** 事件 hook 回傳介面 */\ninterface PluginEvents {\n /** 訂閱事件,回傳取消訂閱函式 */\n readonly on: <T = unknown>(event: string, handler: EventHandler<T>) => Unsubscribe;\n\n /** 發送事件 */\n readonly emit: (event: string, data?: unknown) => void;\n}\n\n// ============================================================\n// 內部工具\n// ============================================================\n\n/** 產生帶前綴的事件名稱 */\nfunction prefixedEventName(event: string): string {\n return `cyodaa:${event}`;\n}\n\n// ============================================================\n// Hook\n// ============================================================\n\n/**\n * 取得 Plugin 事件通訊工具。\n *\n * @example\n * ```tsx\n * import { usePluginEvents } from '@cyodaa/plugin-sdk/react';\n *\n * function MyComponent() {\n * const events = usePluginEvents();\n *\n * useEffect(() => {\n * const unsub = events.on('theme:changed', (data) => {\n * // 處理主題切換\n * });\n * return unsub;\n * }, [events]);\n *\n * const handleNotify = () => {\n * events.emit('plugin:action', { type: 'refresh' });\n * };\n * }\n * ```\n */\nexport function usePluginEvents(): PluginEvents {\n /** 訂閱事件 */\n const on = useCallback(<T = unknown>(\n event: string,\n handler: EventHandler<T>,\n ): Unsubscribe => {\n const eventName = prefixedEventName(event);\n\n const listener = (e: Event) => {\n const customEvent = e as CustomEvent<T>;\n handler(customEvent.detail);\n };\n\n window.addEventListener(eventName, listener);\n\n // 回傳取消訂閱函式\n return () => {\n window.removeEventListener(eventName, listener);\n };\n }, []);\n\n /** 發送事件 */\n const emit = useCallback((event: string, data?: unknown): void => {\n const eventName = prefixedEventName(event);\n\n const customEvent = new CustomEvent(eventName, {\n detail: data,\n bubbles: false,\n cancelable: false,\n });\n\n window.dispatchEvent(customEvent);\n }, []);\n\n return useMemo<PluginEvents>(() => ({ on, emit }), [on, emit]);\n}\n","/**\n * usePluginStorage — Plugin 本地儲存 hook\n *\n * 基於 localStorage,以 `plugin:{pluginId}:` 為前綴隔離各 Plugin 資料。\n * pluginId 透過 SDK Context 內的 tenantId 命名空間進一步隔離。\n */\n\nimport { useCallback, useMemo } from 'react';\n\n// ============================================================\n// 型別\n// ============================================================\n\n/** 儲存 hook 回傳介面 */\ninterface PluginStorage {\n /** 讀取值(JSON 反序列化) */\n readonly get: <T = unknown>(key: string) => T | null;\n\n /** 寫入值(JSON 序列化) */\n readonly set: (key: string, value: unknown) => void;\n\n /** 刪除指定 key */\n readonly delete: (key: string) => void;\n\n /** 列出此 Plugin 所有 key(不含前綴) */\n readonly keys: () => readonly string[];\n\n /** 清除此 Plugin 所有資料 */\n readonly clear: () => void;\n}\n\n// ============================================================\n// Hook\n// ============================================================\n\n/**\n * 取得隔離的 Plugin 本地儲存工具。\n *\n * @param pluginId — Plugin 唯一識別碼\n *\n * @example\n * ```tsx\n * import { usePluginStorage } from '@cyodaa/plugin-sdk/react';\n *\n * function MyComponent() {\n * const storage = usePluginStorage('my-plugin');\n *\n * // 讀取\n * const settings = storage.get<{ darkMode: boolean }>('settings');\n *\n * // 寫入\n * storage.set('settings', { darkMode: true });\n *\n * // 列出所有 key\n * const allKeys = storage.keys();\n * }\n * ```\n */\nexport function usePluginStorage(pluginId: string): PluginStorage {\n /** 產生帶前綴的完整 key */\n const fullKey = useCallback(\n (key: string): string => `plugin:${pluginId}:${key}`,\n [pluginId],\n );\n\n /** 前綴字串(用於 keys/clear) */\n const prefix = useMemo(() => `plugin:${pluginId}:`, [pluginId]);\n\n /** 讀取值 */\n const get = useCallback(<T = unknown>(key: string): T | null => {\n try {\n const raw = localStorage.getItem(fullKey(key));\n if (raw === null) return null;\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n }, [fullKey]);\n\n /** 寫入值 */\n const set = useCallback((key: string, value: unknown): void => {\n try {\n const serialized = JSON.stringify(value);\n localStorage.setItem(fullKey(key), serialized);\n } catch {\n // localStorage 空間不足時靜默失敗\n }\n }, [fullKey]);\n\n /** 刪除指定 key */\n const deleteKey = useCallback((key: string): void => {\n localStorage.removeItem(fullKey(key));\n }, [fullKey]);\n\n /** 列出所有屬於此 Plugin 的 key */\n const keys = useCallback((): readonly string[] => {\n const result: string[] = [];\n const len = localStorage.length;\n\n for (let i = 0; i < len; i++) {\n const k = localStorage.key(i);\n if (k !== null && k.startsWith(prefix)) {\n result.push(k.slice(prefix.length));\n }\n }\n\n return result;\n }, [prefix]);\n\n /** 清除此 Plugin 所有資料 */\n const clear = useCallback((): void => {\n // 先收集所有匹配 key,再批次刪除(避免遍歷時修改集合)\n const keysToRemove: string[] = [];\n const len = localStorage.length;\n\n for (let i = 0; i < len; i++) {\n const k = localStorage.key(i);\n if (k !== null && k.startsWith(prefix)) {\n keysToRemove.push(k);\n }\n }\n\n for (const k of keysToRemove) {\n localStorage.removeItem(k);\n }\n }, [prefix]);\n\n return useMemo<PluginStorage>(\n () => ({ get, set, delete: deleteKey, keys, clear }),\n [get, set, deleteKey, keys, clear],\n );\n}\n"],"mappings":"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","names":["useMemo","useCallback","useMemo","useCallback","useMemo"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@cyodaa/plugin-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Cyodaa Plugin SDK - 在 Cyodaa 平台上開發插件的官方 SDK",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.mjs",
10
+ "require": "./dist/index.cjs"
11
+ },
12
+ "./react": {
13
+ "types": "./dist/react/index.d.ts",
14
+ "import": "./dist/react/index.mjs",
15
+ "require": "./dist/react/index.cjs"
16
+ }
17
+ },
18
+ "main": "./dist/index.cjs",
19
+ "module": "./dist/index.mjs",
20
+ "types": "./dist/index.d.ts",
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsup --watch",
29
+ "typecheck": "tsc --noEmit",
30
+ "clean": "rm -rf dist"
31
+ },
32
+ "peerDependencies": {
33
+ "react": ">=18.0.0",
34
+ "react-dom": ">=18.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/react": "^19.0.0",
38
+ "@types/react-dom": "^19.2.3",
39
+ "react": "^19.0.0",
40
+ "tsup": "^8.0.0",
41
+ "typescript": "^5.5.0"
42
+ },
43
+ "keywords": [
44
+ "cyodaa",
45
+ "plugin",
46
+ "sdk",
47
+ "marketplace"
48
+ ],
49
+ "author": {
50
+ "name": "Cyodaa Team",
51
+ "email": "dev@cyodaa.com"
52
+ },
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/cyodaa/plugin-sdk.git"
57
+ },
58
+ "homepage": "https://cyodaa.com/docs/sdk"
59
+ }