@africode/core 5.0.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/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
- package/LICENSE +623 -0
- package/README.md +442 -0
- package/bin/africode.js +73 -0
- package/bin/africode.js.1758507140 +343 -0
- package/bin/cli.ts +83 -0
- package/bin/create-africode.js +158 -0
- package/bin/scaffold.ts +219 -0
- package/components/accordion.js +183 -0
- package/components/alert.js +131 -0
- package/components/auth.js +172 -0
- package/components/avatar.js +117 -0
- package/components/badge.js +104 -0
- package/components/base.d.ts +139 -0
- package/components/base.js +184 -0
- package/components/button.js +164 -0
- package/components/card.js +137 -0
- package/components/cultural-card.js +243 -0
- package/components/divider.js +83 -0
- package/components/dropdown.js +171 -0
- package/components/error-boundary.js +155 -0
- package/components/form.js +131 -0
- package/components/grid.js +273 -0
- package/components/hero.js +138 -0
- package/components/icon.js +36 -0
- package/components/index.js +57 -0
- package/components/input.js +256 -0
- package/components/kanga-card.js +185 -0
- package/components/language-switcher.js +108 -0
- package/components/loader.js +80 -0
- package/components/modal.js +262 -0
- package/components/motion.js +84 -0
- package/components/navbar.js +236 -0
- package/components/pattern-showcase.js +225 -0
- package/components/progress.js +134 -0
- package/components/react.js +111 -0
- package/components/section.js +54 -0
- package/components/select.js +322 -0
- package/components/sidebar.js +180 -0
- package/components/skeleton.js +85 -0
- package/components/table.js +181 -0
- package/components/tabs.js +202 -0
- package/components/theme-toggle.js +82 -0
- package/components/toast.js +139 -0
- package/components/tooltip.js +167 -0
- package/core/a2ui-schema-manager.js +344 -0
- package/core/a2ui.js +431 -0
- package/core/bun-runtime.js +799 -0
- package/core/cli/commands/add.js +23 -0
- package/core/cli/commands/audit.js +58 -0
- package/core/cli/commands/build.js +137 -0
- package/core/cli/commands/create-plugin.js +241 -0
- package/core/cli/commands/dev.js +228 -0
- package/core/cli/commands/lint.js +23 -0
- package/core/cli/commands/test.js +34 -0
- package/core/cli/migrator.js +71 -0
- package/core/cli/ui.js +46 -0
- package/core/compliance.js +628 -0
- package/core/config.js +263 -0
- package/core/db-advanced.js +481 -0
- package/core/db.js +284 -0
- package/core/enhanced-hmr.js +404 -0
- package/core/errors.js +222 -0
- package/core/file-router.js +290 -0
- package/core/heartbeat.js +64 -0
- package/core/hmr-client.js +204 -0
- package/core/hmr.js +196 -0
- package/core/html.d.ts +116 -0
- package/core/html.js +160 -0
- package/core/hydration.js +52 -0
- package/core/lipa-namba-journey.js +572 -0
- package/core/motion.js +106 -0
- package/core/nida-cig-middleware.js +455 -0
- package/core/patterns.d.ts +124 -0
- package/core/patterns.js +833 -0
- package/core/plugins/index.js +312 -0
- package/core/router.js +387 -0
- package/core/sdk-client.js +62 -0
- package/core/sdk.d.ts +133 -0
- package/core/sdk.js +123 -0
- package/core/seo.js +76 -0
- package/core/server/auth-endpoints.js +339 -0
- package/core/server/auth.js +180 -0
- package/core/server/csrf.js +206 -0
- package/core/server/db.js +39 -0
- package/core/server/middleware.js +324 -0
- package/core/server/rate-limit.js +238 -0
- package/core/server/render.js +69 -0
- package/core/server/router.js +120 -0
- package/core/shim.js +28 -0
- package/core/state.d.ts +86 -0
- package/core/state.js +242 -0
- package/core/store.d.ts +122 -0
- package/core/store.js +61 -0
- package/core/validation.d.ts +233 -0
- package/core/validation.js +590 -0
- package/core/websocket.js +639 -0
- package/dist/africode.js +2905 -0
- package/dist/africode.js.map +61 -0
- package/dist/build-info.json +23 -0
- package/dist/components.js +2888 -0
- package/dist/components.js.map +58 -0
- package/dist/styles/africanity.css +322 -0
- package/dist/styles/typography.css +141 -0
- package/docs/IDE-Guide.md +50 -0
- package/package.json +110 -0
- package/src/index.ts +196 -0
- package/styles/africanity.css +322 -0
- package/styles/typography.css +141 -0
- package/templates/starter/.env.example +15 -0
- package/templates/starter/africode.config.js +40 -0
- package/templates/starter/package.json +14 -0
- package/templates/starter/src/pages/index.html +46 -0
- package/templates/starter/src/pages/index.js +32 -0
- package/templates/starter/src/styles/main.css +4 -0
- package/templates/starter-3d/.env.example +7 -0
- package/templates/starter-3d/africode.config.js +29 -0
- package/templates/starter-3d/components/af-model-viewer.js +125 -0
- package/templates/starter-3d/package.json +15 -0
- package/templates/starter-3d/src/pages/index.html +46 -0
- package/templates/starter-3d/src/pages/index.js +50 -0
- package/templates/starter-3d/src/styles/main.css +4 -0
- package/templates/starter-react/.env.example +15 -0
- package/templates/starter-react/africode.config.js +40 -0
- package/templates/starter-react/package.json +16 -0
- package/templates/starter-react/src/pages/index.html +46 -0
- package/templates/starter-react/src/pages/index.js +68 -0
- package/templates/starter-react/src/styles/main.css +4 -0
- package/templates/starter-tailwind/.env.example +15 -0
- package/templates/starter-tailwind/africode.config.js +40 -0
- package/templates/starter-tailwind/package.json +20 -0
- package/templates/starter-tailwind/src/pages/index.html +46 -0
- package/templates/starter-tailwind/src/pages/index.js +37 -0
- package/templates/starter-tailwind/src/styles/main.css +4 -0
- package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
- package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
package/core/a2ui.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2UI Protocol Implementation
|
|
3
|
+
* Agent-to-User Interface declarative protocol for generative AI
|
|
4
|
+
*
|
|
5
|
+
* Enables AI agents to return UI widgets as part of responses without
|
|
6
|
+
* executing arbitrary code, ensuring security and consistency.
|
|
7
|
+
*
|
|
8
|
+
* @module core/a2ui
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A2UI Message Types
|
|
13
|
+
*/
|
|
14
|
+
export const A2UI_TYPES = {
|
|
15
|
+
COMPONENT: 'component',
|
|
16
|
+
FORM: 'form',
|
|
17
|
+
LIST: 'list',
|
|
18
|
+
CARD: 'card',
|
|
19
|
+
MODAL: 'modal',
|
|
20
|
+
TOAST: 'toast',
|
|
21
|
+
PROGRESS: 'progress'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A2UI Component Schemas
|
|
26
|
+
*/
|
|
27
|
+
export const A2UI_SCHEMAS = {
|
|
28
|
+
component: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
id: { type: 'string' },
|
|
32
|
+
type: { type: 'string', enum: Object.values(A2UI_TYPES) },
|
|
33
|
+
props: { type: 'object' },
|
|
34
|
+
children: { type: 'array', items: { $ref: '#' } }
|
|
35
|
+
},
|
|
36
|
+
required: ['id', 'type']
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A2UI Renderer
|
|
42
|
+
* Renders A2UI messages into actual DOM elements
|
|
43
|
+
*/
|
|
44
|
+
export class A2UIRenderer {
|
|
45
|
+
constructor(rootElement) {
|
|
46
|
+
this.root = rootElement;
|
|
47
|
+
this.components = new Map();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Render an A2UI message
|
|
52
|
+
* @param {Object} message - A2UI message object
|
|
53
|
+
*/
|
|
54
|
+
async render(message) {
|
|
55
|
+
if (!message || !message.type) {
|
|
56
|
+
throw new Error('Invalid A2UI message');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const canRenderToDOM = this.root && typeof this.root.appendChild === 'function';
|
|
60
|
+
|
|
61
|
+
if (!canRenderToDOM) {
|
|
62
|
+
const placeholder = {
|
|
63
|
+
id: message.id || `a2ui-${Date.now()}`,
|
|
64
|
+
type: message.type,
|
|
65
|
+
props: message.props || {},
|
|
66
|
+
children: message.children || []
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (placeholder.id) {
|
|
70
|
+
this.components.set(placeholder.id, placeholder);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return placeholder;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const element = await this.createElement(message);
|
|
77
|
+
this.root.appendChild(element);
|
|
78
|
+
return element;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create DOM element from A2UI component
|
|
83
|
+
* @param {Object} component - A2UI component definition
|
|
84
|
+
*/
|
|
85
|
+
async createElement(component) {
|
|
86
|
+
const { type, props = {}, children = [] } = component;
|
|
87
|
+
|
|
88
|
+
let element;
|
|
89
|
+
|
|
90
|
+
switch (type) {
|
|
91
|
+
case A2UI_TYPES.COMPONENT:
|
|
92
|
+
element = await this.createCustomComponent(props);
|
|
93
|
+
break;
|
|
94
|
+
case A2UI_TYPES.FORM:
|
|
95
|
+
element = await this.createForm(props);
|
|
96
|
+
break;
|
|
97
|
+
case A2UI_TYPES.LIST:
|
|
98
|
+
element = await this.createList(props);
|
|
99
|
+
break;
|
|
100
|
+
case A2UI_TYPES.CARD:
|
|
101
|
+
element = await this.createCard(props);
|
|
102
|
+
break;
|
|
103
|
+
case A2UI_TYPES.MODAL:
|
|
104
|
+
element = await this.createModal(props);
|
|
105
|
+
break;
|
|
106
|
+
case A2UI_TYPES.TOAST:
|
|
107
|
+
element = await this.createToast(props);
|
|
108
|
+
break;
|
|
109
|
+
case A2UI_TYPES.PROGRESS:
|
|
110
|
+
element = await this.createProgress(props);
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
throw new Error(`Unknown A2UI component type: ${type}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Render children
|
|
117
|
+
if (children.length > 0) {
|
|
118
|
+
for (const child of children) {
|
|
119
|
+
const childElement = await this.createElement(child);
|
|
120
|
+
element.appendChild(childElement);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return element;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create custom AfriCode component
|
|
129
|
+
*/
|
|
130
|
+
async createCustomComponent({ tag, ...props }) {
|
|
131
|
+
const element = document.createElement(tag || 'div');
|
|
132
|
+
Object.assign(element, props);
|
|
133
|
+
return element;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Create form component
|
|
138
|
+
*/
|
|
139
|
+
async createForm({ fields = [], onSubmit }) {
|
|
140
|
+
const form = document.createElement('af-form');
|
|
141
|
+
|
|
142
|
+
for (const field of fields) {
|
|
143
|
+
const input = document.createElement('af-input');
|
|
144
|
+
input.setAttribute('name', field.name);
|
|
145
|
+
input.setAttribute('placeholder', field.placeholder || '');
|
|
146
|
+
input.setAttribute('type', field.type || 'text');
|
|
147
|
+
form.appendChild(input);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (onSubmit) {
|
|
151
|
+
form.addEventListener('submit', onSubmit);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return form;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create list component
|
|
159
|
+
*/
|
|
160
|
+
async createList({ items = [] }) {
|
|
161
|
+
const list = document.createElement('af-grid');
|
|
162
|
+
|
|
163
|
+
for (const item of items) {
|
|
164
|
+
const card = await this.createCard(item);
|
|
165
|
+
list.appendChild(card);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return list;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create card component
|
|
173
|
+
*/
|
|
174
|
+
async createCard({ title, content, image }) {
|
|
175
|
+
const card = document.createElement('af-card');
|
|
176
|
+
|
|
177
|
+
if (title) {
|
|
178
|
+
const titleEl = document.createElement('h3');
|
|
179
|
+
titleEl.textContent = title;
|
|
180
|
+
card.appendChild(titleEl);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (content) {
|
|
184
|
+
const contentEl = document.createElement('p');
|
|
185
|
+
contentEl.textContent = content;
|
|
186
|
+
card.appendChild(contentEl);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (image) {
|
|
190
|
+
const img = document.createElement('img');
|
|
191
|
+
img.src = image;
|
|
192
|
+
img.alt = title || 'Card image';
|
|
193
|
+
card.appendChild(img);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return card;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Create modal component
|
|
201
|
+
*/
|
|
202
|
+
async createModal({ title, content, actions = [] }) {
|
|
203
|
+
const modal = document.createElement('af-modal');
|
|
204
|
+
modal.setAttribute('open', '');
|
|
205
|
+
|
|
206
|
+
if (title) {
|
|
207
|
+
const titleEl = document.createElement('h2');
|
|
208
|
+
titleEl.slot = 'header';
|
|
209
|
+
titleEl.textContent = title;
|
|
210
|
+
modal.appendChild(titleEl);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (content) {
|
|
214
|
+
const contentEl = document.createElement('p');
|
|
215
|
+
contentEl.textContent = content;
|
|
216
|
+
modal.appendChild(contentEl);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const actionsContainer = document.createElement('div');
|
|
220
|
+
actionsContainer.slot = 'footer';
|
|
221
|
+
|
|
222
|
+
for (const action of actions) {
|
|
223
|
+
const button = document.createElement('af-button');
|
|
224
|
+
button.textContent = action.label;
|
|
225
|
+
if (action.primary) {
|
|
226
|
+
button.setAttribute('variant', 'primary');
|
|
227
|
+
}
|
|
228
|
+
if (action.onClick) {
|
|
229
|
+
button.addEventListener('click', action.onClick);
|
|
230
|
+
}
|
|
231
|
+
actionsContainer.appendChild(button);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
modal.appendChild(actionsContainer);
|
|
235
|
+
return modal;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Create toast component
|
|
240
|
+
*/
|
|
241
|
+
async createToast({ message, type = 'info', duration = 3000 }) {
|
|
242
|
+
const toast = document.createElement('af-toast');
|
|
243
|
+
toast.setAttribute('type', type);
|
|
244
|
+
toast.textContent = message;
|
|
245
|
+
|
|
246
|
+
// Auto-remove after duration
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
toast.remove();
|
|
249
|
+
}, duration);
|
|
250
|
+
|
|
251
|
+
return toast;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Create progress component
|
|
256
|
+
*/
|
|
257
|
+
async createProgress({ value = 0, max = 100, label }) {
|
|
258
|
+
const progress = document.createElement('af-progress');
|
|
259
|
+
progress.setAttribute('value', value);
|
|
260
|
+
progress.setAttribute('max', max);
|
|
261
|
+
|
|
262
|
+
if (label) {
|
|
263
|
+
progress.setAttribute('label', label);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return progress;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Update existing component
|
|
271
|
+
*/
|
|
272
|
+
async update(id, newProps) {
|
|
273
|
+
const element = this.components.get(id);
|
|
274
|
+
if (element) {
|
|
275
|
+
Object.assign(element, newProps);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Remove component
|
|
281
|
+
*/
|
|
282
|
+
remove(id) {
|
|
283
|
+
const element = this.components.get(id);
|
|
284
|
+
if (element) {
|
|
285
|
+
element.remove();
|
|
286
|
+
this.components.delete(id);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* A2UI Protocol Handler
|
|
293
|
+
* Processes streaming A2UI messages from AI agents
|
|
294
|
+
*/
|
|
295
|
+
export class A2UIProtocol {
|
|
296
|
+
constructor(renderer) {
|
|
297
|
+
this.renderer = renderer;
|
|
298
|
+
this.messageQueue = [];
|
|
299
|
+
this.messageHandlers = [];
|
|
300
|
+
this.isProcessing = false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Register a message handler callback
|
|
305
|
+
* @param {Function} callback - Handler to receive processed messages
|
|
306
|
+
*/
|
|
307
|
+
onMessage(callback) {
|
|
308
|
+
if (typeof callback === 'function') {
|
|
309
|
+
this.messageHandlers.push(callback);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Send a message into the A2UI system
|
|
315
|
+
* @param {Object|string} message - A2UI message
|
|
316
|
+
*/
|
|
317
|
+
async sendMessage(message) {
|
|
318
|
+
return this.processMessage(message);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Process incoming A2UI message
|
|
323
|
+
* @param {Object|string} message - A2UI message
|
|
324
|
+
*/
|
|
325
|
+
async processMessage(message) {
|
|
326
|
+
if (typeof message === 'string') {
|
|
327
|
+
try {
|
|
328
|
+
message = JSON.parse(message);
|
|
329
|
+
} catch {
|
|
330
|
+
console.warn('Invalid A2UI message format:', message);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.messageQueue.push(message);
|
|
336
|
+
await this.processQueue();
|
|
337
|
+
|
|
338
|
+
for (const handler of this.messageHandlers) {
|
|
339
|
+
handler(message);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Process queued messages
|
|
345
|
+
*/
|
|
346
|
+
async processQueue() {
|
|
347
|
+
if (this.isProcessing || this.messageQueue.length === 0) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
this.isProcessing = true;
|
|
352
|
+
|
|
353
|
+
while (this.messageQueue.length > 0) {
|
|
354
|
+
const message = this.messageQueue.shift();
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
await this.renderer.render(message);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.error('A2UI render error:', error);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
this.isProcessing = false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Stream handler for real-time AI responses
|
|
368
|
+
* @param {ReadableStream} stream - AI response stream
|
|
369
|
+
*/
|
|
370
|
+
async handleStream(stream) {
|
|
371
|
+
const reader = stream.getReader();
|
|
372
|
+
const decoder = new TextDecoder();
|
|
373
|
+
|
|
374
|
+
let buffer = '';
|
|
375
|
+
|
|
376
|
+
while (true) {
|
|
377
|
+
const { done, value } = await reader.read();
|
|
378
|
+
|
|
379
|
+
if (done) {
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
buffer += decoder.decode(value, { stream: true });
|
|
384
|
+
|
|
385
|
+
// Process complete messages
|
|
386
|
+
const messages = buffer.split('\n');
|
|
387
|
+
buffer = messages.pop(); // Keep incomplete message in buffer
|
|
388
|
+
|
|
389
|
+
for (const message of messages) {
|
|
390
|
+
if (message.trim()) {
|
|
391
|
+
await this.processMessage(message.trim());
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Process any remaining buffer
|
|
397
|
+
if (buffer.trim()) {
|
|
398
|
+
await this.processMessage(buffer.trim());
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Initialize A2UI system
|
|
405
|
+
* @param {HTMLElement} root - Root element for rendering
|
|
406
|
+
*/
|
|
407
|
+
export function initA2UI(root) {
|
|
408
|
+
// Handle server-side rendering (no DOM)
|
|
409
|
+
if (typeof document === 'undefined') {
|
|
410
|
+
root = null;
|
|
411
|
+
} else {
|
|
412
|
+
root = root || document.body;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const renderer = new A2UIRenderer(root);
|
|
416
|
+
const protocol = new A2UIProtocol(renderer);
|
|
417
|
+
|
|
418
|
+
// Make globally available for AI agents
|
|
419
|
+
if (typeof window !== 'undefined') {
|
|
420
|
+
window.a2ui = {
|
|
421
|
+
render: (message) => protocol.processMessage(message),
|
|
422
|
+
handleStream: (stream) => protocol.handleStream(stream),
|
|
423
|
+
update: (id, props) => renderer.update(id, props),
|
|
424
|
+
remove: (id) => renderer.remove(id),
|
|
425
|
+
sendMessage: (message) => protocol.sendMessage(message),
|
|
426
|
+
onMessage: (callback) => protocol.onMessage(callback)
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return { renderer, protocol };
|
|
431
|
+
}
|