wai-license-connector-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +399 -0
- package/dist/cjs/client.d.ts +10 -0
- package/dist/cjs/client.d.ts.map +1 -0
- package/dist/cjs/client.js +57 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/configuration.d.ts +26 -0
- package/dist/cjs/configuration.d.ts.map +1 -0
- package/dist/cjs/configuration.js +47 -0
- package/dist/cjs/configuration.js.map +1 -0
- package/dist/cjs/connector.d.ts +63 -0
- package/dist/cjs/connector.d.ts.map +1 -0
- package/dist/cjs/connector.js +224 -0
- package/dist/cjs/connector.js.map +1 -0
- package/dist/cjs/errors.d.ts +43 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +87 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/heartbeat.d.ts +24 -0
- package/dist/cjs/heartbeat.d.ts.map +1 -0
- package/dist/cjs/heartbeat.js +59 -0
- package/dist/cjs/heartbeat.js.map +1 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/instance.d.ts +14 -0
- package/dist/cjs/instance.d.ts.map +1 -0
- package/dist/cjs/instance.js +88 -0
- package/dist/cjs/instance.js.map +1 -0
- package/dist/cjs/manifest.d.ts +29 -0
- package/dist/cjs/manifest.d.ts.map +1 -0
- package/dist/cjs/manifest.js +29 -0
- package/dist/cjs/manifest.js.map +1 -0
- package/dist/cjs/registration.d.ts +20 -0
- package/dist/cjs/registration.d.ts.map +1 -0
- package/dist/cjs/registration.js +77 -0
- package/dist/cjs/registration.js.map +1 -0
- package/dist/cjs/types.d.ts +108 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/client.d.ts +10 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +54 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/configuration.d.ts +26 -0
- package/dist/esm/configuration.d.ts.map +1 -0
- package/dist/esm/configuration.js +42 -0
- package/dist/esm/configuration.js.map +1 -0
- package/dist/esm/connector.d.ts +63 -0
- package/dist/esm/connector.d.ts.map +1 -0
- package/dist/esm/connector.js +220 -0
- package/dist/esm/connector.js.map +1 -0
- package/dist/esm/errors.d.ts +43 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +77 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/heartbeat.d.ts +24 -0
- package/dist/esm/heartbeat.d.ts.map +1 -0
- package/dist/esm/heartbeat.js +54 -0
- package/dist/esm/heartbeat.js.map +1 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/instance.d.ts +14 -0
- package/dist/esm/instance.d.ts.map +1 -0
- package/dist/esm/instance.js +51 -0
- package/dist/esm/instance.js.map +1 -0
- package/dist/esm/manifest.d.ts +29 -0
- package/dist/esm/manifest.d.ts.map +1 -0
- package/dist/esm/manifest.js +26 -0
- package/dist/esm/manifest.js.map +1 -0
- package/dist/esm/registration.d.ts +20 -0
- package/dist/esm/registration.d.ts.map +1 -0
- package/dist/esm/registration.js +41 -0
- package/dist/esm/registration.js.map +1 -0
- package/dist/esm/types.d.ts +108 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/client.d.ts +10 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/configuration.d.ts +26 -0
- package/dist/types/configuration.d.ts.map +1 -0
- package/dist/types/connector.d.ts +63 -0
- package/dist/types/connector.d.ts.map +1 -0
- package/dist/types/errors.d.ts +43 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/heartbeat.d.ts +24 -0
- package/dist/types/heartbeat.d.ts.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/instance.d.ts +14 -0
- package/dist/types/instance.d.ts.map +1 -0
- package/dist/types/manifest.d.ts +29 -0
- package/dist/types/manifest.d.ts.map +1 -0
- package/dist/types/registration.d.ts +20 -0
- package/dist/types/registration.d.ts.map +1 -0
- package/dist/types/types.d.ts +108 -0
- package/dist/types/types.d.ts.map +1 -0
- package/package.json +56 -0
- package/src/__tests__/connector.spec.ts +368 -0
- package/src/__tests__/heartbeat.spec.ts +105 -0
- package/src/__tests__/instance.spec.ts +79 -0
- package/src/__tests__/manifest.spec.ts +64 -0
- package/src/__tests__/registration.spec.ts +89 -0
- package/src/client.ts +58 -0
- package/src/configuration.ts +57 -0
- package/src/connector.ts +276 -0
- package/src/errors.ts +88 -0
- package/src/heartbeat.ts +76 -0
- package/src/index.ts +35 -0
- package/src/instance.ts +55 -0
- package/src/manifest.ts +51 -0
- package/src/registration.ts +57 -0
- package/src/types.ts +150 -0
package/README.md
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# @wai-license/connector-sdk
|
|
2
|
+
|
|
3
|
+
Official SDK for making any Node.js application a **Wai License-compatible connector**.
|
|
4
|
+
|
|
5
|
+
Framework-agnostic · Node 20+ · Native fetch only · No axios
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# npm
|
|
13
|
+
npm install @wai-license/connector-sdk
|
|
14
|
+
|
|
15
|
+
# pnpm (monorepo)
|
|
16
|
+
pnpm add @wai-license/connector-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Node 20 or later is required. No polyfills needed — the SDK uses the built-in `fetch`, `crypto`, and `fs` APIs.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { WaiConnector } from '@wai-license/connector-sdk';
|
|
27
|
+
|
|
28
|
+
const connector = new WaiConnector({
|
|
29
|
+
// Identity
|
|
30
|
+
connectorCode: 'alcya', // Stable code — matches the connector registered in Wai License
|
|
31
|
+
connectorName: 'ALCYA',
|
|
32
|
+
connectorVersion: '1.0.0',
|
|
33
|
+
connectorType: 'api',
|
|
34
|
+
|
|
35
|
+
// Connection
|
|
36
|
+
waiUrl: process.env.WAI_URL!, // e.g. https://wai.example.com
|
|
37
|
+
apiKey: process.env.WAI_API_KEY!,
|
|
38
|
+
|
|
39
|
+
// Capabilities and actions
|
|
40
|
+
capabilities: ['customers', 'licenses'],
|
|
41
|
+
actions: [
|
|
42
|
+
{
|
|
43
|
+
code: 'activate_license',
|
|
44
|
+
name: 'Activate License',
|
|
45
|
+
endpoint: { url: '/wai/licenses/activate' },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
code: 'create_customer',
|
|
49
|
+
name: 'Create Customer',
|
|
50
|
+
endpoint: { url: '/wai/customers', method: 'POST' },
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
|
|
54
|
+
// Optional hooks
|
|
55
|
+
onRegister: (ctx) => console.log('Registered:', ctx.instanceId),
|
|
56
|
+
onHeartbeat: (ctx) => console.log('Heartbeat sent, took', ctx.durationMs, 'ms'),
|
|
57
|
+
onConfiguration: (ctx) => console.log('Config refreshed at', ctx.fetchedAt),
|
|
58
|
+
onError: (err) => console.error('Connector error:', err.name, err.message),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await connector.start();
|
|
62
|
+
console.log('Connector started, instanceId:', connector.instanceId);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`start()` will:
|
|
66
|
+
1. Load or create a persistent instance identity (`.wai-connector.json`)
|
|
67
|
+
2. Register with Wai License
|
|
68
|
+
3. Send the first heartbeat
|
|
69
|
+
4. Start the heartbeat loop (default: every 60 seconds)
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Express Integration
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import express from 'express';
|
|
77
|
+
import { WaiConnector } from '@wai-license/connector-sdk';
|
|
78
|
+
|
|
79
|
+
const connector = new WaiConnector({ /* ...options... */ });
|
|
80
|
+
|
|
81
|
+
const app = express();
|
|
82
|
+
app.use(express.json());
|
|
83
|
+
|
|
84
|
+
// Serve the manifest — Wai License polls this URL
|
|
85
|
+
app.get('/.well-known/wai-license.json', async (_req, res) => {
|
|
86
|
+
const handler = connector.handlers.manifest();
|
|
87
|
+
const response = handler();
|
|
88
|
+
const body = await response.json();
|
|
89
|
+
res.json(body);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Optional: let Wai License push a registration confirmation
|
|
93
|
+
app.post('/wai/register', async (req, res) => {
|
|
94
|
+
const handler = connector.handlers.register();
|
|
95
|
+
const response = handler(req as unknown as Request);
|
|
96
|
+
const body = await response.json();
|
|
97
|
+
res.status(response.status).json(body);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Optional: heartbeat ping endpoint
|
|
101
|
+
app.post('/wai/heartbeat', async (_req, res) => {
|
|
102
|
+
const handler = connector.handlers.heartbeat();
|
|
103
|
+
const response = handler();
|
|
104
|
+
const body = await response.json();
|
|
105
|
+
res.status(response.status).json(body);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Start everything
|
|
109
|
+
app.listen(3000, async () => {
|
|
110
|
+
await connector.start();
|
|
111
|
+
console.log('Express + Wai Connector running on :3000');
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Fastify Integration
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import Fastify from 'fastify';
|
|
121
|
+
import { WaiConnector } from '@wai-license/connector-sdk';
|
|
122
|
+
|
|
123
|
+
const connector = new WaiConnector({ /* ...options... */ });
|
|
124
|
+
const app = Fastify();
|
|
125
|
+
|
|
126
|
+
app.get('/.well-known/wai-license.json', async (_request, reply) => {
|
|
127
|
+
const handler = connector.handlers.manifest();
|
|
128
|
+
const response = handler();
|
|
129
|
+
const body = await response.json();
|
|
130
|
+
return reply.type('application/json').send(body);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
app.post('/wai/heartbeat', async (_request, reply) => {
|
|
134
|
+
const handler = connector.handlers.heartbeat();
|
|
135
|
+
const response = handler();
|
|
136
|
+
const body = await response.json();
|
|
137
|
+
return reply.status(response.status).send(body);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const start = async () => {
|
|
141
|
+
await connector.start();
|
|
142
|
+
await app.listen({ port: 3000 });
|
|
143
|
+
console.log('Fastify + Wai Connector running on :3000');
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
start().catch(console.error);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## ALCYA Integration Example
|
|
152
|
+
|
|
153
|
+
ALCYA is a Node.js SaaS application that manages software licenses. Here is a complete integration using the connector SDK with Express:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
// src/wai-connector.ts
|
|
157
|
+
import { WaiConnector, AuthenticationError, NetworkError } from '@wai-license/connector-sdk';
|
|
158
|
+
import { logger } from './logger';
|
|
159
|
+
|
|
160
|
+
export const connector = new WaiConnector({
|
|
161
|
+
connectorCode: 'alcya',
|
|
162
|
+
connectorName: 'ALCYA',
|
|
163
|
+
connectorVersion: process.env.npm_package_version ?? '1.0.0',
|
|
164
|
+
connectorType: 'api',
|
|
165
|
+
waiUrl: process.env.WAI_URL!,
|
|
166
|
+
apiKey: process.env.WAI_API_KEY!,
|
|
167
|
+
capabilities: ['customers', 'licenses', 'subscriptions'],
|
|
168
|
+
actions: [
|
|
169
|
+
{ code: 'activate_license', name: 'Activate License', endpoint: { url: '/wai/licenses/activate' } },
|
|
170
|
+
{ code: 'deactivate_license', name: 'Deactivate License', endpoint: { url: '/wai/licenses/deactivate' } },
|
|
171
|
+
{ code: 'create_customer', name: 'Create Customer', endpoint: { url: '/wai/customers' } },
|
|
172
|
+
{ code: 'suspend_customer', name: 'Suspend Customer', endpoint: { url: '/wai/customers/suspend' } },
|
|
173
|
+
],
|
|
174
|
+
heartbeatIntervalMs: 30_000,
|
|
175
|
+
onRegister: (ctx) => logger.info({ instanceId: ctx.instanceId }, 'Registered with Wai License'),
|
|
176
|
+
onHeartbeat: (ctx) => logger.debug({ durationMs: ctx.durationMs }, 'Heartbeat sent'),
|
|
177
|
+
onConfiguration: (ctx) => {
|
|
178
|
+
logger.info('Wai configuration refreshed');
|
|
179
|
+
// React to configuration changes
|
|
180
|
+
const config = ctx.config;
|
|
181
|
+
if (config['featureFlags']) {
|
|
182
|
+
applyFeatureFlags(config['featureFlags'] as Record<string, boolean>);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
onError: (err) => {
|
|
186
|
+
if (err instanceof AuthenticationError) {
|
|
187
|
+
logger.error('Invalid WAI_API_KEY — check environment configuration');
|
|
188
|
+
} else if (err instanceof NetworkError) {
|
|
189
|
+
logger.warn({ message: err.message }, 'Wai License unreachable, will retry');
|
|
190
|
+
} else {
|
|
191
|
+
logger.error({ err }, 'Wai Connector error');
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
function applyFeatureFlags(_flags: Record<string, boolean>): void {
|
|
197
|
+
// application-specific flag application
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
// src/app.ts
|
|
203
|
+
import express from 'express';
|
|
204
|
+
import { connector } from './wai-connector';
|
|
205
|
+
|
|
206
|
+
const app = express();
|
|
207
|
+
app.use(express.json());
|
|
208
|
+
|
|
209
|
+
// Manifest endpoint — Wai License discovers the connector from here
|
|
210
|
+
app.get('/.well-known/wai-license.json', async (_req, res) => {
|
|
211
|
+
const handler = connector.handlers.manifest();
|
|
212
|
+
const resp = handler();
|
|
213
|
+
res.status(resp.status).json(await resp.json());
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Action endpoints — Wai License calls these when executing sync jobs
|
|
217
|
+
app.post('/wai/licenses/activate', async (req, res) => {
|
|
218
|
+
const { payload } = req.body as { payload: { licenseKey: string; customerId: string } };
|
|
219
|
+
// ... activate the license in ALCYA ...
|
|
220
|
+
res.json({ success: true, licenseKey: payload.licenseKey });
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
app.post('/wai/customers', async (req, res) => {
|
|
224
|
+
const { payload } = req.body as { payload: { email: string; name: string } };
|
|
225
|
+
// ... create the customer in ALCYA ...
|
|
226
|
+
res.json({ success: true, customerId: 'cust_xxx' });
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
app.listen(3000, async () => {
|
|
230
|
+
await connector.start();
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## TFC Platform Integration Example
|
|
237
|
+
|
|
238
|
+
TFC Platform is a multi-tenant billing platform that connects with Wai License to manage enterprise licenses:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// connector/index.ts
|
|
242
|
+
import { WaiConnector } from '@wai-license/connector-sdk';
|
|
243
|
+
import Hono from 'hono';
|
|
244
|
+
|
|
245
|
+
const wai = new WaiConnector({
|
|
246
|
+
connectorCode: 'tfc-platform',
|
|
247
|
+
connectorName: 'TFC Platform',
|
|
248
|
+
connectorVersion: '2.1.0',
|
|
249
|
+
connectorType: 'webhook',
|
|
250
|
+
waiUrl: process.env.WAI_LICENSE_URL!,
|
|
251
|
+
apiKey: process.env.WAI_LICENSE_KEY!,
|
|
252
|
+
capabilities: ['customers', 'licenses', 'billing'],
|
|
253
|
+
actions: [
|
|
254
|
+
{ code: 'provision_license', name: 'Provision License', endpoint: { url: '/webhooks/wai/provision' } },
|
|
255
|
+
{ code: 'revoke_license', name: 'Revoke License', endpoint: { url: '/webhooks/wai/revoke' } },
|
|
256
|
+
{ code: 'sync_customer', name: 'Sync Customer', endpoint: { url: '/webhooks/wai/customers/sync' } },
|
|
257
|
+
{ code: 'update_plan', name: 'Update Plan', endpoint: { url: '/webhooks/wai/plans/update' } },
|
|
258
|
+
],
|
|
259
|
+
heartbeatIntervalMs: 60_000,
|
|
260
|
+
instanceFilePath: '/data/persistent/.wai-connector.json', // persisted volume
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const app = new Hono();
|
|
264
|
+
|
|
265
|
+
// Discovery manifest
|
|
266
|
+
app.get('/.well-known/wai-license.json', async (c) => {
|
|
267
|
+
const handler = wai.handlers.manifest();
|
|
268
|
+
const resp = handler();
|
|
269
|
+
return c.json(await resp.json(), resp.status as 200);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Webhook handlers — Wai License POSTs events here
|
|
273
|
+
app.post('/webhooks/wai/provision', async (c) => {
|
|
274
|
+
const body = await c.req.json() as { payload: unknown };
|
|
275
|
+
// ... provision the license in TFC ...
|
|
276
|
+
return c.json({ provisioned: true });
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
app.post('/webhooks/wai/revoke', async (c) => {
|
|
280
|
+
const body = await c.req.json() as { payload: unknown };
|
|
281
|
+
// ... revoke the license ...
|
|
282
|
+
return c.json({ revoked: true });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
export default {
|
|
286
|
+
fetch: app.fetch,
|
|
287
|
+
async scheduled() {
|
|
288
|
+
await wai.fetchConfiguration();
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Start the connector once the Hono server is ready
|
|
293
|
+
await wai.start();
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## API Reference
|
|
299
|
+
|
|
300
|
+
### `new WaiConnector(options)`
|
|
301
|
+
|
|
302
|
+
| Option | Type | Required | Default | Description |
|
|
303
|
+
|--------|------|----------|---------|-------------|
|
|
304
|
+
| `connectorCode` | `string` | Yes | — | Stable connector identifier |
|
|
305
|
+
| `connectorName` | `string` | Yes | — | Human-readable name |
|
|
306
|
+
| `connectorVersion` | `string` | Yes | — | SemVer string |
|
|
307
|
+
| `connectorType` | `string` | Yes | — | `api` or `webhook` |
|
|
308
|
+
| `waiUrl` | `string` | Yes | — | Base URL of the Wai License server |
|
|
309
|
+
| `apiKey` | `string` | Yes | — | Project API key (`wl_...`) |
|
|
310
|
+
| `capabilities` | `string[]` | Yes | — | List of capability keys |
|
|
311
|
+
| `actions` | `ConnectorActionDefinition[]` | Yes | — | Actions exposed to Wai License |
|
|
312
|
+
| `heartbeatIntervalMs` | `number` | No | `60000` | Heartbeat frequency |
|
|
313
|
+
| `timeoutMs` | `number` | No | `10000` | HTTP request timeout |
|
|
314
|
+
| `instanceFilePath` | `string` | No | `.wai-connector.json` | Instance identity persistence path |
|
|
315
|
+
| `onRegister` | `(ctx: RegisterContext) => void` | No | — | Called on successful registration |
|
|
316
|
+
| `onHeartbeat` | `(ctx: HeartbeatContext) => void` | No | — | Called after each heartbeat |
|
|
317
|
+
| `onConfiguration` | `(ctx: ConfigurationContext) => void` | No | — | Called when config is refreshed |
|
|
318
|
+
| `onError` | `(err: WaiConnectorError) => void` | No | — | Called on any SDK error |
|
|
319
|
+
|
|
320
|
+
### Instance methods
|
|
321
|
+
|
|
322
|
+
| Method | Returns | Description |
|
|
323
|
+
|--------|---------|-------------|
|
|
324
|
+
| `start()` | `Promise<void>` | Register, heartbeat, and start the loop |
|
|
325
|
+
| `stop()` | `Promise<void>` | Stop the heartbeat timer |
|
|
326
|
+
| `getManifest()` | `WaiManifest` | Manifest object for `/.well-known/wai-license.json` |
|
|
327
|
+
| `getConfiguration()` | `Record<string, unknown>` | Last-known config from Wai License |
|
|
328
|
+
| `fetchConfiguration()` | `Promise<Record<string, unknown>>` | Fetch fresh config now |
|
|
329
|
+
| `handlers.manifest()` | `ConnectorHandler` | Framework-agnostic manifest HTTP handler |
|
|
330
|
+
| `handlers.register()` | `ConnectorHandler` | Framework-agnostic register handler |
|
|
331
|
+
| `handlers.heartbeat()` | `ConnectorHandler` | Framework-agnostic heartbeat handler |
|
|
332
|
+
| `handlers.config()` | `ConnectorHandler` | Framework-agnostic config handler |
|
|
333
|
+
|
|
334
|
+
### Accessors
|
|
335
|
+
|
|
336
|
+
| Getter | Type | Description |
|
|
337
|
+
|--------|------|-------------|
|
|
338
|
+
| `instanceId` | `string \| null` | Set after `start()` |
|
|
339
|
+
| `isStarted` | `boolean` | Whether the connector is running |
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Instance Identity
|
|
344
|
+
|
|
345
|
+
Each running process is assigned a stable UUID that persists between restarts. It is stored in `.wai-connector.json` in the current working directory by default.
|
|
346
|
+
|
|
347
|
+
```json
|
|
348
|
+
{ "instanceId": "550e8400-e29b-41d4-a716-446655440000" }
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
To use a custom path (e.g. a mounted persistent volume):
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
new WaiConnector({ instanceFilePath: '/data/.wai-connector.json', ...opts });
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
The SDK is fault-tolerant: if the file cannot be written (read-only filesystem, missing permissions), a UUID is still generated for the session lifetime and the failure is silently ignored.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Error Handling
|
|
362
|
+
|
|
363
|
+
All errors extend `WaiConnectorError`:
|
|
364
|
+
|
|
365
|
+
| Class | `statusCode` | When |
|
|
366
|
+
|-------|-------------|------|
|
|
367
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
368
|
+
| `RegistrationError` | HTTP status | Registration rejected |
|
|
369
|
+
| `HeartbeatError` | HTTP status | Heartbeat rejected |
|
|
370
|
+
| `ConfigurationError` | HTTP status | Config fetch failed |
|
|
371
|
+
| `NetworkError` | 0 | Timeout, DNS failure, connection refused |
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
import { WaiConnector, AuthenticationError, NetworkError } from '@wai-license/connector-sdk';
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
await connector.start();
|
|
378
|
+
} catch (err) {
|
|
379
|
+
if (err instanceof AuthenticationError) {
|
|
380
|
+
// Check WAI_API_KEY in your environment
|
|
381
|
+
} else if (err instanceof NetworkError) {
|
|
382
|
+
// Wai License is unreachable
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Security
|
|
390
|
+
|
|
391
|
+
- The `apiKey` is sent as a Bearer token in the HTTP `Authorization` header — it is **never** included in request bodies, error messages, or logs.
|
|
392
|
+
- Instance identity (`instanceId`) is safe to log — it contains no sensitive information.
|
|
393
|
+
- The SDK never logs authorization headers or API keys at any log level.
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## License
|
|
398
|
+
|
|
399
|
+
Proprietary — Wai License internal package.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RequestOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Low-level HTTP transport for Wai License API calls.
|
|
4
|
+
* Uses native fetch — no axios, no external HTTP libraries.
|
|
5
|
+
* Never logs the Authorization header or raw apiKey.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function apiRequest<T>(opts: RequestOptions): Promise<T>;
|
|
10
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,CA+CpE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apiRequest = apiRequest;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
/**
|
|
6
|
+
* Low-level HTTP transport for Wai License API calls.
|
|
7
|
+
* Uses native fetch — no axios, no external HTTP libraries.
|
|
8
|
+
* Never logs the Authorization header or raw apiKey.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
async function apiRequest(opts) {
|
|
13
|
+
const url = `${opts.waiUrl.replace(/\/$/, '')}${opts.path}`;
|
|
14
|
+
const headers = {
|
|
15
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
Accept: 'application/json',
|
|
18
|
+
};
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
const timeoutId = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
21
|
+
let response;
|
|
22
|
+
try {
|
|
23
|
+
response = await fetch(url, {
|
|
24
|
+
method: opts.method,
|
|
25
|
+
headers,
|
|
26
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
27
|
+
signal: controller.signal,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
clearTimeout(timeoutId);
|
|
32
|
+
const isTimeout = err instanceof Error && err.name === 'AbortError';
|
|
33
|
+
throw new errors_1.NetworkError(isTimeout
|
|
34
|
+
? `Request timed out after ${opts.timeoutMs}ms`
|
|
35
|
+
: `Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(timeoutId);
|
|
39
|
+
}
|
|
40
|
+
if (response.ok) {
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
return (text ? JSON.parse(text) : {});
|
|
43
|
+
}
|
|
44
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
45
|
+
try {
|
|
46
|
+
const payload = (await response.json());
|
|
47
|
+
if (typeof payload['message'] === 'string')
|
|
48
|
+
errorMessage = payload['message'];
|
|
49
|
+
else if (typeof payload['error'] === 'string')
|
|
50
|
+
errorMessage = payload['error'];
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// non-JSON error body
|
|
54
|
+
}
|
|
55
|
+
throw (0, errors_1.createConnectorError)(response.status, errorMessage);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":";;AAUA,gCA+CC;AAzDD,qCAA8D;AAG9D;;;;;;GAMG;AACI,KAAK,UAAU,UAAU,CAAI,IAAoB;IACtD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE5D,MAAM,OAAO,GAA2B;QACtC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;QACtC,cAAc,EAAE,kBAAkB;QAClC,MAAM,EAAE,kBAAkB;KAC3B,CAAC;IAEF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAEvE,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC1B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YACrE,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,MAAM,SAAS,GAAG,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC;QACpE,MAAM,IAAI,qBAAY,CACpB,SAAS;YACP,CAAC,CAAC,2BAA2B,IAAI,CAAC,SAAS,IAAI;YAC/C,CAAC,CAAC,kBAAkB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACzE,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,SAAS,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAM,CAAC;IAC7C,CAAC;IAED,IAAI,YAAY,GAAG,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAC;QACnE,IAAI,OAAO,OAAO,CAAC,SAAS,CAAC,KAAK,QAAQ;YAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;aACzE,IAAI,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,QAAQ;YAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjF,CAAC;IAAC,MAAM,CAAC;QACP,sBAAsB;IACxB,CAAC;IAED,MAAM,IAAA,6BAAoB,EAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ConfigurationContext } from './types';
|
|
2
|
+
export interface ConfigurationParams {
|
|
3
|
+
waiUrl: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
connectorCode: string;
|
|
6
|
+
instanceId: string;
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* In-memory configuration store.
|
|
11
|
+
* Updated whenever getConfiguration() is called successfully.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export declare class ConfigurationStore {
|
|
16
|
+
private _config;
|
|
17
|
+
get(): Record<string, unknown>;
|
|
18
|
+
set(config: Record<string, unknown>): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fetches the latest connector configuration from the Wai License server.
|
|
22
|
+
* Calls GET /connectors/config with Bearer auth.
|
|
23
|
+
* Stores the result in the provided ConfigurationStore.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getConfiguration(params: ConfigurationParams, store: ConfigurationStore): Promise<ConfigurationContext>;
|
|
26
|
+
//# sourceMappingURL=configuration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configuration.d.ts","sourceRoot":"","sources":["../../src/configuration.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAA+B;IAE9C,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI9B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAG3C;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAkB/B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigurationStore = void 0;
|
|
4
|
+
exports.getConfiguration = getConfiguration;
|
|
5
|
+
const client_1 = require("./client");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
/**
|
|
8
|
+
* In-memory configuration store.
|
|
9
|
+
* Updated whenever getConfiguration() is called successfully.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
class ConfigurationStore {
|
|
14
|
+
_config = {};
|
|
15
|
+
get() {
|
|
16
|
+
return { ...this._config };
|
|
17
|
+
}
|
|
18
|
+
set(config) {
|
|
19
|
+
this._config = { ...config };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.ConfigurationStore = ConfigurationStore;
|
|
23
|
+
/**
|
|
24
|
+
* Fetches the latest connector configuration from the Wai License server.
|
|
25
|
+
* Calls GET /connectors/config with Bearer auth.
|
|
26
|
+
* Stores the result in the provided ConfigurationStore.
|
|
27
|
+
*/
|
|
28
|
+
async function getConfiguration(params, store) {
|
|
29
|
+
let config;
|
|
30
|
+
try {
|
|
31
|
+
config = await (0, client_1.apiRequest)({
|
|
32
|
+
waiUrl: params.waiUrl,
|
|
33
|
+
apiKey: params.apiKey,
|
|
34
|
+
timeoutMs: params.timeoutMs,
|
|
35
|
+
path: `/connectors/config?connectorCode=${encodeURIComponent(params.connectorCode)}&instanceId=${encodeURIComponent(params.instanceId)}`,
|
|
36
|
+
method: 'GET',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const base = err;
|
|
41
|
+
throw (0, errors_1.createConnectorError)(base.statusCode ?? 0, base.message, 'configuration');
|
|
42
|
+
}
|
|
43
|
+
store.set(config);
|
|
44
|
+
const fetchedAt = new Date().toISOString();
|
|
45
|
+
return { config, fetchedAt };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=configuration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configuration.js","sourceRoot":"","sources":["../../src/configuration.ts"],"names":[],"mappings":";;;AAmCA,4CAqBC;AAxDD,qCAAsC;AACtC,qCAAgD;AAWhD;;;;;GAKG;AACH,MAAa,kBAAkB;IACrB,OAAO,GAA4B,EAAE,CAAC;IAE9C,GAAG;QACD,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,MAA+B;QACjC,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC/B,CAAC;CACF;AAVD,gDAUC;AAED;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CACpC,MAA2B,EAC3B,KAAyB;IAEzB,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAA,mBAAU,EAA0B;YACjD,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,IAAI,EAAE,oCAAoC,kBAAkB,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,kBAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE;YACxI,MAAM,EAAE,KAAK;SACd,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,GAA+C,CAAC;QAC7D,MAAM,IAAA,6BAAoB,EAAC,IAAI,CAAC,UAAU,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IAClF,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { WaiManifest } from './manifest';
|
|
2
|
+
import type { WaiConnectorOptions, ConnectorHandlers } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Main entry point for the Wai License Connector SDK.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const connector = new WaiConnector({
|
|
9
|
+
* connectorCode: 'alcya',
|
|
10
|
+
* connectorName: 'ALCYA',
|
|
11
|
+
* connectorVersion: '1.0.0',
|
|
12
|
+
* connectorType: 'api',
|
|
13
|
+
* waiUrl: process.env.WAI_URL!,
|
|
14
|
+
* apiKey: process.env.WAI_API_KEY!,
|
|
15
|
+
* capabilities: ['customers', 'licenses'],
|
|
16
|
+
* actions: [
|
|
17
|
+
* { code: 'activate_license', name: 'Activate License', endpoint: { url: '/wai/licenses/activate' } },
|
|
18
|
+
* ],
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await connector.start();
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare class WaiConnector {
|
|
25
|
+
private readonly opts;
|
|
26
|
+
private _instanceId;
|
|
27
|
+
private _heartbeatHandle;
|
|
28
|
+
private _started;
|
|
29
|
+
private readonly _configStore;
|
|
30
|
+
constructor(options: WaiConnectorOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Loads the instance identity, registers with Wai License,
|
|
33
|
+
* sends the first heartbeat, then starts the heartbeat loop.
|
|
34
|
+
*/
|
|
35
|
+
start(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Stops the heartbeat timer. Safe to call multiple times.
|
|
38
|
+
*/
|
|
39
|
+
stop(): Promise<void>;
|
|
40
|
+
/** Returns the connector manifest that Wai License reads from /.well-known/wai-license.json */
|
|
41
|
+
getManifest(): WaiManifest;
|
|
42
|
+
/** Returns the last-known configuration fetched from Wai License. */
|
|
43
|
+
getConfiguration(): Record<string, unknown>;
|
|
44
|
+
/** Fetches fresh configuration from Wai License and updates the in-memory store. */
|
|
45
|
+
fetchConfiguration(): Promise<Record<string, unknown>>;
|
|
46
|
+
/**
|
|
47
|
+
* Returns framework-agnostic HTTP handler functions.
|
|
48
|
+
* Each handler accepts an optional Request and returns a Response.
|
|
49
|
+
*
|
|
50
|
+
* @example Express
|
|
51
|
+
* ```ts
|
|
52
|
+
* app.get('/.well-known/wai-license.json', (req, res) => {
|
|
53
|
+
* const response = connector.handlers.manifest()(req as unknown as Request);
|
|
54
|
+
* res.json(JSON.parse(/* body *\/));
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
get handlers(): ConnectorHandlers;
|
|
59
|
+
get instanceId(): string | null;
|
|
60
|
+
get isStarted(): boolean;
|
|
61
|
+
private static _validate;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=connector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../src/connector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAElB,MAAM,SAAS,CAAC;AAIjB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAYnB;IAEF,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,gBAAgB,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA4B;gBAE7C,OAAO,EAAE,mBAAmB;IAwBxC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA8C5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,+FAA+F;IAC/F,WAAW,IAAI,WAAW;IAa1B,qEAAqE;IACrE,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI3C,oFAAoF;IAC9E,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAuB5D;;;;;;;;;;;OAWG;IACH,IAAI,QAAQ,IAAI,iBAAiB,CAuChC;IAID,IAAI,UAAU,IAAI,MAAM,GAAG,IAAI,CAE9B;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAID,OAAO,CAAC,MAAM,CAAC,SAAS;CA0BzB"}
|