@oasisomniverse/web8-api 1.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/README.md +151 -0
- package/docs/README.md +20 -0
- package/docs/modules/Mesh.md +312 -0
- package/docs/modules/ProtocolBridge.md +123 -0
- package/index.d.ts +44 -0
- package/index.js +3 -0
- package/index.mjs +4 -0
- package/package.json +66 -0
- package/src/core/httpClient.js +110 -0
- package/src/core/routeHelper.js +85 -0
- package/src/core/tokenStore.js +52 -0
- package/src/core/types.d.ts +18 -0
- package/src/index.js +45 -0
- package/src/modules/Mesh.d.ts +24 -0
- package/src/modules/Mesh.js +33 -0
- package/src/modules/ProtocolBridge.d.ts +12 -0
- package/src/modules/ProtocolBridge.js +25 -0
- package/src/modules/index.js +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @oasisomniverse/web8-api
|
|
2
|
+
|
|
3
|
+
Isomorphic (Node 18+ and browser) JavaScript/TypeScript-friendly client for the
|
|
4
|
+
**WEB8 OASIS Galactic Mesh API** - fractal holonic mesh node registration,
|
|
5
|
+
link/route computation, self-healing message relay, and protocol-bridge
|
|
6
|
+
translation between external wire formats and the unified `MeshMessage`
|
|
7
|
+
envelope, built on the OASIS2 WEB8 WebAPI.
|
|
8
|
+
|
|
9
|
+
Zero dependencies. Wraps the global `fetch`. Works the same in Node and the
|
|
10
|
+
browser.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @oasisomniverse/web8-api
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
const { Web8Client } = require('@oasisomniverse/web8-api');
|
|
22
|
+
// or: import { Web8Client } from '@oasisomniverse/web8-api';
|
|
23
|
+
|
|
24
|
+
const web8 = new Web8Client({ baseUrl: 'https://api.web8.oasisomniverse.one' });
|
|
25
|
+
|
|
26
|
+
const { isError, message, result } = await web8.mesh.registerNode({ name: 'edge-node-1' });
|
|
27
|
+
if (isError) throw new Error(message);
|
|
28
|
+
console.log(result);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Calling any endpoint
|
|
32
|
+
|
|
33
|
+
Every controller on the OASIS2 WEB8 WebAPI is reachable as a lowerCamel
|
|
34
|
+
property on the client (`web8.mesh`, `web8.protocolBridge`). Every generated
|
|
35
|
+
method takes a single args object:
|
|
36
|
+
|
|
37
|
+
- Any key matching a `{token}` in the route template is consumed and
|
|
38
|
+
substituted into the URL (case-insensitive match).
|
|
39
|
+
- Any remaining keys become the query string (GET/DELETE) or JSON body
|
|
40
|
+
(POST/PUT) - **matching the real `[FromQuery]`/`[FromBody]` binding of the
|
|
41
|
+
underlying C# action**, not just the HTTP verb. `endpoints.json` records
|
|
42
|
+
exactly which arg names are query-bound (and which single arg is the whole
|
|
43
|
+
body, for actions like `TranslateInbound` whose entire body is one
|
|
44
|
+
primitive `[FromBody] string`) per operation - see [`docs/`](./docs/README.md)
|
|
45
|
+
for the per-method breakdown.
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
// POST v1/mesh/nodes/{nodeId}/heartbeat -> nodeId consumed as a route token
|
|
49
|
+
await web8.mesh.heartbeat({ nodeId });
|
|
50
|
+
|
|
51
|
+
// GET v1/mesh/route -> all args become query params
|
|
52
|
+
const route = await web8.mesh.computeRoute({ sourceNodeId, destinationNodeId });
|
|
53
|
+
|
|
54
|
+
// POST v1/mesh/links -> nodeAId/nodeBId/latencyMs are all [FromQuery] even
|
|
55
|
+
// though this is a POST, so they're sent on the URL - no JSON body is sent
|
|
56
|
+
await web8.mesh.addLink({ nodeAId, nodeBId, latencyMs: 12 });
|
|
57
|
+
|
|
58
|
+
// POST v1/protocol-bridge/translate-inbound -> format/sourceNodeId/destinationNodeId
|
|
59
|
+
// are [FromQuery]; rawPayload is the single [FromBody] string, so it becomes
|
|
60
|
+
// the raw JSON body itself (not wrapped in an object)
|
|
61
|
+
const inbound = await web8.protocolBridge.translateInbound({
|
|
62
|
+
format: 'Mqtt', sourceNodeId, destinationNodeId, rawPayload: '{"temp":21.5}'
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Every response has the shape:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
interface OASISResponse<T = any> {
|
|
70
|
+
isError: boolean;
|
|
71
|
+
message: string | null;
|
|
72
|
+
result: T;
|
|
73
|
+
raw: any;
|
|
74
|
+
statusCode: number;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Auth
|
|
79
|
+
|
|
80
|
+
WEB8 is an internal galactic-mesh/protocol-bridge layer that sits behind the
|
|
81
|
+
same OASIS avatar identity as WEB4/WEB5/WEB6/WEB7 - it has no avatar/login
|
|
82
|
+
endpoints of its own. Reuse a JWT you've already obtained elsewhere (e.g.
|
|
83
|
+
from `web4-oasis-api`'s `client.auth.login()`):
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
web8.setToken(jwtToken);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Module examples
|
|
90
|
+
|
|
91
|
+
### Mesh (`web8.mesh`)
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
const nodeA = await web8.mesh.registerNode({ name: 'edge-node-a' });
|
|
95
|
+
const nodeB = await web8.mesh.registerNode({ name: 'edge-node-b' });
|
|
96
|
+
await web8.mesh.addLink({ nodeAId: nodeA.result.id, nodeBId: nodeB.result.id, latencyMs: 12 });
|
|
97
|
+
await web8.mesh.heartbeat({ nodeId: nodeA.result.id });
|
|
98
|
+
|
|
99
|
+
const route = await web8.mesh.computeRoute({ sourceNodeId: nodeA.result.id, destinationNodeId: nodeB.result.id });
|
|
100
|
+
const relay = await web8.mesh.sendMessage({ sourceNodeId: nodeA.result.id, destinationNodeId: nodeB.result.id, payload: 'hello' });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Protocol Bridge (`web8.protocolBridge`)
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
const inbound = await web8.protocolBridge.translateInbound({
|
|
107
|
+
format: 'Mqtt',
|
|
108
|
+
sourceNodeId,
|
|
109
|
+
destinationNodeId,
|
|
110
|
+
rawPayload: '{"temp":21.5}'
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// targetFormat is [FromQuery]; the rest of the args become the MeshMessage
|
|
114
|
+
// JSON body directly (not nested under a "message" key)
|
|
115
|
+
const outbound = await web8.protocolBridge.translateOutbound({ targetFormat: 'Json', ...inbound.result });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Module reference
|
|
119
|
+
|
|
120
|
+
2 modules, 8 operations in total. Full per-method tables live in
|
|
121
|
+
[`docs/`](./docs/README.md).
|
|
122
|
+
|
|
123
|
+
| Client property | Route prefix | Operations |
|
|
124
|
+
| --- | --- | --- |
|
|
125
|
+
| `web8.mesh` | `v1/mesh` | 6 |
|
|
126
|
+
| `web8.protocolBridge` | `v1/protocol-bridge` | 2 |
|
|
127
|
+
|
|
128
|
+
See [`docs/README.md`](./docs/README.md) for the full generated reference,
|
|
129
|
+
or [`docs/modules/`](./docs/modules) for per-module method tables with
|
|
130
|
+
parameter and route details.
|
|
131
|
+
|
|
132
|
+
## Regenerating
|
|
133
|
+
|
|
134
|
+
The generated modules, type declarations and docs are produced from
|
|
135
|
+
`endpoints.json` (extracted from the WEB8 WebAPI controller source):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run generate # src/modules/*.js + src/modules/index.js
|
|
139
|
+
npm run types # src/modules/*.d.ts + index.d.ts + src/core/types.d.ts
|
|
140
|
+
npm run docs # docs/README.md + docs/modules/*.md
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Testing
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm test
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# WEB8 Galactic Mesh & Protocol Bridge API — JavaScript SDK Reference
|
|
2
|
+
|
|
3
|
+
Generated from `endpoints.json` (extracted from the WebAPI controllers) by
|
|
4
|
+
`scripts/generate-full-docs.js`. Regenerate the full pipeline after the API
|
|
5
|
+
changes:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
node scripts/extract-endpoints.js
|
|
9
|
+
node scripts/generate-modules.js
|
|
10
|
+
node scripts/generate-full-docs.js
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- [Module Reference](#module-reference) (2 modules, 8 operations)
|
|
14
|
+
|
|
15
|
+
## Module Reference
|
|
16
|
+
|
|
17
|
+
| Client property | Route prefix | Operations |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| [`web8.mesh`](modules/Mesh.md) | `v1/mesh` | 6 |
|
|
20
|
+
| [`web8.protocolBridge`](modules/ProtocolBridge.md) | `v1/protocol-bridge` | 2 |
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Mesh — `web8.mesh`
|
|
2
|
+
|
|
3
|
+
Source controller: [`MeshController.cs`](https://github.com/NextGenSoftwareUK/OASIS2/blob/main/WEB8/NextGenSoftware.OASIS.Web8.WebAPI/Controllers/MeshController.cs)
|
|
4
|
+
Route prefix: `v1/mesh`
|
|
5
|
+
6 operation(s).
|
|
6
|
+
|
|
7
|
+
Every method takes a single args object: any key matching a `{token}` in the route is substituted into the URL; everything else becomes the query string (GET/DELETE) or JSON body (POST/PUT). Every call resolves to the standard OASIS envelope:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
{
|
|
11
|
+
isError: boolean;
|
|
12
|
+
isWarning: boolean;
|
|
13
|
+
message: string;
|
|
14
|
+
errorCode?: string;
|
|
15
|
+
result: T; // see each endpoint's Response section below
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Operations
|
|
20
|
+
|
|
21
|
+
### `addLink`
|
|
22
|
+
|
|
23
|
+
**POST** `v1/mesh/links`
|
|
24
|
+
|
|
25
|
+
**Request**
|
|
26
|
+
|
|
27
|
+
Body fields:
|
|
28
|
+
|
|
29
|
+
| Field | Type |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `nodeAId` | `Guid` |
|
|
32
|
+
| `nodeBId` | `Guid` |
|
|
33
|
+
| `latencyMs` | `double (optional)` |
|
|
34
|
+
|
|
35
|
+
**Response**
|
|
36
|
+
|
|
37
|
+
Standard `OASISResult` envelope (see top of this page) with:
|
|
38
|
+
|
|
39
|
+
`result` type: `bool`
|
|
40
|
+
|
|
41
|
+
**Example**
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const { isError, message, result } = await web8.mesh.addLink({
|
|
45
|
+
nodeAId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
|
46
|
+
nodeBId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
|
47
|
+
latencyMs: 1.0
|
|
48
|
+
});
|
|
49
|
+
if (isError) throw new Error(message);
|
|
50
|
+
console.log(result);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Example response:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"isError": false,
|
|
58
|
+
"message": "",
|
|
59
|
+
"result": true
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### `computeRoute`
|
|
66
|
+
|
|
67
|
+
**GET** `v1/mesh/route`
|
|
68
|
+
|
|
69
|
+
**Request**
|
|
70
|
+
|
|
71
|
+
Query parameters:
|
|
72
|
+
|
|
73
|
+
| Field | Type |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `sourceNodeId` | `Guid` |
|
|
76
|
+
| `destinationNodeId` | `Guid` |
|
|
77
|
+
|
|
78
|
+
**Response**
|
|
79
|
+
|
|
80
|
+
Standard `OASISResult` envelope (see top of this page) with:
|
|
81
|
+
|
|
82
|
+
`result` type: `Guid` (array)
|
|
83
|
+
|
|
84
|
+
**Example**
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const { isError, message, result } = await web8.mesh.computeRoute({
|
|
88
|
+
sourceNodeId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
|
89
|
+
destinationNodeId: '3fa85f64-5717-4562-b3fc-2c963f66afa6'
|
|
90
|
+
});
|
|
91
|
+
if (isError) throw new Error(message);
|
|
92
|
+
console.log(result);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Example response:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"isError": false,
|
|
100
|
+
"message": "",
|
|
101
|
+
"result": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### `getNodes`
|
|
108
|
+
|
|
109
|
+
**GET** `v1/mesh/nodes`
|
|
110
|
+
|
|
111
|
+
**Request**
|
|
112
|
+
|
|
113
|
+
No request body.
|
|
114
|
+
|
|
115
|
+
**Response**
|
|
116
|
+
|
|
117
|
+
Standard `OASISResult` envelope (see top of this page) with:
|
|
118
|
+
|
|
119
|
+
`result` type: `GalacticNode` (array)
|
|
120
|
+
|
|
121
|
+
| Field | Type |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| `Id` | `Guid` |
|
|
124
|
+
| `Name` | `string` |
|
|
125
|
+
| `Type` | `NodeType` |
|
|
126
|
+
| `EndpointUrl` | `string` |
|
|
127
|
+
| `IsSovereign` | `bool` |
|
|
128
|
+
| `RegisteredUtc` | `DateTime` |
|
|
129
|
+
| `LastSeenUtc` | `DateTime` |
|
|
130
|
+
|
|
131
|
+
**Example**
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
const { isError, message, result } = await web8.mesh.getNodes({});
|
|
135
|
+
if (isError) throw new Error(message);
|
|
136
|
+
console.log(result);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Example response:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"isError": false,
|
|
144
|
+
"message": "",
|
|
145
|
+
"result": [{ "Id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "Name": "example string", "Type": { }, "EndpointUrl": "example string", "IsSovereign": true, "RegisteredUtc": "2026-01-01T00:00:00Z", "LastSeenUtc": "2026-01-01T00:00:00Z" }]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### `heartbeat`
|
|
152
|
+
|
|
153
|
+
**POST** `v1/mesh/nodes/{nodeId}/heartbeat`
|
|
154
|
+
|
|
155
|
+
Route parameters:
|
|
156
|
+
|
|
157
|
+
| Field | Type |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `nodeId` | `Guid` |
|
|
160
|
+
|
|
161
|
+
**Request**
|
|
162
|
+
|
|
163
|
+
No request body.
|
|
164
|
+
|
|
165
|
+
**Response**
|
|
166
|
+
|
|
167
|
+
Standard `OASISResult` envelope (see top of this page) with:
|
|
168
|
+
|
|
169
|
+
`result` type: `bool`
|
|
170
|
+
|
|
171
|
+
**Example**
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
const { isError, message, result } = await web8.mesh.heartbeat({
|
|
175
|
+
nodeId: '<nodeId>'
|
|
176
|
+
});
|
|
177
|
+
if (isError) throw new Error(message);
|
|
178
|
+
console.log(result);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Example response:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"isError": false,
|
|
186
|
+
"message": "",
|
|
187
|
+
"result": true
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### `registerNode`
|
|
194
|
+
|
|
195
|
+
The WEB8 fractal holonic mesh - register nodes, declare links, compute routes and relay messages with self-healing failover.
|
|
196
|
+
|
|
197
|
+
**POST** `v1/mesh/nodes`
|
|
198
|
+
|
|
199
|
+
**Request**
|
|
200
|
+
|
|
201
|
+
Body type: `GalacticNode`
|
|
202
|
+
|
|
203
|
+
| Field | Type |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| `Id` | `Guid` |
|
|
206
|
+
| `Name` | `string` |
|
|
207
|
+
| `Type` | `NodeType` |
|
|
208
|
+
| `EndpointUrl` | `string` |
|
|
209
|
+
| `IsSovereign` | `bool` |
|
|
210
|
+
| `RegisteredUtc` | `DateTime` |
|
|
211
|
+
| `LastSeenUtc` | `DateTime` |
|
|
212
|
+
|
|
213
|
+
**Response**
|
|
214
|
+
|
|
215
|
+
Standard `OASISResult` envelope (see top of this page) with:
|
|
216
|
+
|
|
217
|
+
`result` type: `GalacticNode`
|
|
218
|
+
|
|
219
|
+
| Field | Type |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| `Id` | `Guid` |
|
|
222
|
+
| `Name` | `string` |
|
|
223
|
+
| `Type` | `NodeType` |
|
|
224
|
+
| `EndpointUrl` | `string` |
|
|
225
|
+
| `IsSovereign` | `bool` |
|
|
226
|
+
| `RegisteredUtc` | `DateTime` |
|
|
227
|
+
| `LastSeenUtc` | `DateTime` |
|
|
228
|
+
|
|
229
|
+
**Example**
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
const { isError, message, result } = await web8.mesh.registerNode({
|
|
233
|
+
id: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
234
|
+
name: "example string",
|
|
235
|
+
type: { },
|
|
236
|
+
endpointUrl: "example string",
|
|
237
|
+
isSovereign: true,
|
|
238
|
+
registeredUtc: "2026-01-01T00:00:00Z",
|
|
239
|
+
lastSeenUtc: "2026-01-01T00:00:00Z"
|
|
240
|
+
});
|
|
241
|
+
if (isError) throw new Error(message);
|
|
242
|
+
console.log(result);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Example response:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"isError": false,
|
|
250
|
+
"message": "",
|
|
251
|
+
"result": { "Id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "Name": "example string", "Type": { }, "EndpointUrl": "example string", "IsSovereign": true, "RegisteredUtc": "2026-01-01T00:00:00Z", "LastSeenUtc": "2026-01-01T00:00:00Z" }
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
### `sendMessage`
|
|
258
|
+
|
|
259
|
+
Routes and relays a message hop-by-hop to its destination, self-healing around any failed/stale node.
|
|
260
|
+
|
|
261
|
+
**POST** `v1/mesh/send`
|
|
262
|
+
|
|
263
|
+
**Request**
|
|
264
|
+
|
|
265
|
+
Body type: `MeshMessage`
|
|
266
|
+
|
|
267
|
+
| Field | Type |
|
|
268
|
+
| --- | --- |
|
|
269
|
+
| `Id` | `Guid` |
|
|
270
|
+
| `SourceNodeId` | `Guid` |
|
|
271
|
+
| `DestinationNodeId` | `Guid` |
|
|
272
|
+
| `Payload` | `string` |
|
|
273
|
+
| `Ttl` | `int` |
|
|
274
|
+
|
|
275
|
+
**Response**
|
|
276
|
+
|
|
277
|
+
Standard `OASISResult` envelope (see top of this page) with:
|
|
278
|
+
|
|
279
|
+
`result` type: `MeshRouteResult`
|
|
280
|
+
|
|
281
|
+
| Field | Type |
|
|
282
|
+
| --- | --- |
|
|
283
|
+
| `MessageId` | `Guid` |
|
|
284
|
+
| `Path` | `List<Guid>` |
|
|
285
|
+
| `TotalLatencyMs` | `double` |
|
|
286
|
+
| `Delivered` | `bool` |
|
|
287
|
+
| `RelayLog` | `List<string>` |
|
|
288
|
+
|
|
289
|
+
**Example**
|
|
290
|
+
|
|
291
|
+
```js
|
|
292
|
+
const { isError, message, result } = await web8.mesh.sendMessage({
|
|
293
|
+
id: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
294
|
+
sourceNodeId: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
295
|
+
destinationNodeId: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
296
|
+
payload: "example string",
|
|
297
|
+
ttl: 1
|
|
298
|
+
});
|
|
299
|
+
if (isError) throw new Error(message);
|
|
300
|
+
console.log(result);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Example response:
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
{
|
|
307
|
+
"isError": false,
|
|
308
|
+
"message": "",
|
|
309
|
+
"result": { "MessageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "Path": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"], "TotalLatencyMs": 1.0, "Delivered": true, "RelayLog": ["example string"] }
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# ProtocolBridge — `web8.protocolBridge`
|
|
2
|
+
|
|
3
|
+
Source controller: [`ProtocolBridgeController.cs`](https://github.com/NextGenSoftwareUK/OASIS2/blob/main/WEB8/NextGenSoftware.OASIS.Web8.WebAPI/Controllers/ProtocolBridgeController.cs)
|
|
4
|
+
Route prefix: `v1/protocol-bridge`
|
|
5
|
+
2 operation(s).
|
|
6
|
+
|
|
7
|
+
Every method takes a single args object: any key matching a `{token}` in the route is substituted into the URL; everything else becomes the query string (GET/DELETE) or JSON body (POST/PUT). Every call resolves to the standard OASIS envelope:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
{
|
|
11
|
+
isError: boolean;
|
|
12
|
+
isWarning: boolean;
|
|
13
|
+
message: string;
|
|
14
|
+
errorCode?: string;
|
|
15
|
+
result: T; // see each endpoint's Response section below
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Operations
|
|
20
|
+
|
|
21
|
+
### `translateInbound`
|
|
22
|
+
|
|
23
|
+
Translates any external system's wire format into the unified WEB8 MeshMessage envelope and back.
|
|
24
|
+
|
|
25
|
+
**POST** `v1/protocol-bridge/translate-inbound`
|
|
26
|
+
|
|
27
|
+
**Request**
|
|
28
|
+
|
|
29
|
+
Body fields:
|
|
30
|
+
|
|
31
|
+
| Field | Type |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `format` | `BridgeFormat` |
|
|
34
|
+
| `sourceNodeId` | `Guid` |
|
|
35
|
+
| `destinationNodeId` | `Guid` |
|
|
36
|
+
| `rawPayload` | `string` |
|
|
37
|
+
|
|
38
|
+
**Response**
|
|
39
|
+
|
|
40
|
+
Standard `OASISResult` envelope (see top of this page) with:
|
|
41
|
+
|
|
42
|
+
`result` type: `MeshMessage`
|
|
43
|
+
|
|
44
|
+
| Field | Type |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `Id` | `Guid` |
|
|
47
|
+
| `SourceNodeId` | `Guid` |
|
|
48
|
+
| `DestinationNodeId` | `Guid` |
|
|
49
|
+
| `Payload` | `string` |
|
|
50
|
+
| `Ttl` | `int` |
|
|
51
|
+
|
|
52
|
+
**Example**
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
const { isError, message, result } = await web8.protocolBridge.translateInbound({
|
|
56
|
+
format: '<format>',
|
|
57
|
+
sourceNodeId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
|
58
|
+
destinationNodeId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
|
59
|
+
rawPayload: 'example string'
|
|
60
|
+
});
|
|
61
|
+
if (isError) throw new Error(message);
|
|
62
|
+
console.log(result);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Example response:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"isError": false,
|
|
70
|
+
"message": "",
|
|
71
|
+
"result": { "Id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "SourceNodeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "DestinationNodeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "Payload": "example string", "Ttl": 1 }
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### `translateOutbound`
|
|
78
|
+
|
|
79
|
+
**POST** `v1/protocol-bridge/translate-outbound`
|
|
80
|
+
|
|
81
|
+
**Request**
|
|
82
|
+
|
|
83
|
+
Body type: `MeshMessage`
|
|
84
|
+
|
|
85
|
+
| Field | Type |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `Id` | `Guid` |
|
|
88
|
+
| `SourceNodeId` | `Guid` |
|
|
89
|
+
| `DestinationNodeId` | `Guid` |
|
|
90
|
+
| `Payload` | `string` |
|
|
91
|
+
| `Ttl` | `int` |
|
|
92
|
+
|
|
93
|
+
**Response**
|
|
94
|
+
|
|
95
|
+
Standard `OASISResult` envelope (see top of this page) with:
|
|
96
|
+
|
|
97
|
+
`result` type: `string`
|
|
98
|
+
|
|
99
|
+
**Example**
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const { isError, message, result } = await web8.protocolBridge.translateOutbound({
|
|
103
|
+
targetFormat: '<targetFormat>',
|
|
104
|
+
id: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
105
|
+
sourceNodeId: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
106
|
+
destinationNodeId: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
107
|
+
payload: "example string",
|
|
108
|
+
ttl: 1
|
|
109
|
+
});
|
|
110
|
+
if (isError) throw new Error(message);
|
|
111
|
+
console.log(result);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Example response:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"isError": false,
|
|
119
|
+
"message": "",
|
|
120
|
+
"result": "example string"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-types.js - do not hand-edit.
|
|
2
|
+
import type { OASISResponse, OASISSession } from './src/core/types';
|
|
3
|
+
import type { MeshModule } from './src/modules/Mesh';
|
|
4
|
+
import type { ProtocolBridgeModule } from './src/modules/ProtocolBridge';
|
|
5
|
+
|
|
6
|
+
export type { OASISResponse, OASISSession };
|
|
7
|
+
|
|
8
|
+
export interface Web8ClientOptions {
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
persistSession?: boolean;
|
|
11
|
+
fetchImpl?: typeof fetch;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export declare class HttpClient {
|
|
15
|
+
constructor(options?: { baseUrl?: string; tokenStore?: unknown; fetchImpl?: typeof fetch });
|
|
16
|
+
setBaseUrl(baseUrl: string): void;
|
|
17
|
+
request(verb: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, options?: Record<string, any>): Promise<OASISResponse>;
|
|
18
|
+
get(path: string, options?: Record<string, any>): Promise<OASISResponse>;
|
|
19
|
+
post(path: string, options?: Record<string, any>): Promise<OASISResponse>;
|
|
20
|
+
put(path: string, options?: Record<string, any>): Promise<OASISResponse>;
|
|
21
|
+
delete(path: string, options?: Record<string, any>): Promise<OASISResponse>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export declare class TokenStore {
|
|
25
|
+
constructor(options?: { persist?: boolean });
|
|
26
|
+
getSession(): OASISSession | null;
|
|
27
|
+
getToken(): string | null;
|
|
28
|
+
setSession(session: OASISSession | null): void;
|
|
29
|
+
clear(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export declare const DEFAULT_BASE_URL: string;
|
|
33
|
+
|
|
34
|
+
export declare class Web8Client {
|
|
35
|
+
constructor(options?: Web8ClientOptions);
|
|
36
|
+
readonly http: HttpClient;
|
|
37
|
+
readonly tokenStore: TokenStore;
|
|
38
|
+
readonly mesh: MeshModule;
|
|
39
|
+
readonly protocolBridge: ProtocolBridgeModule;
|
|
40
|
+
setBaseUrl(baseUrl: string): void;
|
|
41
|
+
setToken(jwtToken: string, sessionExtras?: Partial<OASISSession>): void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default Web8Client;
|
package/index.js
ADDED
package/index.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oasisomniverse/web8-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"publishConfig": { "access": "public" },
|
|
5
|
+
"description": "Isomorphic (Node + browser) JavaScript/TypeScript-friendly client for the WEB8 OASIS Galactic Mesh API - fractal holonic mesh node registration, link/route computation, self-healing message relay, and protocol-bridge translation between external wire formats and the unified MeshMessage envelope, built on the OASIS2 WEB8 WebAPI.",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "index.mjs",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"import": "./index.mjs",
|
|
13
|
+
"require": "./index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"index.js",
|
|
21
|
+
"index.mjs",
|
|
22
|
+
"index.d.ts",
|
|
23
|
+
"src",
|
|
24
|
+
"docs"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"generate": "node scripts/generate-modules.js",
|
|
28
|
+
"docs": "node scripts/generate-docs.js",
|
|
29
|
+
"types": "node scripts/generate-types.js",
|
|
30
|
+
"test": "node --test test"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/NextGenSoftwareUK/OASIS-API-Javascript-Package-WEB8.git"
|
|
35
|
+
},
|
|
36
|
+
"author": "NextGen Software Ltd",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"keywords": [
|
|
39
|
+
"WEB8 Galactic Mesh API",
|
|
40
|
+
"WEB8 WebAPI",
|
|
41
|
+
"OASIS Galactic Mesh",
|
|
42
|
+
"Holonic Mesh",
|
|
43
|
+
"Self-Healing Routing",
|
|
44
|
+
"Protocol Bridge",
|
|
45
|
+
"Mesh Networking",
|
|
46
|
+
"OASIS API",
|
|
47
|
+
"WEB 4",
|
|
48
|
+
"WEB 5",
|
|
49
|
+
"WEB 6",
|
|
50
|
+
"WEB 7",
|
|
51
|
+
"WEB 8",
|
|
52
|
+
"Blockchain",
|
|
53
|
+
"Interoperable",
|
|
54
|
+
"Metaverse",
|
|
55
|
+
"Aggregation",
|
|
56
|
+
"HOT-Swappable-Architecture",
|
|
57
|
+
"Decentralised",
|
|
58
|
+
"Distributed",
|
|
59
|
+
"Abstraction-Layer",
|
|
60
|
+
"Multi-Network",
|
|
61
|
+
"SSO",
|
|
62
|
+
"Agent-Centric",
|
|
63
|
+
"GOD-Protocol/API",
|
|
64
|
+
"More Coming..."
|
|
65
|
+
]
|
|
66
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_BASE_URL = 'https://api.web8.oasisomniverse.one';
|
|
4
|
+
|
|
5
|
+
function buildQueryString(query) {
|
|
6
|
+
const entries = Object.entries(query || {}).filter(([, v]) => v !== undefined && v !== null);
|
|
7
|
+
if (!entries.length) return '';
|
|
8
|
+
const params = new URLSearchParams();
|
|
9
|
+
for (const [key, value] of entries) {
|
|
10
|
+
params.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
11
|
+
}
|
|
12
|
+
return `?${params.toString()}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Thin isomorphic HTTP client around the global fetch API (Node 18+, all modern browsers).
|
|
17
|
+
* Every WEB8 Galactic Mesh & Protocol Bridge API call ultimately goes through `request()` below - there are no mocked
|
|
18
|
+
* or stubbed responses anywhere in this SDK.
|
|
19
|
+
*/
|
|
20
|
+
class HttpClient {
|
|
21
|
+
constructor({ baseUrl = DEFAULT_BASE_URL, tokenStore, fetchImpl = globalThis.fetch } = {}) {
|
|
22
|
+
if (!fetchImpl) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'No global fetch implementation found. Use Node 18+, a modern browser, or pass { fetchImpl } explicitly.'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
28
|
+
this.tokenStore = tokenStore;
|
|
29
|
+
this.fetchImpl = fetchImpl;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setBaseUrl(baseUrl) {
|
|
33
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} verb GET | POST | PUT | DELETE
|
|
38
|
+
* @param {string} path e.g. "v1/complete"
|
|
39
|
+
* @param {object} [options]
|
|
40
|
+
* @param {object} [options.query] query string params (GET/DELETE)
|
|
41
|
+
* @param {object} [options.body] JSON body (POST/PUT/DELETE)
|
|
42
|
+
* @param {boolean} [options.auth] attach Authorization: Bearer <token> (default true)
|
|
43
|
+
* @param {string} [options.token] override token for this single request
|
|
44
|
+
*/
|
|
45
|
+
async request(verb, path, { query, body, auth = true, token } = {}) {
|
|
46
|
+
const url = `${this.baseUrl}/${path.replace(/^\/+/, '')}${buildQueryString(query)}`;
|
|
47
|
+
const headers = {
|
|
48
|
+
'Content-Type': 'application/json',
|
|
49
|
+
Accept: 'application/json'
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const bearer = token || (auth ? this.tokenStore?.getToken() : null);
|
|
53
|
+
if (bearer) headers.Authorization = `Bearer ${bearer}`;
|
|
54
|
+
|
|
55
|
+
const init = { method: verb, headers };
|
|
56
|
+
if (body !== undefined && verb !== 'GET') init.body = JSON.stringify(body);
|
|
57
|
+
|
|
58
|
+
let res;
|
|
59
|
+
try {
|
|
60
|
+
res = await this.fetchImpl(url, init);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
return { isError: true, message: `Network error calling ${url}: ${err.message}`, exception: err };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const text = await res.text();
|
|
66
|
+
let json;
|
|
67
|
+
try {
|
|
68
|
+
json = text ? JSON.parse(text) : null;
|
|
69
|
+
} catch {
|
|
70
|
+
json = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const message =
|
|
75
|
+
json?.result?.message || json?.message || json?.title || `Request failed with status ${res.status}`;
|
|
76
|
+
return { isError: true, message, statusCode: res.status, raw: json };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// OASIS responses are typically { isError, message, result: { isError, message, result: <payload> } }.
|
|
80
|
+
// We surface the innermost payload as `.result` while keeping the full envelope available as `.raw`.
|
|
81
|
+
const inner = json?.result !== undefined ? json.result : json;
|
|
82
|
+
const payload = inner?.result !== undefined ? inner.result : inner;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
isError: Boolean(inner?.isError || json?.isError),
|
|
86
|
+
message: inner?.message || json?.message || null,
|
|
87
|
+
result: payload,
|
|
88
|
+
raw: json,
|
|
89
|
+
statusCode: res.status
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get(path, options) {
|
|
94
|
+
return this.request('GET', path, options);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
post(path, options) {
|
|
98
|
+
return this.request('POST', path, options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
put(path, options) {
|
|
102
|
+
return this.request('PUT', path, options);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
delete(path, options) {
|
|
106
|
+
return this.request('DELETE', path, options);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { HttpClient, DEFAULT_BASE_URL };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TOKEN_PATTERN = /\{(\w+)(?::\w+)?\}/g;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolves a route template like "get-by-id/{id}" against an args object,
|
|
7
|
+
* substituting path tokens and returning the resolved path plus whatever
|
|
8
|
+
* args were *not* consumed as path tokens (these become the query/body).
|
|
9
|
+
*/
|
|
10
|
+
function resolveRoute(routeTemplate, args = {}) {
|
|
11
|
+
const consumed = new Set();
|
|
12
|
+
const path = routeTemplate.replace(TOKEN_PATTERN, (match, name) => {
|
|
13
|
+
const key = Object.keys(args).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
14
|
+
consumed.add(key);
|
|
15
|
+
const value = key !== undefined ? args[key] : undefined;
|
|
16
|
+
if (value === undefined) {
|
|
17
|
+
throw new Error(`Missing required route parameter "${name}" for route "${routeTemplate}"`);
|
|
18
|
+
}
|
|
19
|
+
return encodeURIComponent(value);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const rest = {};
|
|
23
|
+
for (const [key, value] of Object.entries(args)) {
|
|
24
|
+
if (!consumed.has(key)) rest[key] = value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { path, rest };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function takeKey(obj, name) {
|
|
31
|
+
const matchKey = Object.keys(obj).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
32
|
+
if (matchKey === undefined) return { found: false, value: undefined };
|
|
33
|
+
const value = obj[matchKey];
|
|
34
|
+
delete obj[matchKey];
|
|
35
|
+
return { found: true, value };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds a bound method for a single WEB8 endpoint operation.
|
|
40
|
+
* @param {import('./httpClient').HttpClient} http
|
|
41
|
+
* @param {string} routePrefix e.g. "api/avatar"
|
|
42
|
+
* @param {string} verb GET | POST | PUT | DELETE
|
|
43
|
+
* @param {string} route route template relative to routePrefix, e.g. "get-by-id/{id}"
|
|
44
|
+
* @param {object} [opts]
|
|
45
|
+
* @param {string[]} [opts.query] arg names that ASP.NET binds from the query
|
|
46
|
+
* string on this action regardless of HTTP verb (e.g. `[FromQuery]` flags
|
|
47
|
+
* mixed into an otherwise-body-bound POST/PUT action). Always sent as query.
|
|
48
|
+
* @param {string} [opts.bodyParam] when the action's entire request body is a
|
|
49
|
+
* single `[FromBody]` parameter (primitive or object), the JSON body is
|
|
50
|
+
* that arg's value directly rather than the leftover-args object wrapped
|
|
51
|
+
* around it.
|
|
52
|
+
*/
|
|
53
|
+
function makeOperation(http, routePrefix, verb, route, opts = {}) {
|
|
54
|
+
const declaredQueryKeys = opts.query || [];
|
|
55
|
+
const bodyParam = opts.bodyParam;
|
|
56
|
+
|
|
57
|
+
return async function operation(args = {}) {
|
|
58
|
+
const { path, rest } = resolveRoute(route, args);
|
|
59
|
+
const fullPath = path ? `${routePrefix}/${path}` : routePrefix;
|
|
60
|
+
|
|
61
|
+
const query = {};
|
|
62
|
+
for (const key of declaredQueryKeys) {
|
|
63
|
+
const { found, value } = takeKey(rest, key);
|
|
64
|
+
if (found) query[key] = value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let body;
|
|
68
|
+
if (bodyParam) {
|
|
69
|
+
const { found, value } = takeKey(rest, bodyParam);
|
|
70
|
+
if (found) body = value;
|
|
71
|
+
// Any args left over that we don't recognize still get sent (as query)
|
|
72
|
+
// rather than silently dropped.
|
|
73
|
+
Object.assign(query, rest);
|
|
74
|
+
} else if (verb === 'GET' || verb === 'DELETE') {
|
|
75
|
+
Object.assign(query, rest);
|
|
76
|
+
} else {
|
|
77
|
+
body = Object.keys(rest).length ? rest : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const hasQuery = Object.keys(query).length > 0;
|
|
81
|
+
return http.request(verb, fullPath, { query: hasQuery ? query : undefined, body });
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { resolveRoute, makeOperation };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const hasLocalStorage = typeof globalThis.localStorage !== 'undefined';
|
|
4
|
+
const STORAGE_KEY = 'oasis_session';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Holds the current JWT/avatar session for the SDK.
|
|
8
|
+
* In the browser it persists to localStorage by default; in Node (or when
|
|
9
|
+
* persistence is disabled) it simply lives in memory for the lifetime of
|
|
10
|
+
* the client instance. Callers can always set/get/clear explicitly.
|
|
11
|
+
*/
|
|
12
|
+
class TokenStore {
|
|
13
|
+
constructor({ persist = hasLocalStorage } = {}) {
|
|
14
|
+
this.persist = persist;
|
|
15
|
+
this._session = null;
|
|
16
|
+
|
|
17
|
+
if (this.persist) {
|
|
18
|
+
try {
|
|
19
|
+
const raw = globalThis.localStorage.getItem(STORAGE_KEY);
|
|
20
|
+
if (raw) this._session = JSON.parse(raw);
|
|
21
|
+
} catch {
|
|
22
|
+
this._session = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getSession() {
|
|
28
|
+
return this._session;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getToken() {
|
|
32
|
+
return this._session?.jwtToken || this._session?.token || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setSession(session) {
|
|
36
|
+
this._session = session || null;
|
|
37
|
+
if (this.persist) {
|
|
38
|
+
try {
|
|
39
|
+
if (session) globalThis.localStorage.setItem(STORAGE_KEY, JSON.stringify(session));
|
|
40
|
+
else globalThis.localStorage.removeItem(STORAGE_KEY);
|
|
41
|
+
} catch {
|
|
42
|
+
// Storage unavailable (e.g. private browsing) - in-memory session still works.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
clear() {
|
|
48
|
+
this.setSession(null);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { TokenStore };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-types.js - do not hand-edit.
|
|
2
|
+
export interface OASISResponse<T = any> {
|
|
3
|
+
isError: boolean;
|
|
4
|
+
message: string | null;
|
|
5
|
+
result: T;
|
|
6
|
+
raw: any;
|
|
7
|
+
statusCode: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface OASISSession {
|
|
11
|
+
avatarId: string;
|
|
12
|
+
username: string;
|
|
13
|
+
email: string;
|
|
14
|
+
firstName?: string;
|
|
15
|
+
lastName?: string;
|
|
16
|
+
jwtToken: string;
|
|
17
|
+
refreshToken?: string;
|
|
18
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { HttpClient, DEFAULT_BASE_URL } = require('./core/httpClient');
|
|
4
|
+
const { TokenStore } = require('./core/tokenStore');
|
|
5
|
+
const { attachGeneratedModules } = require('./modules/index');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Main SDK entry point. Works in Node 18+ and any modern browser.
|
|
9
|
+
*
|
|
10
|
+
* const { Web8Client } = require('@oasisomniverse/web8-api');
|
|
11
|
+
* const web8 = new Web8Client({ baseUrl: 'https://api.web8.oasisomniverse.one' });
|
|
12
|
+
* web8.setToken(jwtToken); // reuse a WEB4 OASIS JWT - WEB8 has no auth of its own
|
|
13
|
+
* const node = await web8.mesh.registerNode({ name: 'edge-node-1' });
|
|
14
|
+
*
|
|
15
|
+
* Every controller on the WEB8 Mesh WebAPI is reachable as a lowerCamel
|
|
16
|
+
* property (web8.mesh, web8.protocolBridge). Generated methods take a single
|
|
17
|
+
* args object; route template tokens (e.g. {nodeId}) are consumed from it
|
|
18
|
+
* automatically, remaining keys become the query string (GET/DELETE) or JSON
|
|
19
|
+
* body (POST/PUT).
|
|
20
|
+
*/
|
|
21
|
+
class Web8Client {
|
|
22
|
+
constructor({ baseUrl = DEFAULT_BASE_URL, persistSession, fetchImpl } = {}) {
|
|
23
|
+
this.tokenStore = new TokenStore({ persist: persistSession });
|
|
24
|
+
this.http = new HttpClient({ baseUrl, tokenStore: this.tokenStore, fetchImpl });
|
|
25
|
+
|
|
26
|
+
attachGeneratedModules(this, this.http);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setBaseUrl(baseUrl) {
|
|
30
|
+
this.http.setBaseUrl(baseUrl);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* WEB8 is an internal galactic-mesh/protocol-bridge layer sitting behind
|
|
35
|
+
* the same OASIS identity as WEB4/WEB5/WEB6/WEB7 - it has no avatar/auth
|
|
36
|
+
* endpoints of its own. Reuse a JWT you already obtained from the WEB4
|
|
37
|
+
* OASIS API (or your own backend) here.
|
|
38
|
+
*/
|
|
39
|
+
setToken(jwtToken, sessionExtras = {}) {
|
|
40
|
+
this.tokenStore.setSession({ ...sessionExtras, jwtToken });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { Web8Client, HttpClient, TokenStore, DEFAULT_BASE_URL };
|
|
45
|
+
module.exports.default = Web8Client;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-types.js - do not hand-edit.
|
|
2
|
+
import type { OASISResponse } from '../core/types';
|
|
3
|
+
|
|
4
|
+
export declare class MeshModule {
|
|
5
|
+
constructor(http: unknown);
|
|
6
|
+
|
|
7
|
+
/** POST v1/mesh/links (query: nodeAId, nodeBId, latencyMs) */
|
|
8
|
+
addLink(args?: Record<string, any>): Promise<OASISResponse>;
|
|
9
|
+
|
|
10
|
+
/** GET v1/mesh/route (query: sourceNodeId, destinationNodeId) */
|
|
11
|
+
computeRoute(args?: Record<string, any>): Promise<OASISResponse>;
|
|
12
|
+
|
|
13
|
+
/** GET v1/mesh/nodes */
|
|
14
|
+
getNodes(args?: Record<string, any>): Promise<OASISResponse>;
|
|
15
|
+
|
|
16
|
+
/** POST v1/mesh/nodes/{nodeId}/heartbeat */
|
|
17
|
+
heartbeat(args?: Record<string, any>): Promise<OASISResponse>;
|
|
18
|
+
|
|
19
|
+
/** POST v1/mesh/nodes */
|
|
20
|
+
registerNode(args?: Record<string, any>): Promise<OASISResponse>;
|
|
21
|
+
|
|
22
|
+
/** POST v1/mesh/send */
|
|
23
|
+
sendMessage(args?: Record<string, any>): Promise<OASISResponse>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// AUTO-GENERATED by scripts/generate-modules.js from endpoints.json - do not hand-edit.
|
|
4
|
+
// Regenerate with: node scripts/generate-modules.js
|
|
5
|
+
|
|
6
|
+
const { makeOperation } = require('../core/routeHelper');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generated wrapper for v1/mesh (source: WEB8 Mesh WebAPI MeshController.cs).
|
|
10
|
+
* Every method takes a single args object: path-template tokens (e.g. {id})
|
|
11
|
+
* are consumed from it automatically; any remaining keys are sent as the
|
|
12
|
+
* query string (GET/DELETE) or JSON body (POST/PUT).
|
|
13
|
+
*/
|
|
14
|
+
class MeshModule {
|
|
15
|
+
constructor(http) {
|
|
16
|
+
this._http = http;
|
|
17
|
+
|
|
18
|
+
// POST v1/mesh/links (query: nodeAId, nodeBId, latencyMs)
|
|
19
|
+
this.addLink = makeOperation(http, "v1/mesh", "POST", "links", {"query":["nodeAId","nodeBId","latencyMs"]});
|
|
20
|
+
// GET v1/mesh/route (query: sourceNodeId, destinationNodeId)
|
|
21
|
+
this.computeRoute = makeOperation(http, "v1/mesh", "GET", "route", {"query":["sourceNodeId","destinationNodeId"]});
|
|
22
|
+
// GET v1/mesh/nodes
|
|
23
|
+
this.getNodes = makeOperation(http, "v1/mesh", "GET", "nodes");
|
|
24
|
+
// POST v1/mesh/nodes/{nodeId}/heartbeat
|
|
25
|
+
this.heartbeat = makeOperation(http, "v1/mesh", "POST", "nodes/{nodeId}/heartbeat");
|
|
26
|
+
// POST v1/mesh/nodes
|
|
27
|
+
this.registerNode = makeOperation(http, "v1/mesh", "POST", "nodes");
|
|
28
|
+
// POST v1/mesh/send
|
|
29
|
+
this.sendMessage = makeOperation(http, "v1/mesh", "POST", "send");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { MeshModule };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-types.js - do not hand-edit.
|
|
2
|
+
import type { OASISResponse } from '../core/types';
|
|
3
|
+
|
|
4
|
+
export declare class ProtocolBridgeModule {
|
|
5
|
+
constructor(http: unknown);
|
|
6
|
+
|
|
7
|
+
/** POST v1/protocol-bridge/translate-inbound (query: format, sourceNodeId, destinationNodeId) (body: rawPayload) */
|
|
8
|
+
translateInbound(args?: Record<string, any>): Promise<OASISResponse>;
|
|
9
|
+
|
|
10
|
+
/** POST v1/protocol-bridge/translate-outbound (query: targetFormat) */
|
|
11
|
+
translateOutbound(args?: Record<string, any>): Promise<OASISResponse>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// AUTO-GENERATED by scripts/generate-modules.js from endpoints.json - do not hand-edit.
|
|
4
|
+
// Regenerate with: node scripts/generate-modules.js
|
|
5
|
+
|
|
6
|
+
const { makeOperation } = require('../core/routeHelper');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generated wrapper for v1/protocol-bridge (source: WEB8 Mesh WebAPI ProtocolBridgeController.cs).
|
|
10
|
+
* Every method takes a single args object: path-template tokens (e.g. {id})
|
|
11
|
+
* are consumed from it automatically; any remaining keys are sent as the
|
|
12
|
+
* query string (GET/DELETE) or JSON body (POST/PUT).
|
|
13
|
+
*/
|
|
14
|
+
class ProtocolBridgeModule {
|
|
15
|
+
constructor(http) {
|
|
16
|
+
this._http = http;
|
|
17
|
+
|
|
18
|
+
// POST v1/protocol-bridge/translate-inbound (query: format, sourceNodeId, destinationNodeId) (body: rawPayload)
|
|
19
|
+
this.translateInbound = makeOperation(http, "v1/protocol-bridge", "POST", "translate-inbound", {"query":["format","sourceNodeId","destinationNodeId"],"bodyParam":"rawPayload"});
|
|
20
|
+
// POST v1/protocol-bridge/translate-outbound (query: targetFormat)
|
|
21
|
+
this.translateOutbound = makeOperation(http, "v1/protocol-bridge", "POST", "translate-outbound", {"query":["targetFormat"]});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { ProtocolBridgeModule };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// AUTO-GENERATED by scripts/generate-modules.js - do not hand-edit.
|
|
4
|
+
const { MeshModule } = require('./Mesh');
|
|
5
|
+
const { ProtocolBridgeModule } = require('./ProtocolBridge');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Attaches every generated module to the client under its lowerCamel controller
|
|
9
|
+
* name (e.g. client.completion, client.images, client.holonicMemory).
|
|
10
|
+
*/
|
|
11
|
+
function attachGeneratedModules(client, http) {
|
|
12
|
+
client.mesh = client.mesh || new MeshModule(http);
|
|
13
|
+
client.protocolBridge = client.protocolBridge || new ProtocolBridgeModule(http);
|
|
14
|
+
return client;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { attachGeneratedModules };
|