@gemigo/app-sdk 0.2.5
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 +22 -0
- package/README.md +117 -0
- package/dist/apis/index.d.ts +10 -0
- package/dist/connection-C-IGR2wz.js +2 -0
- package/dist/connection-DwOg1Kh5.js +248 -0
- package/dist/core/api-factory.d.ts +84 -0
- package/dist/core/connection.d.ts +99 -0
- package/dist/core/event-bus.d.ts +77 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/fallback/index.d.ts +8 -0
- package/dist/fallback/network.d.ts +2 -0
- package/dist/fallback/notify.d.ts +2 -0
- package/dist/fallback/storage.d.ts +2 -0
- package/dist/gemigo-app-sdk.es.js +272 -0
- package/dist/gemigo-app-sdk.umd.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/types/ai.d.ts +48 -0
- package/dist/types/clipboard.d.ts +39 -0
- package/dist/types/common.d.ts +72 -0
- package/dist/types/desktop.d.ts +138 -0
- package/dist/types/dialog.d.ts +65 -0
- package/dist/types/extension.d.ts +167 -0
- package/dist/types/file.d.ts +79 -0
- package/dist/types/index.d.ts +91 -0
- package/dist/types/manifest.d.ts +87 -0
- package/dist/types/network.d.ts +37 -0
- package/dist/types/notify.d.ts +28 -0
- package/dist/types/storage.d.ts +27 -0
- package/package.json +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 GemiGo Contributors
|
|
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.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @gemigo/app-sdk
|
|
2
|
+
|
|
3
|
+
GemiGo App SDK that auto-adapts for Web / Desktop / Browser Extension.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### CDN (Recommended)
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script src="https://unpkg.com/@gemigo/app-sdk/dist/gemigo-app-sdk.umd.js"></script>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### npm
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @gemigo/app-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<script src="https://unpkg.com/@gemigo/app-sdk/dist/gemigo-app-sdk.umd.js"></script>
|
|
23
|
+
<script>
|
|
24
|
+
// SDK auto-connects, use gemigo.extension.* APIs directly
|
|
25
|
+
gemigo.extension.getPageInfo().then(console.log);
|
|
26
|
+
|
|
27
|
+
// Subscribe to context menu events
|
|
28
|
+
gemigo.extension.onContextMenu((event) => {
|
|
29
|
+
console.log('Menu clicked:', event.menuId, event.selectionText);
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API Reference
|
|
35
|
+
|
|
36
|
+
### Page Content Reading
|
|
37
|
+
|
|
38
|
+
| Method | Description |
|
|
39
|
+
|--------|-------------|
|
|
40
|
+
| `getPageInfo()` | Get current page URL, title, favicon |
|
|
41
|
+
| `getPageHTML()` | Get full page HTML content |
|
|
42
|
+
| `getPageText()` | Get page text content |
|
|
43
|
+
| `getSelection()` | Get selected text and position `{ text, rect? }` |
|
|
44
|
+
| `extractArticle()` | Extract article title, content, excerpt |
|
|
45
|
+
| `extractLinks()` | Extract all links from page |
|
|
46
|
+
| `extractImages()` | Extract all images from page |
|
|
47
|
+
| `queryElement(selector, limit?)` | Query elements by CSS selector |
|
|
48
|
+
|
|
49
|
+
### Page Manipulation
|
|
50
|
+
|
|
51
|
+
| Method | Description |
|
|
52
|
+
|--------|-------------|
|
|
53
|
+
| `highlight(selector, color?)` | Highlight elements (returns highlightId) |
|
|
54
|
+
| `removeHighlight(highlightId)` | Remove highlight |
|
|
55
|
+
| `insertWidget(html, position)` | Insert floating widget |
|
|
56
|
+
| `updateWidget(widgetId, html)` | Update widget content |
|
|
57
|
+
| `removeWidget(widgetId)` | Remove widget |
|
|
58
|
+
| `injectCSS(css)` | Inject CSS (returns styleId) |
|
|
59
|
+
| `removeCSS(styleId)` | Remove injected CSS |
|
|
60
|
+
|
|
61
|
+
### Screenshots
|
|
62
|
+
|
|
63
|
+
| Method | Description |
|
|
64
|
+
|--------|-------------|
|
|
65
|
+
| `captureVisible()` | Capture visible area screenshot |
|
|
66
|
+
|
|
67
|
+
### Events
|
|
68
|
+
|
|
69
|
+
| Method | Description |
|
|
70
|
+
|--------|-------------|
|
|
71
|
+
| `onContextMenu(handler)` | Subscribe to context menu events |
|
|
72
|
+
| `onSelectionChange(handler)` | Subscribe to selection changes `(text, rect, url)` |
|
|
73
|
+
| `getContextMenuEvent()` | Get pending context menu event |
|
|
74
|
+
|
|
75
|
+
### Common APIs
|
|
76
|
+
|
|
77
|
+
| Method | Description |
|
|
78
|
+
|--------|-------------|
|
|
79
|
+
| `gemigo.notify(title, message)` | Send system notification |
|
|
80
|
+
|
|
81
|
+
## Example: Translation Bubble
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<script src="https://unpkg.com/@gemigo/app-sdk/dist/gemigo-app-sdk.umd.js"></script>
|
|
85
|
+
<script>
|
|
86
|
+
gemigo.extension.onContextMenu(async (event) => {
|
|
87
|
+
if (event.selectionText) {
|
|
88
|
+
const translated = await translateText(event.selectionText);
|
|
89
|
+
|
|
90
|
+
// Show translation bubble on page
|
|
91
|
+
await gemigo.extension.insertWidget(
|
|
92
|
+
`<div style="background:#667eea;color:#fff;padding:16px;border-radius:12px;">
|
|
93
|
+
${translated}
|
|
94
|
+
</div>`,
|
|
95
|
+
'bottom-right'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
</script>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Example: Reader Mode
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// Inject reader-friendly CSS
|
|
106
|
+
const { styleId } = await gemigo.extension.injectCSS(`
|
|
107
|
+
body { max-width: 720px; margin: 0 auto; font-family: Georgia, serif; }
|
|
108
|
+
nav, aside, .ads { display: none !important; }
|
|
109
|
+
`);
|
|
110
|
+
|
|
111
|
+
// Remove later
|
|
112
|
+
await gemigo.extension.removeCSS(styleId);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ChildMethods } from '../core';
|
|
2
|
+
import { GemigoSDK, Platform, Capabilities } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Update the SDK's host environment information.
|
|
5
|
+
*/
|
|
6
|
+
export declare const updateHostInfo: (info: {
|
|
7
|
+
platform: Platform;
|
|
8
|
+
capabilities: Capabilities;
|
|
9
|
+
} | null) => void;
|
|
10
|
+
export declare const sdk: GemigoSDK, childMethods: ChildMethods;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
var MessageType;
|
|
2
|
+
(function(e) {
|
|
3
|
+
e.Call = "call", e.Reply = "reply", e.Syn = "syn", e.SynAck = "synAck", e.Ack = "ack";
|
|
4
|
+
})(MessageType ||= {});
|
|
5
|
+
var Resolution;
|
|
6
|
+
(function(e) {
|
|
7
|
+
e.Fulfilled = "fulfilled", e.Rejected = "rejected";
|
|
8
|
+
})(Resolution ||= {});
|
|
9
|
+
var ErrorCode;
|
|
10
|
+
(function(e) {
|
|
11
|
+
e.ConnectionDestroyed = "ConnectionDestroyed", e.ConnectionTimeout = "ConnectionTimeout", e.NoIframeSrc = "NoIframeSrc";
|
|
12
|
+
})(ErrorCode ||= {});
|
|
13
|
+
var NativeErrorName;
|
|
14
|
+
(function(e) {
|
|
15
|
+
e.DataCloneError = "DataCloneError";
|
|
16
|
+
})(NativeErrorName ||= {});
|
|
17
|
+
var NativeEventType;
|
|
18
|
+
(function(e) {
|
|
19
|
+
e.Message = "message";
|
|
20
|
+
})(NativeEventType ||= {});
|
|
21
|
+
var createDestructor_default = (e, g) => {
|
|
22
|
+
let _ = [], v = !1;
|
|
23
|
+
return {
|
|
24
|
+
destroy(y) {
|
|
25
|
+
v || (v = !0, g(`${e}: Destroying connection`), _.forEach((e) => {
|
|
26
|
+
e(y);
|
|
27
|
+
}));
|
|
28
|
+
},
|
|
29
|
+
onDestroy(e) {
|
|
30
|
+
v ? e() : _.push(e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}, createLogger_default = (e) => (...g) => {
|
|
34
|
+
e && console.log("[Penpal]", ...g);
|
|
35
|
+
};
|
|
36
|
+
const serializeError = ({ name: e, message: g, stack: _ }) => ({
|
|
37
|
+
name: e,
|
|
38
|
+
message: g,
|
|
39
|
+
stack: _
|
|
40
|
+
}), deserializeError = (e) => {
|
|
41
|
+
let g = /* @__PURE__ */ Error();
|
|
42
|
+
return Object.keys(e).forEach((_) => g[_] = e[_]), g;
|
|
43
|
+
};
|
|
44
|
+
var connectCallReceiver_default = (_, b, x) => {
|
|
45
|
+
let { localName: C, local: w, remote: T, originForSending: E, originForReceiving: D } = _, O = !1, k = (_) => {
|
|
46
|
+
if (_.source !== T || _.data.penpal !== MessageType.Call) return;
|
|
47
|
+
if (D !== "*" && _.origin !== D) {
|
|
48
|
+
x(`${C} received message from origin ${_.origin} which did not match expected origin ${D}`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let { methodName: y, args: w, id: k } = _.data;
|
|
52
|
+
x(`${C}: Received ${y}() call`);
|
|
53
|
+
let A = (_) => (b) => {
|
|
54
|
+
if (x(`${C}: Sending ${y}() reply`), O) {
|
|
55
|
+
x(`${C}: Unable to send ${y}() reply due to destroyed connection`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let w = {
|
|
59
|
+
penpal: MessageType.Reply,
|
|
60
|
+
id: k,
|
|
61
|
+
resolution: _,
|
|
62
|
+
returnValue: b
|
|
63
|
+
};
|
|
64
|
+
_ === Resolution.Rejected && b instanceof Error && (w.returnValue = serializeError(b), w.returnValueIsError = !0);
|
|
65
|
+
try {
|
|
66
|
+
T.postMessage(w, E);
|
|
67
|
+
} catch (_) {
|
|
68
|
+
if (_.name === NativeErrorName.DataCloneError) {
|
|
69
|
+
let v = {
|
|
70
|
+
penpal: MessageType.Reply,
|
|
71
|
+
id: k,
|
|
72
|
+
resolution: Resolution.Rejected,
|
|
73
|
+
returnValue: serializeError(_),
|
|
74
|
+
returnValueIsError: !0
|
|
75
|
+
};
|
|
76
|
+
T.postMessage(v, E);
|
|
77
|
+
}
|
|
78
|
+
throw _;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
new Promise((e) => e(b[y].apply(b, w))).then(A(Resolution.Fulfilled), A(Resolution.Rejected));
|
|
82
|
+
};
|
|
83
|
+
return w.addEventListener(NativeEventType.Message, k), () => {
|
|
84
|
+
O = !0, w.removeEventListener(NativeEventType.Message, k);
|
|
85
|
+
};
|
|
86
|
+
}, id = 0, generateId_default = () => ++id, KEY_PATH_DELIMITER = ".", keyPathToSegments = (e) => e ? e.split(KEY_PATH_DELIMITER) : [], segmentsToKeyPath = (e) => e.join(KEY_PATH_DELIMITER), createKeyPath = (e, g) => {
|
|
87
|
+
let _ = keyPathToSegments(g || "");
|
|
88
|
+
return _.push(e), segmentsToKeyPath(_);
|
|
89
|
+
};
|
|
90
|
+
const setAtKeyPath = (e, g, _) => {
|
|
91
|
+
let v = keyPathToSegments(g);
|
|
92
|
+
return v.reduce((e, g, y) => (e[g] === void 0 && (e[g] = {}), y === v.length - 1 && (e[g] = _), e[g]), e), e;
|
|
93
|
+
}, serializeMethods = (e, g) => {
|
|
94
|
+
let _ = {};
|
|
95
|
+
return Object.keys(e).forEach((v) => {
|
|
96
|
+
let y = e[v], b = createKeyPath(v, g);
|
|
97
|
+
typeof y == "object" && Object.assign(_, serializeMethods(y, b)), typeof y == "function" && (_[b] = y);
|
|
98
|
+
}), _;
|
|
99
|
+
}, deserializeMethods = (e) => {
|
|
100
|
+
let g = {};
|
|
101
|
+
for (let _ in e) setAtKeyPath(g, _, e[_]);
|
|
102
|
+
return g;
|
|
103
|
+
};
|
|
104
|
+
var connectCallSender_default = (v, b, x, S, w) => {
|
|
105
|
+
let { localName: T, local: D, remote: O, originForSending: k, originForReceiving: A } = b, j = !1;
|
|
106
|
+
w(`${T}: Connecting call sender`);
|
|
107
|
+
let M = (v) => (...b) => {
|
|
108
|
+
w(`${T}: Sending ${v}() call`);
|
|
109
|
+
let x;
|
|
110
|
+
try {
|
|
111
|
+
O.closed && (x = !0);
|
|
112
|
+
} catch {
|
|
113
|
+
x = !0;
|
|
114
|
+
}
|
|
115
|
+
if (x && S(), j) {
|
|
116
|
+
let e = /* @__PURE__ */ Error(`Unable to send ${v}() call due to destroyed connection`);
|
|
117
|
+
throw e.code = ErrorCode.ConnectionDestroyed, e;
|
|
118
|
+
}
|
|
119
|
+
return new Promise((_, x) => {
|
|
120
|
+
let S = generateId_default(), j = (b) => {
|
|
121
|
+
if (b.source !== O || b.data.penpal !== MessageType.Reply || b.data.id !== S) return;
|
|
122
|
+
if (A !== "*" && b.origin !== A) {
|
|
123
|
+
w(`${T} received message from origin ${b.origin} which did not match expected origin ${A}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
let E = b.data;
|
|
127
|
+
w(`${T}: Received ${v}() reply`), D.removeEventListener(NativeEventType.Message, j);
|
|
128
|
+
let k = E.returnValue;
|
|
129
|
+
E.returnValueIsError && (k = deserializeError(k)), (E.resolution === Resolution.Fulfilled ? _ : x)(k);
|
|
130
|
+
};
|
|
131
|
+
D.addEventListener(NativeEventType.Message, j);
|
|
132
|
+
let M = {
|
|
133
|
+
penpal: MessageType.Call,
|
|
134
|
+
id: S,
|
|
135
|
+
methodName: v,
|
|
136
|
+
args: b
|
|
137
|
+
};
|
|
138
|
+
O.postMessage(M, k);
|
|
139
|
+
});
|
|
140
|
+
}, N = x.reduce((e, g) => (e[g] = M(g), e), {});
|
|
141
|
+
return Object.assign(v, deserializeMethods(N)), () => {
|
|
142
|
+
j = !0;
|
|
143
|
+
};
|
|
144
|
+
}, startConnectionTimeout_default = (e, g) => {
|
|
145
|
+
let v;
|
|
146
|
+
return e !== void 0 && (v = window.setTimeout(() => {
|
|
147
|
+
let v = /* @__PURE__ */ Error(`Connection timed out after ${e}ms`);
|
|
148
|
+
v.code = ErrorCode.ConnectionTimeout, g(v);
|
|
149
|
+
}, e)), () => {
|
|
150
|
+
clearTimeout(v);
|
|
151
|
+
};
|
|
152
|
+
}, handleSynAckMessageFactory_default = (g, _, v, y) => {
|
|
153
|
+
let { destroy: b, onDestroy: x } = v;
|
|
154
|
+
return (v) => {
|
|
155
|
+
if (!(g instanceof RegExp ? g.test(v.origin) : g === "*" || g === v.origin)) {
|
|
156
|
+
y(`Child: Handshake - Received SYN-ACK from origin ${v.origin} which did not match expected origin ${g}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
y("Child: Handshake - Received SYN-ACK, responding with ACK");
|
|
160
|
+
let S = v.origin === "null" ? "*" : v.origin, C = {
|
|
161
|
+
penpal: MessageType.Ack,
|
|
162
|
+
methodNames: Object.keys(_)
|
|
163
|
+
};
|
|
164
|
+
window.parent.postMessage(C, S);
|
|
165
|
+
let T = {
|
|
166
|
+
localName: "Child",
|
|
167
|
+
local: window,
|
|
168
|
+
remote: window.parent,
|
|
169
|
+
originForSending: S,
|
|
170
|
+
originForReceiving: v.origin
|
|
171
|
+
};
|
|
172
|
+
x(connectCallReceiver_default(T, _, y));
|
|
173
|
+
let E = {};
|
|
174
|
+
return x(connectCallSender_default(E, T, v.data.methodNames, b, y)), E;
|
|
175
|
+
};
|
|
176
|
+
}, areGlobalsAccessible = () => {
|
|
177
|
+
try {
|
|
178
|
+
clearTimeout();
|
|
179
|
+
} catch {
|
|
180
|
+
return !1;
|
|
181
|
+
}
|
|
182
|
+
return !0;
|
|
183
|
+
}, connectToParent_default = (g = {}) => {
|
|
184
|
+
let { parentOrigin: _ = "*", methods: v = {}, timeout: S, debug: C = !1 } = g, w = createLogger_default(C), T = createDestructor_default("Child", w), { destroy: E, onDestroy: D } = T, O = handleSynAckMessageFactory_default(_, serializeMethods(v), T, w), k = () => {
|
|
185
|
+
w("Child: Handshake - Sending SYN");
|
|
186
|
+
let g = { penpal: MessageType.Syn }, v = _ instanceof RegExp ? "*" : _;
|
|
187
|
+
window.parent.postMessage(g, v);
|
|
188
|
+
};
|
|
189
|
+
return {
|
|
190
|
+
promise: new Promise((g, _) => {
|
|
191
|
+
let v = startConnectionTimeout_default(S, E), b = (_) => {
|
|
192
|
+
if (areGlobalsAccessible() && !(_.source !== parent || !_.data) && _.data.penpal === MessageType.SynAck) {
|
|
193
|
+
let e = O(_);
|
|
194
|
+
e && (window.removeEventListener(NativeEventType.Message, b), v(), g(e));
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
window.addEventListener(NativeEventType.Message, b), k(), D((e) => {
|
|
198
|
+
window.removeEventListener(NativeEventType.Message, b), e && _(e);
|
|
199
|
+
});
|
|
200
|
+
}),
|
|
201
|
+
destroy() {
|
|
202
|
+
E();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}, connectionPromise = null, resolvedHost = null, connectionFailed = !1, defaultChildMethods, DEFAULT_TIMEOUT_MS = 1500;
|
|
206
|
+
function isInIframe() {
|
|
207
|
+
try {
|
|
208
|
+
return window.self !== window.top;
|
|
209
|
+
} catch {
|
|
210
|
+
return !0;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function tryGetHost(e, g) {
|
|
214
|
+
if (resolvedHost) return resolvedHost;
|
|
215
|
+
if (connectionFailed) return null;
|
|
216
|
+
if (!isInIframe()) return connectionFailed = !0, null;
|
|
217
|
+
if (!connectionPromise) {
|
|
218
|
+
let _ = {}, v = e ?? defaultChildMethods;
|
|
219
|
+
v && Object.assign(_, v), connectionPromise = connectToParent_default({
|
|
220
|
+
methods: _,
|
|
221
|
+
timeout: g?.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
222
|
+
}).promise.then((e) => (resolvedHost = e, e)).catch((e) => (console.error("[GemiGo Connection] Promise failed:", e), connectionFailed = !0, connectionPromise = null, null));
|
|
223
|
+
}
|
|
224
|
+
return connectionPromise;
|
|
225
|
+
}
|
|
226
|
+
function initConnection(e, g) {
|
|
227
|
+
defaultChildMethods = e, tryGetHost(e, g);
|
|
228
|
+
}
|
|
229
|
+
async function callHost(e, g = [], _) {
|
|
230
|
+
let v = await tryGetHost();
|
|
231
|
+
if (v && typeof v[e] == "function") {
|
|
232
|
+
let y = await v[e](...g);
|
|
233
|
+
if (y?.success !== !1) return y?.data === void 0 ? y?.value === void 0 ? y : y.value : y.data;
|
|
234
|
+
if (_) return _(...g);
|
|
235
|
+
throw Error(y?.error || `Host method ${String(e)} failed`);
|
|
236
|
+
}
|
|
237
|
+
if (_) return _(...g);
|
|
238
|
+
throw Error(`Method ${String(e)} not supported in this environment`);
|
|
239
|
+
}
|
|
240
|
+
function createRPCProxy(e, g = {}) {
|
|
241
|
+
let _ = {};
|
|
242
|
+
for (let v of e) {
|
|
243
|
+
let e = g.mapping?.[v] || v, y = g.fallbacks?.[v];
|
|
244
|
+
_[v] = (...g) => callHost(e, g, y);
|
|
245
|
+
}
|
|
246
|
+
return _;
|
|
247
|
+
}
|
|
248
|
+
export { tryGetHost as i, createRPCProxy as n, initConnection as r, callHost as t };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { HostMethods } from './connection';
|
|
2
|
+
import { SDKEvents } from './event-bus';
|
|
3
|
+
export interface APIConfig<TAPI extends object> {
|
|
4
|
+
/** RPC methods mapping to host functions */
|
|
5
|
+
rpc?: {
|
|
6
|
+
methods: readonly (keyof TAPI)[];
|
|
7
|
+
mapping?: Partial<Record<keyof TAPI, keyof HostMethods>>;
|
|
8
|
+
fallbacks?: Partial<Record<keyof TAPI, (...args: any[]) => any>>;
|
|
9
|
+
};
|
|
10
|
+
/** Event methods mapping to SDK event bus */
|
|
11
|
+
events?: readonly (keyof TAPI)[] | {
|
|
12
|
+
[K in keyof TAPI]?: keyof SDKEvents | {
|
|
13
|
+
event: keyof SDKEvents;
|
|
14
|
+
childMethod?: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a unified API instance from a declarative configuration.
|
|
20
|
+
*
|
|
21
|
+
* @param config - API configuration
|
|
22
|
+
* @returns Object with generated API instance and child methods for host injection
|
|
23
|
+
*/
|
|
24
|
+
export declare function createUnifiedAPI<TAPI extends object, TChild extends object = Record<string, any>>(config: APIConfig<TAPI>): {
|
|
25
|
+
api: TAPI;
|
|
26
|
+
childMethods: TChild;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Create a standalone RPC action with argument transformation and fallback handling.
|
|
30
|
+
*
|
|
31
|
+
* @param methodName - Host method name to call
|
|
32
|
+
* @param config - Action configuration
|
|
33
|
+
* @returns An async function that handles the full RPC cycle
|
|
34
|
+
*/
|
|
35
|
+
export declare function createRPCAction<TArgs extends any[], TResult, THostResult = any>(methodName: keyof HostMethods, config: {
|
|
36
|
+
/** Transform input arguments for host method */
|
|
37
|
+
transform?: (...args: TArgs) => any[];
|
|
38
|
+
/** Fallback if host fails */
|
|
39
|
+
fallback: (...args: TArgs) => TResult | Promise<TResult>;
|
|
40
|
+
/** Process host result */
|
|
41
|
+
onSuccess?: (result: THostResult) => TResult;
|
|
42
|
+
}): (...args: TArgs) => Promise<TResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Master SDK Action Configuration
|
|
45
|
+
*/
|
|
46
|
+
export interface ActionConfig<TFunc extends (...args: any[]) => any> {
|
|
47
|
+
method: keyof HostMethods;
|
|
48
|
+
/** Transform input arguments for host method */
|
|
49
|
+
transform?: (...args: Parameters<TFunc>) => any[];
|
|
50
|
+
/** Fallback if host fails */
|
|
51
|
+
fallback: TFunc;
|
|
52
|
+
/** Process host result */
|
|
53
|
+
onSuccess?: (result: any) => ReturnType<TFunc> extends Promise<infer R> ? R : ReturnType<TFunc>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Master SDK Factory - Create the entire SDK and child methods from a single config.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createSDK<TSDK extends object, TChild extends object = Record<string, any>>(config: {
|
|
59
|
+
/** Sub-modules (e.g., storage, extension) */
|
|
60
|
+
modules?: {
|
|
61
|
+
[K in keyof TSDK]?: NonNullable<TSDK[K]> extends (...args: any[]) => any ? never : NonNullable<TSDK[K]> extends object ? APIConfig<NonNullable<TSDK[K]>> : never;
|
|
62
|
+
};
|
|
63
|
+
/** Standalone functions (e.g., notify) */
|
|
64
|
+
actions?: {
|
|
65
|
+
[K in keyof TSDK]?: TSDK[K] extends (...args: any[]) => any ? ActionConfig<TSDK[K]> : never;
|
|
66
|
+
};
|
|
67
|
+
/** Dynamic getter properties (e.g., platform, capabilities) */
|
|
68
|
+
getters?: {
|
|
69
|
+
[K in keyof TSDK]?: () => TSDK[K];
|
|
70
|
+
};
|
|
71
|
+
/** Static values or stubs (e.g., platform, ai) */
|
|
72
|
+
statics?: {
|
|
73
|
+
[K in keyof TSDK]?: TSDK[K];
|
|
74
|
+
};
|
|
75
|
+
}): {
|
|
76
|
+
sdk: TSDK;
|
|
77
|
+
childMethods: TChild;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Bootstrap the SDK connection and discover host protocol info.
|
|
81
|
+
*/
|
|
82
|
+
export declare function bootstrapSDK(childMethods: any, options?: {
|
|
83
|
+
timeoutMs?: number;
|
|
84
|
+
}): Promise<any>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { AsyncMethodReturns } from 'penpal';
|
|
2
|
+
import { Capabilities, ExtensionRPCMethods, RPCResult, Platform } from '../types';
|
|
3
|
+
import { SDKEvents } from './event-bus';
|
|
4
|
+
export interface ProtocolRPCMethods {
|
|
5
|
+
getProtocolInfo(): Promise<{
|
|
6
|
+
protocolVersion: number;
|
|
7
|
+
platform: Platform;
|
|
8
|
+
appId: string;
|
|
9
|
+
capabilities: Capabilities;
|
|
10
|
+
}>;
|
|
11
|
+
ping(): Promise<{
|
|
12
|
+
pong: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export interface StorageRPCMethods {
|
|
16
|
+
storageGet(key: string): Promise<{
|
|
17
|
+
success: boolean;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
}>;
|
|
20
|
+
storageSet(key: string, value: unknown): Promise<RPCResult>;
|
|
21
|
+
storageDelete(key: string): Promise<RPCResult>;
|
|
22
|
+
storageClear(): Promise<RPCResult>;
|
|
23
|
+
}
|
|
24
|
+
export interface NetworkRPCMethods {
|
|
25
|
+
networkRequest(url: string, options?: {
|
|
26
|
+
method?: string;
|
|
27
|
+
headers?: Record<string, string>;
|
|
28
|
+
body?: string | object;
|
|
29
|
+
responseType?: 'json' | 'text' | 'arraybuffer';
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
success: boolean;
|
|
33
|
+
status?: number;
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
data?: any;
|
|
36
|
+
error?: string;
|
|
37
|
+
code?: string;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export interface NotifyRPCMethods {
|
|
41
|
+
notify(payload: {
|
|
42
|
+
title: string;
|
|
43
|
+
message: string;
|
|
44
|
+
}): Promise<RPCResult<string>>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Host methods interface - what the host provides to apps.
|
|
48
|
+
* Same interface for all platforms.
|
|
49
|
+
*/
|
|
50
|
+
export interface HostMethods extends ExtensionRPCMethods, ProtocolRPCMethods, StorageRPCMethods, NetworkRPCMethods, NotifyRPCMethods {
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Child methods interface - what apps expose to the host.
|
|
54
|
+
* Automatically derived from SDK event definitions.
|
|
55
|
+
*/
|
|
56
|
+
export type ChildMethods = {
|
|
57
|
+
[K in keyof SDKEvents]?: (...args: SDKEvents[K]) => void;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Get or create connection to parent (host).
|
|
61
|
+
* Returns null if connection failed or not in iframe.
|
|
62
|
+
*/
|
|
63
|
+
export declare function tryGetHost(childMethods?: ChildMethods, options?: {
|
|
64
|
+
timeoutMs?: number;
|
|
65
|
+
}): Promise<AsyncMethodReturns<HostMethods> | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Get host connection (throws if not available).
|
|
68
|
+
*/
|
|
69
|
+
export declare function getHost(): Promise<AsyncMethodReturns<HostMethods>>;
|
|
70
|
+
/**
|
|
71
|
+
* Check if connected to host.
|
|
72
|
+
*/
|
|
73
|
+
export declare function isConnected(): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Check if connection failed.
|
|
76
|
+
*/
|
|
77
|
+
export declare function hasConnectionFailed(): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Initialize connection immediately.
|
|
80
|
+
*/
|
|
81
|
+
export declare function initConnection(childMethods?: ChildMethods, options?: {
|
|
82
|
+
timeoutMs?: number;
|
|
83
|
+
}): void;
|
|
84
|
+
/**
|
|
85
|
+
* Generic host call with optional fallback.
|
|
86
|
+
*/
|
|
87
|
+
export declare function callHost<T>(methodName: keyof HostMethods, args?: any[], fallback?: (...args: any[]) => T | Promise<T>): Promise<T>;
|
|
88
|
+
/**
|
|
89
|
+
* Create a simple RPC proxy for a set of methods.
|
|
90
|
+
*/
|
|
91
|
+
export declare function createRPCProxy<T extends Record<string, any>>(methodNames: readonly (keyof T)[], config?: {
|
|
92
|
+
mapping?: Record<string, keyof HostMethods>;
|
|
93
|
+
fallbacks?: Record<string, (...args: any[]) => any>;
|
|
94
|
+
}): T;
|
|
95
|
+
/**
|
|
96
|
+
* Wrap an async function with a fallback.
|
|
97
|
+
* If the primary function throws, the fallback is called with the same arguments.
|
|
98
|
+
*/
|
|
99
|
+
export declare function withFallback<TArgs extends unknown[], TResult>(primary: (...args: TArgs) => Promise<TResult>, fallback: (...args: TArgs) => TResult | Promise<TResult>): (...args: TArgs) => Promise<TResult>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ExtensionAPI } from '../types';
|
|
2
|
+
export type EventHandler<T extends unknown[] = unknown[]> = (...args: T) => void;
|
|
3
|
+
export interface EventBus<TEvents extends Record<string, unknown[]>> {
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe to an event
|
|
6
|
+
* @returns Unsubscribe function
|
|
7
|
+
*/
|
|
8
|
+
on<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): () => void;
|
|
9
|
+
/**
|
|
10
|
+
* Emit an event to all subscribers
|
|
11
|
+
*/
|
|
12
|
+
emit<K extends keyof TEvents>(event: K, ...args: TEvents[K]): void;
|
|
13
|
+
/**
|
|
14
|
+
* Remove all subscribers for an event (or all events)
|
|
15
|
+
*/
|
|
16
|
+
off<K extends keyof TEvents>(event?: K): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a type-safe event bus
|
|
20
|
+
*/
|
|
21
|
+
export declare function createEventBus<TEvents extends Record<string, unknown[]>>(): EventBus<TEvents>;
|
|
22
|
+
/**
|
|
23
|
+
* SDK-level event types
|
|
24
|
+
* Add new event types here as the SDK grows
|
|
25
|
+
*/
|
|
26
|
+
export type SDKEvents = {
|
|
27
|
+
onContextMenu: Parameters<Parameters<ExtensionAPI['onContextMenu']>[0]>;
|
|
28
|
+
onSelectionChange: Parameters<Parameters<ExtensionAPI['onSelectionChange']>[0]>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Shared SDK event bus instance
|
|
32
|
+
* Use this for SDK-wide event communication
|
|
33
|
+
*/
|
|
34
|
+
export declare const sdkEventBus: EventBus<SDKEvents>;
|
|
35
|
+
/**
|
|
36
|
+
* Create a callback handler that subscribes to the SDK event bus.
|
|
37
|
+
* Useful for building callback APIs like onContextMenu, onSelectionChange, etc.
|
|
38
|
+
*
|
|
39
|
+
* @param eventName - The SDK event to subscribe to
|
|
40
|
+
* @param ensureConnection - Optional function to ensure host connection (e.g., tryGetHost)
|
|
41
|
+
* @returns A function that accepts a callback and returns an unsubscribe function
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const onContextMenu = createCallbackHandler('extension:contextMenu', tryGetHost);
|
|
45
|
+
*/
|
|
46
|
+
export declare function createCallbackHandler<K extends keyof SDKEvents>(eventName: K, ensureConnection?: () => void): (callback: EventHandler<SDKEvents[K]>) => () => void;
|
|
47
|
+
/**
|
|
48
|
+
* Create both emitter (for childMethods) and subscriber (for callbackAPI) from a single config.
|
|
49
|
+
* This ensures event names and signatures stay in sync.
|
|
50
|
+
*
|
|
51
|
+
* @param config - Map of local method names to SDK event names
|
|
52
|
+
* @param ensureConnection - Optional function to ensure host connection
|
|
53
|
+
* @returns Object with emitters and subscribers
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* const { emitters, subscribers } = createEventPair({
|
|
57
|
+
* onContextMenuEvent: 'extension:contextMenu',
|
|
58
|
+
* onSelectionChange: 'extension:selectionChange',
|
|
59
|
+
* }, tryGetHost);
|
|
60
|
+
*
|
|
61
|
+
* // Use emitters in childMethods
|
|
62
|
+
* export const childMethods = emitters;
|
|
63
|
+
*
|
|
64
|
+
* // Use subscribers in callbackAPI
|
|
65
|
+
* export const callbackAPI = {
|
|
66
|
+
* onContextMenu: subscribers.onContextMenuEvent,
|
|
67
|
+
* onSelectionChange: subscribers.onSelectionChange,
|
|
68
|
+
* };
|
|
69
|
+
*/
|
|
70
|
+
export declare function createEventPair<TConfig extends Record<string, keyof SDKEvents>>(config: TConfig, ensureConnection?: () => void): {
|
|
71
|
+
emitters: {
|
|
72
|
+
[K in keyof TConfig]: (...args: SDKEvents[TConfig[K]]) => void;
|
|
73
|
+
};
|
|
74
|
+
subscribers: {
|
|
75
|
+
[K in keyof TConfig]: (callback: EventHandler<SDKEvents[TConfig[K]]>) => () => void;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Module
|
|
3
|
+
*/
|
|
4
|
+
export { getHost, tryGetHost, initConnection, isConnected, hasConnectionFailed, callHost, createRPCProxy, withFallback, } from './connection';
|
|
5
|
+
export { createEventBus, sdkEventBus, createCallbackHandler, createEventPair } from './event-bus';
|
|
6
|
+
export type { EventBus, EventHandler, SDKEvents } from './event-bus';
|
|
7
|
+
export { createUnifiedAPI, createRPCAction, createSDK, bootstrapSDK } from './api-factory';
|
|
8
|
+
export type { APIConfig } from './api-factory';
|
|
9
|
+
export type { HostMethods, ChildMethods } from './connection';
|