@nominalso/vibe-host 0.1.0 → 0.3.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 +21 -7
- package/dist/index.cjs +261 -55
- package/dist/index.d.cts +191 -16
- package/dist/index.d.ts +191 -16
- package/dist/index.js +259 -55
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
// package.json
|
|
2
|
-
var version = "0.
|
|
2
|
+
var version = "0.3.0";
|
|
3
3
|
|
|
4
4
|
// ../protocol-types/dist/index.js
|
|
5
|
+
function normalizeSubroute(subroute) {
|
|
6
|
+
const normalized = subroute.startsWith("/") ? subroute : `/${subroute}`;
|
|
7
|
+
const path = normalized.split(/[?#]/)[0];
|
|
8
|
+
let decoded = path;
|
|
9
|
+
let prev;
|
|
10
|
+
do {
|
|
11
|
+
prev = decoded;
|
|
12
|
+
decoded = decoded.replace(/%25/gi, "%").replace(/%2e/gi, ".").replace(/%2f/gi, "/");
|
|
13
|
+
} while (decoded !== prev);
|
|
14
|
+
if (decoded.split("/").some((segment) => segment === "..")) return null;
|
|
15
|
+
return normalized;
|
|
16
|
+
}
|
|
5
17
|
var PROTOCOL_ID = "nominal-vibe-bridge";
|
|
6
18
|
var PROTOCOL_VERSION = 1;
|
|
7
19
|
var BRIDGE_KINDS = ["request", "response", "push", "command", "progress"];
|
|
@@ -18,6 +30,47 @@ function isBridgeMessage(data) {
|
|
|
18
30
|
if (CORRELATED_KINDS_SET.has(d.kind) && typeof d.requestId !== "string") return false;
|
|
19
31
|
return true;
|
|
20
32
|
}
|
|
33
|
+
var BridgeError = class extends Error {
|
|
34
|
+
constructor(code, message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.code = code;
|
|
37
|
+
this.name = "BridgeError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var HttpBridgeError = class extends BridgeError {
|
|
41
|
+
constructor(status, message) {
|
|
42
|
+
super("REQUEST_FAILED", message);
|
|
43
|
+
this.status = status;
|
|
44
|
+
this.name = "HttpBridgeError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
function isOriginPattern(entry) {
|
|
48
|
+
return entry.includes("*");
|
|
49
|
+
}
|
|
50
|
+
var patternCache = /* @__PURE__ */ new Map();
|
|
51
|
+
function patternToRegExp(pattern) {
|
|
52
|
+
const cached = patternCache.get(pattern);
|
|
53
|
+
if (cached) return cached;
|
|
54
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
55
|
+
const body = escaped.replace(/\\\*/g, "[^./:@?#\\s]+");
|
|
56
|
+
const compiled = new RegExp(`^${body}$`);
|
|
57
|
+
patternCache.set(pattern, compiled);
|
|
58
|
+
return compiled;
|
|
59
|
+
}
|
|
60
|
+
function matchesOrigin(origin, entry) {
|
|
61
|
+
if (!isOriginPattern(entry)) return origin === entry;
|
|
62
|
+
return patternToRegExp(entry).test(origin);
|
|
63
|
+
}
|
|
64
|
+
function isOriginAllowed(origin, allowlist, { allowPatterns }) {
|
|
65
|
+
for (const entry of allowlist) {
|
|
66
|
+
if (isOriginPattern(entry)) {
|
|
67
|
+
if (allowPatterns && matchesOrigin(origin, entry)) return true;
|
|
68
|
+
} else if (origin === entry) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
21
74
|
|
|
22
75
|
// ../api-client/dist/index.js
|
|
23
76
|
import {
|
|
@@ -352,10 +405,6 @@ var getSubsidiaryParentCurrenciesApiTenancySubsidiarySubsidiaryIdParentCurrencie
|
|
|
352
405
|
};
|
|
353
406
|
function createVibeApiClient({ baseUrl, stripApiPrefix }) {
|
|
354
407
|
const client2 = createClient2(createConfig2({ baseUrl }));
|
|
355
|
-
client2.interceptors.request.use((request) => {
|
|
356
|
-
console.log("[vibe-api-client] request url:", request.url);
|
|
357
|
-
return request;
|
|
358
|
-
});
|
|
359
408
|
if (stripApiPrefix) {
|
|
360
409
|
client2.interceptors.request.use(
|
|
361
410
|
(request) => new Request(request.url.replace(`${baseUrl}/api/`, `${baseUrl}/`), request)
|
|
@@ -364,16 +413,28 @@ function createVibeApiClient({ baseUrl, stripApiPrefix }) {
|
|
|
364
413
|
return client2;
|
|
365
414
|
}
|
|
366
415
|
|
|
367
|
-
// src/
|
|
416
|
+
// src/handlers/call.ts
|
|
368
417
|
async function call(fn, payload, client2) {
|
|
369
|
-
const { data } = await fn({ ...payload, client: client2 });
|
|
370
|
-
if (data == null)
|
|
418
|
+
const { data, response } = await fn({ ...payload, client: client2 });
|
|
419
|
+
if (data == null) {
|
|
420
|
+
if (response && !response.ok) {
|
|
421
|
+
throw new HttpBridgeError(
|
|
422
|
+
response.status,
|
|
423
|
+
`API request failed with status ${response.status}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
if (!response) {
|
|
427
|
+
throw new Error("API request produced no response");
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
371
431
|
return data;
|
|
372
432
|
}
|
|
373
|
-
|
|
374
|
-
|
|
433
|
+
|
|
434
|
+
// src/handlers/accounting.ts
|
|
435
|
+
function accountingHandlers(client2) {
|
|
375
436
|
return {
|
|
376
|
-
//
|
|
437
|
+
// Chart of accounts
|
|
377
438
|
GET_CHART_OF_ACCOUNTS: (p) => call(getCoaFlatApiAccountingSubsidiarySubsidiaryIdCoaAccountGet, p, client2),
|
|
378
439
|
GET_COA_TREE: (p) => call(getCoaTreeApiAccountingSubsidiarySubsidiaryIdCoaGet, p, client2),
|
|
379
440
|
GET_COA_FLAT_SIMPLE: (p) => call(
|
|
@@ -398,7 +459,7 @@ function createHandlerMap(options) {
|
|
|
398
459
|
),
|
|
399
460
|
GET_ACCOUNTS: (p) => call(getAccountsApiAccountingAccountGet, p, client2),
|
|
400
461
|
GET_ACCOUNT: (p) => call(getAccountApiAccountingAccountAccountIdGet, p, client2),
|
|
401
|
-
//
|
|
462
|
+
// Exchange rates
|
|
402
463
|
GET_CONVERSION_RATES: (p) => call(getConversionRateApiAccountingCurrencyCurrencyConversionRatesGet, p, client2),
|
|
403
464
|
GET_EFFECTIVE_EXCHANGE_RATE: (p) => call(getEffectiveExchangeRateApiAccountingConsolidationExchangeRateGet, p, client2),
|
|
404
465
|
GET_EXCHANGE_RATE_BY_DATE: (p) => call(
|
|
@@ -406,7 +467,7 @@ function createHandlerMap(options) {
|
|
|
406
467
|
p,
|
|
407
468
|
client2
|
|
408
469
|
),
|
|
409
|
-
//
|
|
470
|
+
// Dimensions
|
|
410
471
|
GET_DIMENSIONS: (p) => call(getDimensionsApiAccountingDimensionGet, p, client2),
|
|
411
472
|
GET_DIMENSION_VALUES: (p) => call(getDimensionValuesApiAccountingDimensionValueGet, p, client2),
|
|
412
473
|
GET_DIMENSION_VALUES_HIERARCHICAL: (p) => call(
|
|
@@ -419,7 +480,7 @@ function createHandlerMap(options) {
|
|
|
419
480
|
p,
|
|
420
481
|
client2
|
|
421
482
|
),
|
|
422
|
-
//
|
|
483
|
+
// Journal entries
|
|
423
484
|
GET_JOURNAL_ENTRIES: (p) => call(
|
|
424
485
|
getSubsidiaryJournalApiAccountingSubsidiarySubsidiaryIdJournalEntryGet,
|
|
425
486
|
p,
|
|
@@ -434,8 +495,14 @@ function createHandlerMap(options) {
|
|
|
434
495
|
getJournalEntryApiAccountingSubsidiarySubsidiaryIdJournalEntryJournalEntryIdGet,
|
|
435
496
|
p,
|
|
436
497
|
client2
|
|
437
|
-
)
|
|
438
|
-
|
|
498
|
+
)
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/handlers/activity.ts
|
|
503
|
+
function activityHandlers(client2) {
|
|
504
|
+
return {
|
|
505
|
+
// Period instances
|
|
439
506
|
GET_PERIODS: (p) => call(getPeriodInstancesApiActivityPeriodInstanceGet, p, client2),
|
|
440
507
|
GET_PERIOD_INSTANCE: (p) => call(getPeriodInstanceApiActivityPeriodInstancePeriodInstanceIdGet, p, client2),
|
|
441
508
|
GET_PERIOD_INSTANCE_BY_SLUG: (p) => call(
|
|
@@ -448,14 +515,14 @@ function createHandlerMap(options) {
|
|
|
448
515
|
p,
|
|
449
516
|
client2
|
|
450
517
|
),
|
|
451
|
-
// Activity
|
|
518
|
+
// Activity definitions
|
|
452
519
|
GET_ACTIVITY_DEFINITIONS: (p) => call(getActivityDefinitionsApiActivityActivityDefinitionGet, p, client2),
|
|
453
520
|
GET_ACTIVITY_DEFINITION: (p) => call(
|
|
454
521
|
getActivityDefinitionByIdApiActivityActivityDefinitionActivityDefinitionIdGet,
|
|
455
522
|
p,
|
|
456
523
|
client2
|
|
457
524
|
),
|
|
458
|
-
// Activity
|
|
525
|
+
// Activity instances
|
|
459
526
|
GET_ACTIVITY_INSTANCES: (p) => call(getActivityInstancesApiActivityActivityInstanceGet, p, client2),
|
|
460
527
|
GET_ACTIVITY_INSTANCE: (p) => call(getActivityInstanceApiActivityActivityInstanceActivityInstanceIdGet, p, client2),
|
|
461
528
|
GET_ACTIVITY_INSTANCE_BY_PERIOD: (p) => call(
|
|
@@ -473,28 +540,43 @@ function createHandlerMap(options) {
|
|
|
473
540
|
p,
|
|
474
541
|
client2
|
|
475
542
|
),
|
|
476
|
-
//
|
|
543
|
+
// Task definitions
|
|
477
544
|
GET_TASK_DEFINITIONS: (p) => call(getAllTasksDefinitionsApiActivityTaskDefinitionGet, p, client2),
|
|
478
545
|
GET_TASK_DEFINITION: (p) => call(getTaskDefinitionByIdApiActivityTaskDefinitionTaskDefinitionIdGet, p, client2),
|
|
479
546
|
CREATE_TASK_DEFINITION: (p) => call(createTaskDefinitionApiActivityTaskDefinitionPost, p, client2),
|
|
480
547
|
UPDATE_TASK_DEFINITION: (p) => call(updateTaskDefinitionApiActivityTaskDefinitionTaskDefinitionIdPut, p, client2),
|
|
481
548
|
GET_TASK_DEFINITIONS_BY_FILTER: (p) => call(getAllTasksDefinitionsByFilterApiActivityTaskDefinitionQueryPost, p, client2),
|
|
482
|
-
//
|
|
549
|
+
// Task instances
|
|
483
550
|
GET_TASK_INSTANCES: (p) => call(getAllTaskInstancesApiActivityTaskInstanceGet, p, client2),
|
|
484
551
|
GET_TASK_INSTANCE: (p) => call(getTaskInstanceByIdApiActivityTaskInstanceTaskInstanceIdGet, p, client2),
|
|
485
552
|
POST_TASK_OUTPUT: (p) => call(updateTaskInstanceApiActivityTaskInstanceTaskInstanceIdPut, p, client2),
|
|
486
|
-
GET_TASK_INSTANCES_BY_FILTER: (p) => call(getAllTaskInstancesByFilterApiActivityTaskInstanceQueryPost, p, client2)
|
|
487
|
-
|
|
553
|
+
GET_TASK_INSTANCES_BY_FILTER: (p) => call(getAllTaskInstancesByFilterApiActivityTaskInstanceQueryPost, p, client2)
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/handlers/audit-trail.ts
|
|
558
|
+
function auditTrailHandlers(client2) {
|
|
559
|
+
return {
|
|
488
560
|
GET_AUDIT_EVENTS: (p) => call(getEventsApiAuditTrailEventGet, p, client2),
|
|
489
|
-
GET_ENTITY_AUDIT_EVENTS: (p) => call(getEntityEventsApiAuditTrailEventEntityTargetTypeTargetIdPost, p, client2)
|
|
490
|
-
|
|
561
|
+
GET_ENTITY_AUDIT_EVENTS: (p) => call(getEntityEventsApiAuditTrailEventEntityTargetTypeTargetIdPost, p, client2)
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/handlers/period-manager.ts
|
|
566
|
+
function periodManagerHandlers(client2) {
|
|
567
|
+
return {
|
|
491
568
|
GET_FISCAL_CALENDARS: (p) => call(
|
|
492
569
|
getFiscalCalendarsBySubsidiaryApiPeriodManagerFiscalCalendarBySubsidiarySubsidiaryIdGet,
|
|
493
570
|
p,
|
|
494
571
|
client2
|
|
495
572
|
),
|
|
496
|
-
GET_FISCAL_CALENDAR: (p) => call(getFiscalCalendarByIdApiPeriodManagerFiscalCalendarCalendarIdGet, p, client2)
|
|
497
|
-
|
|
573
|
+
GET_FISCAL_CALENDAR: (p) => call(getFiscalCalendarByIdApiPeriodManagerFiscalCalendarCalendarIdGet, p, client2)
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/handlers/tenancy.ts
|
|
578
|
+
function tenancyHandlers(client2) {
|
|
579
|
+
return {
|
|
498
580
|
GET_SUBSIDIARIES: (p) => call(getAllSubsidiariesApiTenancySubsidiaryGet, p, client2),
|
|
499
581
|
GET_SUBSIDIARY: (p) => call(getSubsidiaryApiTenancySubsidiarySubsidiaryIdGet, p, client2),
|
|
500
582
|
GET_SUBSIDIARY_PARENT_CURRENCIES: (p) => call(
|
|
@@ -506,46 +588,122 @@ function createHandlerMap(options) {
|
|
|
506
588
|
};
|
|
507
589
|
}
|
|
508
590
|
|
|
509
|
-
// src/
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
591
|
+
// src/handlerMap.ts
|
|
592
|
+
function createHandlerMap(options) {
|
|
593
|
+
const client2 = createVibeApiClient(options);
|
|
594
|
+
return {
|
|
595
|
+
...accountingHandlers(client2),
|
|
596
|
+
...activityHandlers(client2),
|
|
597
|
+
...auditTrailHandlers(client2),
|
|
598
|
+
...periodManagerHandlers(client2),
|
|
599
|
+
...tenancyHandlers(client2)
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/rateLimiter.ts
|
|
604
|
+
var DEFAULT_RATE_LIMIT = 60;
|
|
605
|
+
function createRateLimiter(limit = DEFAULT_RATE_LIMIT, windowMs = 6e4) {
|
|
606
|
+
return { timestamps: [], limit, windowMs };
|
|
607
|
+
}
|
|
608
|
+
function checkRateLimit(state) {
|
|
609
|
+
const now = Date.now();
|
|
610
|
+
const cutoff = now - state.windowMs;
|
|
611
|
+
state.timestamps = state.timestamps.filter((t) => t > cutoff);
|
|
612
|
+
if (state.timestamps.length >= state.limit) {
|
|
613
|
+
return false;
|
|
515
614
|
}
|
|
516
|
-
|
|
615
|
+
state.timestamps.push(now);
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/unwired.ts
|
|
620
|
+
function warnUnwired(command) {
|
|
621
|
+
return () => {
|
|
622
|
+
console.warn(`[vibe-host] Received "${command}" command but no handler is wired \u2014 ignoring.`);
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function rejectUnwired(op) {
|
|
626
|
+
return () => Promise.reject(
|
|
627
|
+
new BridgeError("NOT_WIRED", `[vibe-host] Received "${op}" request but no handler is wired.`)
|
|
628
|
+
);
|
|
517
629
|
}
|
|
518
630
|
|
|
519
631
|
// src/VibeAppHost.ts
|
|
520
632
|
var VibeAppHost = class {
|
|
521
|
-
constructor(
|
|
633
|
+
constructor(opts) {
|
|
634
|
+
/**
|
|
635
|
+
* Concrete iframe origins observed on validated inbound messages. Because you
|
|
636
|
+
* cannot `postMessage` to a glob pattern, proactive pushes target these
|
|
637
|
+
* learned origins (plus the exact allowlist entries) rather than the patterns.
|
|
638
|
+
*/
|
|
639
|
+
this.connectedOrigins = /* @__PURE__ */ new Set();
|
|
522
640
|
this.iframeWindow = null;
|
|
641
|
+
/**
|
|
642
|
+
* Source windows we've already logged a successful handshake for. The bridge
|
|
643
|
+
* polls `GET_CONTEXT` on an interval until it connects, so without this the
|
|
644
|
+
* "Vibe App connected" line repeats on every poll (SSOT §13 F3).
|
|
645
|
+
*/
|
|
646
|
+
this.handshakeLoggedWindows = /* @__PURE__ */ new WeakSet();
|
|
523
647
|
this.kindHandlers = {
|
|
524
648
|
request: (msg, source, origin) => this.handleRequest(msg, source, origin),
|
|
525
649
|
command: (msg) => this.dispatchCommand(msg)
|
|
526
650
|
};
|
|
527
|
-
|
|
528
|
-
REPORT_SUBROUTE: (payload) => {
|
|
529
|
-
const newPath = payload.subroute === "/" ? this.appBasePath : `${this.appBasePath}${payload.subroute}`;
|
|
530
|
-
const method = payload.replace ? "replaceState" : "pushState";
|
|
531
|
-
window.history[method](null, "", newPath);
|
|
532
|
-
}
|
|
533
|
-
};
|
|
651
|
+
const { trustedOrigins, getContext, appBasePath, clientConfig } = opts;
|
|
534
652
|
this.trustedOrigins = new Set(trustedOrigins);
|
|
653
|
+
this.exactOrigins = trustedOrigins.filter((entry) => !isOriginPattern(entry));
|
|
535
654
|
this.getContext = getContext;
|
|
536
655
|
this.appBasePath = appBasePath;
|
|
656
|
+
this.onRequestComplete = opts.onRequestComplete;
|
|
657
|
+
this.rateLimiter = createRateLimiter(opts.rateLimit ?? DEFAULT_RATE_LIMIT);
|
|
537
658
|
this.boundHandleMessage = this.handleMessage.bind(this);
|
|
538
659
|
this.boundHandlePopState = this.handlePopState.bind(this);
|
|
539
|
-
this.handlers = this.initializeHandlers(
|
|
660
|
+
this.handlers = this.initializeHandlers(
|
|
661
|
+
clientConfig || { baseUrl: "", stripApiPrefix: false },
|
|
662
|
+
opts
|
|
663
|
+
);
|
|
664
|
+
this.commandHandlers = this.buildCommandHandlers(opts);
|
|
540
665
|
}
|
|
541
666
|
dispatchCommand(msg) {
|
|
542
667
|
const handler = this.commandHandlers[msg.type];
|
|
543
|
-
handler
|
|
668
|
+
if (!handler) {
|
|
669
|
+
console.warn(`[vibe-host] Received unknown command "${msg.type}" \u2014 ignoring.`);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
handler(msg.payload);
|
|
674
|
+
} catch (err) {
|
|
675
|
+
console.warn(`[vibe-host] Command "${msg.type}" handler threw \u2014 ignoring.`, err);
|
|
676
|
+
}
|
|
544
677
|
}
|
|
545
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Commands: `REPORT_SUBROUTE` is handled internally (transport + traversal
|
|
680
|
+
* guard, item `l`); navigation/UI side effects are injected by the consumer,
|
|
681
|
+
* falling back to warn-and-ignore (DP4).
|
|
682
|
+
*/
|
|
683
|
+
buildCommandHandlers(opts) {
|
|
684
|
+
return {
|
|
685
|
+
REPORT_SUBROUTE: (payload) => {
|
|
686
|
+
const safe = normalizeSubroute(payload.subroute);
|
|
687
|
+
if (safe === null) return;
|
|
688
|
+
const newPath = safe === "/" ? this.appBasePath : `${this.appBasePath}${safe}`;
|
|
689
|
+
const method = payload.replace ? "replaceState" : "pushState";
|
|
690
|
+
window.history[method](null, "", newPath);
|
|
691
|
+
},
|
|
692
|
+
NAVIGATE: opts.onNavigate ?? warnUnwired("NAVIGATE"),
|
|
693
|
+
SET_SIDE_NAV: opts.onSetSideNav ?? warnUnwired("SET_SIDE_NAV"),
|
|
694
|
+
SWITCH_SUBSIDIARY: opts.onSwitchSubsidiary ?? warnUnwired("SWITCH_SUBSIDIARY")
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Requests: the built-in API map (hey-api), plus the injected/awaited side
|
|
699
|
+
* effects with safe defaults — unwired `UPLOAD_FILE` rejects with a coded
|
|
700
|
+
* error; unwired `INVALIDATE_CACHE` resolves to a no-op (DP4).
|
|
701
|
+
*/
|
|
702
|
+
initializeHandlers(clientConfig, opts) {
|
|
546
703
|
return {
|
|
547
704
|
...createHandlerMap(clientConfig),
|
|
548
|
-
UPLOAD_FILE:
|
|
705
|
+
UPLOAD_FILE: opts.onUpload ?? rejectUnwired("UPLOAD_FILE"),
|
|
706
|
+
INVALIDATE_CACHE: opts.onInvalidateCache ?? (async () => ({ invalidated: false }))
|
|
549
707
|
};
|
|
550
708
|
}
|
|
551
709
|
/**
|
|
@@ -566,6 +724,7 @@ var VibeAppHost = class {
|
|
|
566
724
|
window.removeEventListener("message", this.boundHandleMessage);
|
|
567
725
|
window.removeEventListener("popstate", this.boundHandlePopState);
|
|
568
726
|
this.iframeWindow = null;
|
|
727
|
+
this.connectedOrigins.clear();
|
|
569
728
|
};
|
|
570
729
|
}
|
|
571
730
|
/**
|
|
@@ -585,7 +744,7 @@ var VibeAppHost = class {
|
|
|
585
744
|
type: "CONTEXT_PUSH",
|
|
586
745
|
payload: { ...this.getContext(), hostVersion: version }
|
|
587
746
|
};
|
|
588
|
-
for (const origin of this.
|
|
747
|
+
for (const origin of this.concreteTargets()) {
|
|
589
748
|
target.postMessage(message, origin);
|
|
590
749
|
}
|
|
591
750
|
}
|
|
@@ -605,24 +764,55 @@ var VibeAppHost = class {
|
|
|
605
764
|
type,
|
|
606
765
|
payload
|
|
607
766
|
};
|
|
608
|
-
for (const origin of this.
|
|
767
|
+
for (const origin of this.concreteTargets()) {
|
|
609
768
|
this.iframeWindow.postMessage(message, origin);
|
|
610
769
|
}
|
|
611
770
|
}
|
|
612
771
|
async handleMessage(event) {
|
|
613
|
-
if (!
|
|
772
|
+
if (!isOriginAllowed(event.origin, this.trustedOrigins, { allowPatterns: true })) return;
|
|
614
773
|
if (!isBridgeMessage(event.data)) return;
|
|
774
|
+
this.connectedOrigins.add(event.origin);
|
|
615
775
|
await this.kindHandlers[event.data.kind]?.(event.data, event.source, event.origin);
|
|
616
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* Concrete origins a proactive push may target: the exact allowlist entries
|
|
779
|
+
* plus any origins seen on validated inbound messages. Never includes glob
|
|
780
|
+
* patterns (you cannot `postMessage` to one). When only exact origins are
|
|
781
|
+
* configured this equals the configured set, so behaviour is unchanged.
|
|
782
|
+
*/
|
|
783
|
+
concreteTargets() {
|
|
784
|
+
return /* @__PURE__ */ new Set([...this.exactOrigins, ...this.connectedOrigins]);
|
|
785
|
+
}
|
|
617
786
|
async handleRequest(msg, source, origin) {
|
|
618
787
|
const { requestId, type } = msg;
|
|
619
788
|
if (type === "GET_CONTEXT") {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
789
|
+
try {
|
|
790
|
+
const ctx = { ...this.getContext(), hostVersion: version };
|
|
791
|
+
if (!this.handshakeLoggedWindows.has(source)) {
|
|
792
|
+
this.handshakeLoggedWindows.add(source);
|
|
793
|
+
const bridgeVersion = msg.payload?.bridgeVersion ?? "unknown";
|
|
794
|
+
console.log(
|
|
795
|
+
`[vibe-host] Vibe App connected \u2014 bridge: ${bridgeVersion}, host: ${version}, tenant: ${ctx.tenant}, user: ${ctx.user.displayName}`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
this.respond(source, origin, { kind: "response", type, requestId, ok: true, data: ctx });
|
|
799
|
+
} catch (err) {
|
|
800
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
801
|
+
const code = err instanceof BridgeError ? err.code : "CONTEXT_ERROR";
|
|
802
|
+
this.respond(source, origin, { kind: "response", type, requestId, ok: false, error, code });
|
|
803
|
+
}
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (!checkRateLimit(this.rateLimiter)) {
|
|
807
|
+
this.respond(source, origin, {
|
|
808
|
+
kind: "response",
|
|
809
|
+
type,
|
|
810
|
+
requestId,
|
|
811
|
+
ok: false,
|
|
812
|
+
error: `Too many requests. Limit is ${this.rateLimiter.limit} per minute.`,
|
|
813
|
+
code: "RATE_LIMITED"
|
|
814
|
+
});
|
|
815
|
+
this.onRequestComplete?.({ type, ok: false, payload: msg.payload });
|
|
626
816
|
return;
|
|
627
817
|
}
|
|
628
818
|
const sendProgress = (payload) => {
|
|
@@ -640,9 +830,21 @@ var VibeAppHost = class {
|
|
|
640
830
|
const handler = this.handlers[type];
|
|
641
831
|
const data = await handler(msg.payload, sendProgress);
|
|
642
832
|
this.respond(source, origin, { kind: "response", type, requestId, ok: true, data });
|
|
833
|
+
this.onRequestComplete?.({ type, ok: true, payload: msg.payload });
|
|
643
834
|
} catch (err) {
|
|
644
835
|
const error = err instanceof Error ? err.message : String(err);
|
|
645
|
-
|
|
836
|
+
const code = err instanceof BridgeError ? err.code : void 0;
|
|
837
|
+
const status = err instanceof HttpBridgeError ? err.status : void 0;
|
|
838
|
+
this.respond(source, origin, {
|
|
839
|
+
kind: "response",
|
|
840
|
+
type,
|
|
841
|
+
requestId,
|
|
842
|
+
ok: false,
|
|
843
|
+
error,
|
|
844
|
+
code,
|
|
845
|
+
status
|
|
846
|
+
});
|
|
847
|
+
this.onRequestComplete?.({ type, ok: false, payload: msg.payload });
|
|
646
848
|
}
|
|
647
849
|
}
|
|
648
850
|
respond(target, origin, response) {
|
|
@@ -653,5 +855,7 @@ var VibeAppHost = class {
|
|
|
653
855
|
}
|
|
654
856
|
};
|
|
655
857
|
export {
|
|
858
|
+
BridgeError,
|
|
859
|
+
HttpBridgeError,
|
|
656
860
|
VibeAppHost
|
|
657
861
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nominalso/vibe-host",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Host-side SDK for embedding Nominal Vibe Apps — receives bridge requests over a typed postMessage protocol and dispatches them to Nominal APIs (used by nom-ui).",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://github.com/nominalso/vibe-apps-sdk#readme",
|