@crvouga/mockingbird-service-intercom 1.5.0 → 1.6.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/CHANGELOG.md +10 -0
- package/dist/{chunk-ABX6FYZ2.js → chunk-XAJNVT3G.js} +2 -2
- package/dist/{chunk-T56VJH23.js → chunk-ZKUDPTGZ.js} +1430 -626
- package/dist/chunk-ZKUDPTGZ.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.js +2 -2
- package/package.json +3 -3
- package/dist/chunk-T56VJH23.js.map +0 -7
- /package/dist/{chunk-ABX6FYZ2.js.map → chunk-XAJNVT3G.js.map} +0 -0
|
@@ -540,10 +540,10 @@ var OpenAPIReferenceError = class extends Error {
|
|
|
540
540
|
};
|
|
541
541
|
var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
542
542
|
var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
543
|
-
var resolveRef = (
|
|
543
|
+
var resolveRef = (document3, ref) => {
|
|
544
544
|
if (!ref.startsWith("#/"))
|
|
545
545
|
throw new OpenAPIReferenceError(ref);
|
|
546
|
-
let cursor =
|
|
546
|
+
let cursor = document3;
|
|
547
547
|
for (const raw of ref.slice(2).split("/")) {
|
|
548
548
|
const segment = unescapePointer(raw);
|
|
549
549
|
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
@@ -555,14 +555,14 @@ var resolveRef = (document2, ref) => {
|
|
|
555
555
|
throw new OpenAPIReferenceError(ref);
|
|
556
556
|
return cursor;
|
|
557
557
|
};
|
|
558
|
-
var deref = (
|
|
558
|
+
var deref = (document3, value) => {
|
|
559
559
|
let current = value;
|
|
560
560
|
const seen = /* @__PURE__ */ new Set();
|
|
561
561
|
while (isReference(current)) {
|
|
562
562
|
if (seen.has(current.$ref))
|
|
563
563
|
throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
|
|
564
564
|
seen.add(current.$ref);
|
|
565
|
-
current = resolveRef(
|
|
565
|
+
current = resolveRef(document3, current.$ref);
|
|
566
566
|
}
|
|
567
567
|
return current;
|
|
568
568
|
};
|
|
@@ -580,36 +580,36 @@ var HTTP_METHODS = [
|
|
|
580
580
|
];
|
|
581
581
|
|
|
582
582
|
// ../../openapi/core/dist/document.js
|
|
583
|
-
var mergeParameters = (
|
|
583
|
+
var mergeParameters = (document3, item, own) => {
|
|
584
584
|
const merged = /* @__PURE__ */ new Map();
|
|
585
585
|
for (const raw of item.parameters ?? []) {
|
|
586
|
-
const parameter = deref(
|
|
586
|
+
const parameter = deref(document3, raw);
|
|
587
587
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
588
588
|
}
|
|
589
589
|
for (const raw of own ?? []) {
|
|
590
|
-
const parameter = deref(
|
|
590
|
+
const parameter = deref(document3, raw);
|
|
591
591
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
592
592
|
}
|
|
593
593
|
return [...merged.values()];
|
|
594
594
|
};
|
|
595
|
-
var listOperations = (
|
|
595
|
+
var listOperations = (document3) => {
|
|
596
596
|
const operations = [];
|
|
597
|
-
for (const [path, item] of Object.entries(
|
|
597
|
+
for (const [path, item] of Object.entries(document3.paths)) {
|
|
598
598
|
for (const method of HTTP_METHODS) {
|
|
599
599
|
const operation = item[method];
|
|
600
600
|
if (operation?.operationId === void 0)
|
|
601
601
|
continue;
|
|
602
602
|
const responses = {};
|
|
603
603
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
604
|
-
responses[status] = deref(
|
|
604
|
+
responses[status] = deref(document3, response);
|
|
605
605
|
}
|
|
606
606
|
operations.push({
|
|
607
607
|
operationId: operation.operationId,
|
|
608
608
|
method,
|
|
609
609
|
path,
|
|
610
610
|
operation,
|
|
611
|
-
parameters: mergeParameters(
|
|
612
|
-
requestBody: operation.requestBody === void 0 ? void 0 : deref(
|
|
611
|
+
parameters: mergeParameters(document3, item, operation.parameters),
|
|
612
|
+
requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
|
|
613
613
|
responses
|
|
614
614
|
});
|
|
615
615
|
}
|
|
@@ -618,7 +618,7 @@ var listOperations = (document2) => {
|
|
|
618
618
|
};
|
|
619
619
|
|
|
620
620
|
// ../../openapi/core/dist/schema.js
|
|
621
|
-
var resolveSchema = (
|
|
621
|
+
var resolveSchema = (document3, schema) => {
|
|
622
622
|
let current = schema;
|
|
623
623
|
const seen = /* @__PURE__ */ new Set();
|
|
624
624
|
while (typeof current.$ref === "string") {
|
|
@@ -627,7 +627,7 @@ var resolveSchema = (document2, schema) => {
|
|
|
627
627
|
break;
|
|
628
628
|
seen.add(ref);
|
|
629
629
|
const { $ref: _ignored, ...siblings } = current;
|
|
630
|
-
const target = resolveRef(
|
|
630
|
+
const target = resolveRef(document3, ref);
|
|
631
631
|
current = { ...target, ...siblings };
|
|
632
632
|
}
|
|
633
633
|
if (current.nullable === true) {
|
|
@@ -698,9 +698,9 @@ var FORMAT_PATTERNS = {
|
|
|
698
698
|
ipv4: /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/
|
|
699
699
|
};
|
|
700
700
|
var graphemeLength = (value) => [...value].length;
|
|
701
|
-
var validateValue = (
|
|
701
|
+
var validateValue = (document3, schema, value, path = []) => {
|
|
702
702
|
const errors = [];
|
|
703
|
-
const s = resolveSchema(
|
|
703
|
+
const s = resolveSchema(document3, schema);
|
|
704
704
|
const fail2 = (message) => errors.push({ path, message });
|
|
705
705
|
const actual = jsonTypeOf(value);
|
|
706
706
|
if (actual === "undefined") {
|
|
@@ -762,7 +762,7 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
762
762
|
value.forEach((item, i) => {
|
|
763
763
|
const itemSchema = s.prefixItems?.[i] ?? s.items;
|
|
764
764
|
if (itemSchema)
|
|
765
|
-
errors.push(...validateValue(
|
|
765
|
+
errors.push(...validateValue(document3, itemSchema, item, [...path, i]));
|
|
766
766
|
});
|
|
767
767
|
}
|
|
768
768
|
if (actual === "object") {
|
|
@@ -778,16 +778,16 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
778
778
|
for (const key of keys) {
|
|
779
779
|
const property = s.properties?.[key];
|
|
780
780
|
if (property) {
|
|
781
|
-
errors.push(...validateValue(
|
|
781
|
+
errors.push(...validateValue(document3, property, record[key], [...path, key]));
|
|
782
782
|
continue;
|
|
783
783
|
}
|
|
784
784
|
if (s.additionalProperties === false)
|
|
785
785
|
fail2(`unexpected property ${key}`);
|
|
786
786
|
else if (typeof s.additionalProperties === "object") {
|
|
787
|
-
errors.push(...validateValue(
|
|
787
|
+
errors.push(...validateValue(document3, s.additionalProperties, record[key], [...path, key]));
|
|
788
788
|
}
|
|
789
789
|
if (s.propertyNames) {
|
|
790
|
-
const nameErrors = validateValue(
|
|
790
|
+
const nameErrors = validateValue(document3, s.propertyNames, key, [...path, key]);
|
|
791
791
|
if (nameErrors.length > 0)
|
|
792
792
|
fail2(`property name ${key} is invalid: ${nameErrors[0]?.message}`);
|
|
793
793
|
}
|
|
@@ -795,15 +795,15 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
795
795
|
}
|
|
796
796
|
if (s.allOf)
|
|
797
797
|
for (const branch of s.allOf)
|
|
798
|
-
errors.push(...validateValue(
|
|
799
|
-
if (s.anyOf && !s.anyOf.some((branch) => validateValue(
|
|
798
|
+
errors.push(...validateValue(document3, branch, value, path));
|
|
799
|
+
if (s.anyOf && !s.anyOf.some((branch) => validateValue(document3, branch, value).length === 0))
|
|
800
800
|
fail2("matches no anyOf branch");
|
|
801
801
|
if (s.oneOf) {
|
|
802
|
-
const matches3 = s.oneOf.filter((branch) => validateValue(
|
|
802
|
+
const matches3 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
|
|
803
803
|
if (matches3 !== 1)
|
|
804
804
|
fail2(`matches ${matches3} oneOf branches, expected exactly 1`);
|
|
805
805
|
}
|
|
806
|
-
if (s.not && validateValue(
|
|
806
|
+
if (s.not && validateValue(document3, s.not, value).length === 0)
|
|
807
807
|
fail2("matches forbidden `not` schema");
|
|
808
808
|
return errors;
|
|
809
809
|
};
|
|
@@ -1444,6 +1444,1232 @@ var clearNamespace = (sqlite, namespace) => {
|
|
|
1444
1444
|
});
|
|
1445
1445
|
};
|
|
1446
1446
|
|
|
1447
|
+
// ../../ui/dist/reset.js
|
|
1448
|
+
var CSS_RESET = `*,
|
|
1449
|
+
*::before,
|
|
1450
|
+
*::after {
|
|
1451
|
+
box-sizing: border-box;
|
|
1452
|
+
margin: 0;
|
|
1453
|
+
padding: 0;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
* {
|
|
1457
|
+
scrollbar-width: thin;
|
|
1458
|
+
scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
*::-webkit-scrollbar {
|
|
1462
|
+
width: 12px;
|
|
1463
|
+
height: 12px;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
*::-webkit-scrollbar-track,
|
|
1467
|
+
*::-webkit-scrollbar-corner {
|
|
1468
|
+
background: Canvas;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
*::-webkit-scrollbar-thumb {
|
|
1472
|
+
background: color-mix(in srgb, CanvasText 35%, Canvas);
|
|
1473
|
+
border: 3px solid Canvas;
|
|
1474
|
+
border-radius: 999px;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
html {
|
|
1478
|
+
hanging-punctuation: first allow-end last;
|
|
1479
|
+
-webkit-text-size-adjust: none;
|
|
1480
|
+
text-size-adjust: none;
|
|
1481
|
+
tab-size: 4;
|
|
1482
|
+
font-family:
|
|
1483
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
|
|
1484
|
+
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1485
|
+
font-size: 16px;
|
|
1486
|
+
line-height: 1.5;
|
|
1487
|
+
-webkit-font-smoothing: antialiased;
|
|
1488
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1489
|
+
text-rendering: optimizeLegibility;
|
|
1490
|
+
-webkit-tap-highlight-color: transparent;
|
|
1491
|
+
scroll-behavior: smooth;
|
|
1492
|
+
background: Canvas;
|
|
1493
|
+
color: CanvasText;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
body {
|
|
1497
|
+
min-height: 100svh;
|
|
1498
|
+
line-height: 1.5;
|
|
1499
|
+
background: Canvas;
|
|
1500
|
+
color: CanvasText;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
h1 {
|
|
1504
|
+
font-size: 2em;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
h2 {
|
|
1508
|
+
font-size: 1.5em;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
h3 {
|
|
1512
|
+
font-size: 1.17em;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
h4 {
|
|
1516
|
+
font-size: 1em;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
h5 {
|
|
1520
|
+
font-size: 0.83em;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
h6 {
|
|
1524
|
+
font-size: 0.67em;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
h1,
|
|
1528
|
+
h2,
|
|
1529
|
+
h3,
|
|
1530
|
+
h4,
|
|
1531
|
+
h5,
|
|
1532
|
+
h6 {
|
|
1533
|
+
font-weight: 700;
|
|
1534
|
+
line-height: 1.2;
|
|
1535
|
+
text-wrap: balance;
|
|
1536
|
+
overflow-wrap: break-word;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
p,
|
|
1540
|
+
li,
|
|
1541
|
+
figcaption,
|
|
1542
|
+
dd,
|
|
1543
|
+
dt,
|
|
1544
|
+
blockquote {
|
|
1545
|
+
overflow-wrap: break-word;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
p,
|
|
1549
|
+
li,
|
|
1550
|
+
figcaption,
|
|
1551
|
+
blockquote {
|
|
1552
|
+
text-wrap: pretty;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
ul,
|
|
1556
|
+
ol,
|
|
1557
|
+
menu {
|
|
1558
|
+
list-style: none;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
a {
|
|
1562
|
+
color: inherit;
|
|
1563
|
+
text-decoration: underline;
|
|
1564
|
+
text-decoration-thickness: 1px;
|
|
1565
|
+
text-underline-offset: 0.15em;
|
|
1566
|
+
text-decoration-skip-ink: auto;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
b,
|
|
1570
|
+
strong {
|
|
1571
|
+
font-weight: 700;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
small {
|
|
1575
|
+
font-size: 0.875em;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
code,
|
|
1579
|
+
kbd,
|
|
1580
|
+
samp,
|
|
1581
|
+
pre {
|
|
1582
|
+
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1583
|
+
font-size: 1em;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
sub,
|
|
1587
|
+
sup {
|
|
1588
|
+
font-size: 75%;
|
|
1589
|
+
line-height: 0;
|
|
1590
|
+
position: relative;
|
|
1591
|
+
vertical-align: baseline;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
sub {
|
|
1595
|
+
bottom: -0.25em;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
sup {
|
|
1599
|
+
top: -0.5em;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
abbr[title] {
|
|
1603
|
+
text-decoration: underline dotted;
|
|
1604
|
+
text-underline-offset: 0.15em;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
mark {
|
|
1608
|
+
background: Mark;
|
|
1609
|
+
color: MarkText;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
hr {
|
|
1613
|
+
height: 0;
|
|
1614
|
+
border: 0;
|
|
1615
|
+
border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1616
|
+
color: inherit;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
img,
|
|
1620
|
+
picture,
|
|
1621
|
+
video,
|
|
1622
|
+
canvas {
|
|
1623
|
+
display: block;
|
|
1624
|
+
max-width: 100%;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
img,
|
|
1628
|
+
video {
|
|
1629
|
+
height: auto;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
svg {
|
|
1633
|
+
overflow: hidden;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
iframe {
|
|
1637
|
+
border: 0;
|
|
1638
|
+
max-width: 100%;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
table {
|
|
1642
|
+
border-collapse: collapse;
|
|
1643
|
+
border-spacing: 0;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
fieldset {
|
|
1647
|
+
border: 0;
|
|
1648
|
+
min-inline-size: 0;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
legend {
|
|
1652
|
+
padding: 0;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
button,
|
|
1656
|
+
input,
|
|
1657
|
+
select,
|
|
1658
|
+
textarea,
|
|
1659
|
+
optgroup {
|
|
1660
|
+
font: inherit;
|
|
1661
|
+
letter-spacing: inherit;
|
|
1662
|
+
color: inherit;
|
|
1663
|
+
margin: 0;
|
|
1664
|
+
vertical-align: middle;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
button,
|
|
1668
|
+
input[type="button"],
|
|
1669
|
+
input[type="submit"],
|
|
1670
|
+
input[type="reset"] {
|
|
1671
|
+
appearance: none;
|
|
1672
|
+
background: none;
|
|
1673
|
+
border: 0;
|
|
1674
|
+
padding: 0;
|
|
1675
|
+
cursor: pointer;
|
|
1676
|
+
text-align: center;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
input,
|
|
1680
|
+
textarea,
|
|
1681
|
+
select {
|
|
1682
|
+
background-color: Canvas;
|
|
1683
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1684
|
+
border-radius: 0;
|
|
1685
|
+
padding: 0.5em 0.65em;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
input[type="checkbox"],
|
|
1689
|
+
input[type="radio"],
|
|
1690
|
+
input[type="range"],
|
|
1691
|
+
input[type="file"],
|
|
1692
|
+
input[type="color"],
|
|
1693
|
+
input[type="button"],
|
|
1694
|
+
input[type="submit"],
|
|
1695
|
+
input[type="reset"],
|
|
1696
|
+
input[type="image"],
|
|
1697
|
+
input[type="hidden"] {
|
|
1698
|
+
background: none;
|
|
1699
|
+
border: 0;
|
|
1700
|
+
padding: 0;
|
|
1701
|
+
border-radius: 0;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
input[type="checkbox"],
|
|
1705
|
+
input[type="radio"] {
|
|
1706
|
+
accent-color: CanvasText;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
input[type="search"] {
|
|
1710
|
+
appearance: none;
|
|
1711
|
+
background-color: Canvas;
|
|
1712
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
input[type="search"]::-webkit-search-decoration,
|
|
1716
|
+
input[type="search"]::-webkit-search-cancel-button,
|
|
1717
|
+
input[type="search"]::-webkit-search-results-button,
|
|
1718
|
+
input[type="search"]::-webkit-search-results-decoration {
|
|
1719
|
+
-webkit-appearance: none;
|
|
1720
|
+
display: none;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
input[type="number"] {
|
|
1724
|
+
appearance: textfield;
|
|
1725
|
+
background-color: Canvas;
|
|
1726
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
1730
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
1731
|
+
-webkit-appearance: none;
|
|
1732
|
+
margin: 0;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
textarea {
|
|
1736
|
+
resize: vertical;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
::placeholder {
|
|
1740
|
+
opacity: 1;
|
|
1741
|
+
color: color-mix(in srgb, CanvasText 45%, transparent);
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
:focus-visible {
|
|
1745
|
+
outline: 2px solid CanvasText;
|
|
1746
|
+
outline-offset: 2px;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
:focus:not(:focus-visible) {
|
|
1750
|
+
outline: none;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
:disabled,
|
|
1754
|
+
[disabled] {
|
|
1755
|
+
cursor: default;
|
|
1756
|
+
opacity: 0.6;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
label,
|
|
1760
|
+
summary,
|
|
1761
|
+
select,
|
|
1762
|
+
[role="button"] {
|
|
1763
|
+
cursor: pointer;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
summary {
|
|
1767
|
+
display: list-item;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
details {
|
|
1771
|
+
display: block;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
dialog {
|
|
1775
|
+
margin: auto;
|
|
1776
|
+
padding: 1rem;
|
|
1777
|
+
border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1778
|
+
background: Canvas;
|
|
1779
|
+
color: CanvasText;
|
|
1780
|
+
max-width: calc(100vw - 2rem);
|
|
1781
|
+
max-height: calc(100svh - 2rem);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
dialog:not([open]) {
|
|
1785
|
+
display: none;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
::backdrop {
|
|
1789
|
+
background: color-mix(in srgb, CanvasText 40%, transparent);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
[hidden] {
|
|
1793
|
+
display: none !important;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
::selection {
|
|
1797
|
+
background: Highlight;
|
|
1798
|
+
color: HighlightText;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1802
|
+
*,
|
|
1803
|
+
*::before,
|
|
1804
|
+
*::after {
|
|
1805
|
+
animation-duration: 0.01ms !important;
|
|
1806
|
+
animation-iteration-count: 1 !important;
|
|
1807
|
+
transition-duration: 0.01ms !important;
|
|
1808
|
+
scroll-behavior: auto !important;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
@media (pointer: coarse) {
|
|
1813
|
+
input,
|
|
1814
|
+
select,
|
|
1815
|
+
textarea {
|
|
1816
|
+
font-size: max(16px, 1em);
|
|
1817
|
+
}
|
|
1818
|
+
}`;
|
|
1819
|
+
|
|
1820
|
+
// ../core/dist/admin-client.js
|
|
1821
|
+
function isRecord2(value) {
|
|
1822
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1823
|
+
}
|
|
1824
|
+
function mountAdminBrand(doc, host, service, catalog) {
|
|
1825
|
+
const clip = (value, max) => {
|
|
1826
|
+
if (typeof value !== "string")
|
|
1827
|
+
return null;
|
|
1828
|
+
const trimmed = value.trim();
|
|
1829
|
+
if (trimmed === "")
|
|
1830
|
+
return null;
|
|
1831
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
1832
|
+
};
|
|
1833
|
+
const httpUrl = (value) => {
|
|
1834
|
+
if (typeof value !== "string" || value.length > 2e3)
|
|
1835
|
+
return null;
|
|
1836
|
+
try {
|
|
1837
|
+
const url = new URL(value);
|
|
1838
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1839
|
+
return null;
|
|
1840
|
+
return url.href;
|
|
1841
|
+
} catch {
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
const hexColor = (value) => {
|
|
1846
|
+
if (typeof value !== "string")
|
|
1847
|
+
return null;
|
|
1848
|
+
const raw = value.trim();
|
|
1849
|
+
return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
|
|
1850
|
+
};
|
|
1851
|
+
const hostname = (value) => {
|
|
1852
|
+
try {
|
|
1853
|
+
return new URL(value).hostname.replace(/^www\./, "");
|
|
1854
|
+
} catch {
|
|
1855
|
+
return value;
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
host.replaceChildren();
|
|
1859
|
+
host.hidden = true;
|
|
1860
|
+
if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
|
|
1861
|
+
return;
|
|
1862
|
+
const row = catalog[service];
|
|
1863
|
+
if (!isRecord2(row))
|
|
1864
|
+
return;
|
|
1865
|
+
const vendor = clip(row.vendor, 80);
|
|
1866
|
+
const website = httpUrl(row.website);
|
|
1867
|
+
const guide = httpUrl(row.guide);
|
|
1868
|
+
const docs = httpUrl(row.docs);
|
|
1869
|
+
const logo = httpUrl(row.logo);
|
|
1870
|
+
const color = hexColor(row.color);
|
|
1871
|
+
const description = clip(row.description, 280);
|
|
1872
|
+
if (vendor === null || website === null && guide === null && docs === null)
|
|
1873
|
+
return;
|
|
1874
|
+
const add = (node) => {
|
|
1875
|
+
host.append(node);
|
|
1876
|
+
return node;
|
|
1877
|
+
};
|
|
1878
|
+
if (logo !== null) {
|
|
1879
|
+
const img = doc.createElement("img");
|
|
1880
|
+
img.alt = "";
|
|
1881
|
+
img.src = logo;
|
|
1882
|
+
img.width = 16;
|
|
1883
|
+
img.height = 16;
|
|
1884
|
+
img.decoding = "async";
|
|
1885
|
+
img.referrerPolicy = "no-referrer";
|
|
1886
|
+
if (color !== null)
|
|
1887
|
+
img.style.borderColor = color;
|
|
1888
|
+
add(img);
|
|
1889
|
+
}
|
|
1890
|
+
const name = doc.createElement("span");
|
|
1891
|
+
name.className = "vendor-name";
|
|
1892
|
+
name.textContent = vendor;
|
|
1893
|
+
if (description !== null)
|
|
1894
|
+
name.title = description;
|
|
1895
|
+
add(name);
|
|
1896
|
+
const sep = () => {
|
|
1897
|
+
const node = doc.createElement("span");
|
|
1898
|
+
node.className = "vendor-sep";
|
|
1899
|
+
node.setAttribute("aria-hidden", "true");
|
|
1900
|
+
node.textContent = "\xB7";
|
|
1901
|
+
return node;
|
|
1902
|
+
};
|
|
1903
|
+
const link = (href, label, title) => {
|
|
1904
|
+
const node = doc.createElement("a");
|
|
1905
|
+
node.className = "vendor-link";
|
|
1906
|
+
node.href = href;
|
|
1907
|
+
node.target = "_blank";
|
|
1908
|
+
node.rel = "noopener noreferrer";
|
|
1909
|
+
node.referrerPolicy = "no-referrer";
|
|
1910
|
+
node.textContent = label;
|
|
1911
|
+
node.title = title;
|
|
1912
|
+
return node;
|
|
1913
|
+
};
|
|
1914
|
+
if (website !== null) {
|
|
1915
|
+
add(sep());
|
|
1916
|
+
add(link(website, hostname(website), `${vendor} website`));
|
|
1917
|
+
}
|
|
1918
|
+
if (guide !== null) {
|
|
1919
|
+
add(sep());
|
|
1920
|
+
add(link(guide, "Docs", `Docs for ${vendor}`));
|
|
1921
|
+
}
|
|
1922
|
+
if (docs !== null) {
|
|
1923
|
+
add(sep());
|
|
1924
|
+
add(link(docs, "API", `${vendor} API reference`));
|
|
1925
|
+
}
|
|
1926
|
+
host.removeAttribute("hidden");
|
|
1927
|
+
host.hidden = false;
|
|
1928
|
+
}
|
|
1929
|
+
function startAdminBrand(env) {
|
|
1930
|
+
const requested = env.params.get("brands");
|
|
1931
|
+
let brandsUrl = env.brandsUrl;
|
|
1932
|
+
if (requested) {
|
|
1933
|
+
try {
|
|
1934
|
+
const url = new URL(requested, env.location.href);
|
|
1935
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
|
|
1936
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && local)
|
|
1937
|
+
brandsUrl = url.href;
|
|
1938
|
+
} catch {
|
|
1939
|
+
brandsUrl = env.brandsUrl;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
const vendorHost = env.$("vendor");
|
|
1943
|
+
const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
|
|
1944
|
+
const brandService = brandRoot?.getAttribute("data-service") || "";
|
|
1945
|
+
if (vendorHost === null)
|
|
1946
|
+
return;
|
|
1947
|
+
env.fetch(brandsUrl, {
|
|
1948
|
+
headers: { accept: "application/json" },
|
|
1949
|
+
signal: AbortSignal.timeout(5e3)
|
|
1950
|
+
}).then(async (response) => {
|
|
1951
|
+
if (!response.ok)
|
|
1952
|
+
return null;
|
|
1953
|
+
const catalog = await response.json();
|
|
1954
|
+
return catalog;
|
|
1955
|
+
}).then((catalog) => {
|
|
1956
|
+
mountAdminBrand(env.document, vendorHost, brandService, catalog);
|
|
1957
|
+
}).catch(() => {
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
function faultPresetList(value) {
|
|
1961
|
+
if (!isRecord2(value))
|
|
1962
|
+
throw new Error("Fault presets response was not an object");
|
|
1963
|
+
if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
|
|
1964
|
+
return { presets: [] };
|
|
1965
|
+
}
|
|
1966
|
+
if (!Array.isArray(value.presets))
|
|
1967
|
+
throw new Error("Fault presets response was not a list");
|
|
1968
|
+
return {
|
|
1969
|
+
presets: value.presets.flatMap((item) => {
|
|
1970
|
+
if (!isRecord2(item) || typeof item.name !== "string")
|
|
1971
|
+
return [];
|
|
1972
|
+
return [{ name: item.name }];
|
|
1973
|
+
})
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
function bootAdmin(config) {
|
|
1977
|
+
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1978
|
+
const sources = ["declared", "inferred", "mixed"];
|
|
1979
|
+
const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
|
|
1980
|
+
const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
|
|
1981
|
+
const readJson2 = (text2) => {
|
|
1982
|
+
const value = JSON.parse(text2);
|
|
1983
|
+
return value;
|
|
1984
|
+
};
|
|
1985
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
|
|
1986
|
+
switch (char) {
|
|
1987
|
+
case "&":
|
|
1988
|
+
return "&";
|
|
1989
|
+
case "<":
|
|
1990
|
+
return "<";
|
|
1991
|
+
case ">":
|
|
1992
|
+
return ">";
|
|
1993
|
+
case '"':
|
|
1994
|
+
return """;
|
|
1995
|
+
default:
|
|
1996
|
+
return "'";
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
const el = (id, kind) => {
|
|
2000
|
+
const node = document.getElementById(id);
|
|
2001
|
+
if (!(node instanceof kind))
|
|
2002
|
+
throw new Error(`admin shell is missing #${id}`);
|
|
2003
|
+
return node;
|
|
2004
|
+
};
|
|
2005
|
+
const errorMessage = (data, fallback) => {
|
|
2006
|
+
if (!isRecord2(data))
|
|
2007
|
+
return fallback;
|
|
2008
|
+
const error = data.error;
|
|
2009
|
+
if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
|
|
2010
|
+
return fallback;
|
|
2011
|
+
}
|
|
2012
|
+
return error.message;
|
|
2013
|
+
};
|
|
2014
|
+
const requireList = (value, label) => {
|
|
2015
|
+
if (value === void 0 || value === null)
|
|
2016
|
+
return [];
|
|
2017
|
+
if (!Array.isArray(value))
|
|
2018
|
+
throw new Error(`${label} was not a list`);
|
|
2019
|
+
return value;
|
|
2020
|
+
};
|
|
2021
|
+
const state = {
|
|
2022
|
+
namespace: "default",
|
|
2023
|
+
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
2024
|
+
view: "overview",
|
|
2025
|
+
collection: "",
|
|
2026
|
+
shape: null,
|
|
2027
|
+
route: null,
|
|
2028
|
+
editing: null
|
|
2029
|
+
};
|
|
2030
|
+
const standard = new Set(config.standardRoutes);
|
|
2031
|
+
const banner = el("banner", HTMLElement);
|
|
2032
|
+
const keyInput = el("admin-key", HTMLInputElement);
|
|
2033
|
+
keyInput.value = state.key;
|
|
2034
|
+
const params = new URLSearchParams(location.search);
|
|
2035
|
+
const queryKey = params.get("key");
|
|
2036
|
+
if (queryKey) {
|
|
2037
|
+
state.key = queryKey;
|
|
2038
|
+
keyInput.value = state.key;
|
|
2039
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
2040
|
+
params.delete("key");
|
|
2041
|
+
const next = params.toString();
|
|
2042
|
+
history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
|
|
2043
|
+
}
|
|
2044
|
+
const queryNamespace = params.get("namespace");
|
|
2045
|
+
if (queryNamespace)
|
|
2046
|
+
state.namespace = queryNamespace;
|
|
2047
|
+
const showError = (error) => {
|
|
2048
|
+
banner.textContent = error instanceof Error ? error.message : String(error);
|
|
2049
|
+
banner.classList.add("show");
|
|
2050
|
+
};
|
|
2051
|
+
const clearError = () => {
|
|
2052
|
+
banner.classList.remove("show");
|
|
2053
|
+
};
|
|
2054
|
+
const api = async (method, path, body) => {
|
|
2055
|
+
const headers = { accept: "application/json" };
|
|
2056
|
+
if (state.key !== "")
|
|
2057
|
+
headers[config.adminKeyHeader] = state.key;
|
|
2058
|
+
if (state.namespace !== "")
|
|
2059
|
+
headers["x-mockingbird-namespace"] = state.namespace;
|
|
2060
|
+
if (body !== void 0)
|
|
2061
|
+
headers["content-type"] = "application/json";
|
|
2062
|
+
const init = { method, headers };
|
|
2063
|
+
if (body !== void 0)
|
|
2064
|
+
init.body = JSON.stringify(body);
|
|
2065
|
+
const response = await fetch(config.adminPrefix + path, init);
|
|
2066
|
+
const text2 = await response.text();
|
|
2067
|
+
let data = null;
|
|
2068
|
+
if (text2 !== "") {
|
|
2069
|
+
try {
|
|
2070
|
+
data = readJson2(text2);
|
|
2071
|
+
} catch {
|
|
2072
|
+
data = { raw: text2 };
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
if (response.status === 401) {
|
|
2076
|
+
keyInput.focus();
|
|
2077
|
+
throw new Error(errorMessage(data, "Admin key required"));
|
|
2078
|
+
}
|
|
2079
|
+
if (!response.ok) {
|
|
2080
|
+
throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
|
|
2081
|
+
}
|
|
2082
|
+
return data;
|
|
2083
|
+
};
|
|
2084
|
+
const originGet = async (path) => {
|
|
2085
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
2086
|
+
if (!response.ok)
|
|
2087
|
+
throw new Error(`${response.status} ${path}`);
|
|
2088
|
+
const value = await response.json();
|
|
2089
|
+
return value;
|
|
2090
|
+
};
|
|
2091
|
+
const panelApi = {
|
|
2092
|
+
get: (path) => api("GET", path),
|
|
2093
|
+
send: (method, path, body) => api(method, path, body),
|
|
2094
|
+
namespace: () => state.namespace
|
|
2095
|
+
};
|
|
2096
|
+
const isPanelScript = (value) => typeof value === "function";
|
|
2097
|
+
const setTheme = (theme) => {
|
|
2098
|
+
if (theme)
|
|
2099
|
+
document.documentElement.dataset.theme = theme;
|
|
2100
|
+
else
|
|
2101
|
+
delete document.documentElement.dataset.theme;
|
|
2102
|
+
};
|
|
2103
|
+
const storedTheme = localStorage.getItem("mockingbird-admin-theme");
|
|
2104
|
+
if (storedTheme)
|
|
2105
|
+
setTheme(storedTheme);
|
|
2106
|
+
el("theme", HTMLButtonElement).addEventListener("click", () => {
|
|
2107
|
+
const current = document.documentElement.dataset.theme;
|
|
2108
|
+
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2109
|
+
const next = dark ? "light" : "dark";
|
|
2110
|
+
localStorage.setItem("mockingbird-admin-theme", next);
|
|
2111
|
+
setTheme(next);
|
|
2112
|
+
});
|
|
2113
|
+
const show = (view) => {
|
|
2114
|
+
state.view = view;
|
|
2115
|
+
for (const button of document.querySelectorAll("#nav button")) {
|
|
2116
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2117
|
+
continue;
|
|
2118
|
+
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
|
|
2119
|
+
}
|
|
2120
|
+
for (const section of document.querySelectorAll("main .view")) {
|
|
2121
|
+
if (!(section instanceof HTMLElement))
|
|
2122
|
+
continue;
|
|
2123
|
+
section.hidden = section.id !== `view-${view}`;
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
el("nav", HTMLElement).addEventListener("click", (event) => {
|
|
2127
|
+
const target = event.target;
|
|
2128
|
+
if (!(target instanceof Element))
|
|
2129
|
+
return;
|
|
2130
|
+
const button = target.closest("button[data-view]");
|
|
2131
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2132
|
+
return;
|
|
2133
|
+
const view = button.dataset.view;
|
|
2134
|
+
if (view === void 0)
|
|
2135
|
+
return;
|
|
2136
|
+
show(view);
|
|
2137
|
+
refresh().catch(showError);
|
|
2138
|
+
});
|
|
2139
|
+
const cards = (items) => `<div class="grid">${items.map((item) => `<article class="card"><div class="k">${item.k}</div><div class="v">${item.v}</div></article>`).join("")}</div>`;
|
|
2140
|
+
const table = (columns, rows) => {
|
|
2141
|
+
if (rows.length === 0)
|
|
2142
|
+
return '<p class="empty">Nothing here yet.</p>';
|
|
2143
|
+
const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
|
|
2144
|
+
const body = rows.map((row) => {
|
|
2145
|
+
const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
|
|
2146
|
+
const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
|
|
2147
|
+
return `<tr${attrs}>${cells}</tr>`;
|
|
2148
|
+
}).join("");
|
|
2149
|
+
return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
|
2150
|
+
};
|
|
2151
|
+
const preview = (value) => {
|
|
2152
|
+
const text2 = JSON.stringify(value) ?? "null";
|
|
2153
|
+
return esc(text2.length > 180 ? `${text2.slice(0, 180)}\u2026` : text2);
|
|
2154
|
+
};
|
|
2155
|
+
const when = (epoch) => {
|
|
2156
|
+
const date = new Date(epoch);
|
|
2157
|
+
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
|
|
2158
|
+
};
|
|
2159
|
+
const clockCard = (epoch) => {
|
|
2160
|
+
const date = new Date(epoch);
|
|
2161
|
+
if (Number.isNaN(date.getTime()))
|
|
2162
|
+
return esc(String(epoch));
|
|
2163
|
+
const iso = date.toISOString();
|
|
2164
|
+
return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
|
|
2165
|
+
};
|
|
2166
|
+
const decodeClock = (value) => {
|
|
2167
|
+
if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
|
|
2168
|
+
throw new Error("Clock response was not a clock");
|
|
2169
|
+
}
|
|
2170
|
+
return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
|
|
2171
|
+
};
|
|
2172
|
+
const decodeField = (value) => {
|
|
2173
|
+
if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
|
|
2174
|
+
return void 0;
|
|
2175
|
+
}
|
|
2176
|
+
const field = {
|
|
2177
|
+
name: value.name,
|
|
2178
|
+
kind: value.kind,
|
|
2179
|
+
optional: value.optional === true
|
|
2180
|
+
};
|
|
2181
|
+
if (typeof value.description === "string")
|
|
2182
|
+
field.description = value.description;
|
|
2183
|
+
if (Array.isArray(value.fields)) {
|
|
2184
|
+
field.fields = value.fields.flatMap((item) => {
|
|
2185
|
+
const child = decodeField(item);
|
|
2186
|
+
return child === void 0 ? [] : [child];
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
return field;
|
|
2190
|
+
};
|
|
2191
|
+
const decodeCollection = (value) => {
|
|
2192
|
+
if (!isRecord2(value) || typeof value.name !== "string")
|
|
2193
|
+
return void 0;
|
|
2194
|
+
const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
|
|
2195
|
+
const field = decodeField(item);
|
|
2196
|
+
return field === void 0 ? [] : [field];
|
|
2197
|
+
}) : [];
|
|
2198
|
+
const collection = {
|
|
2199
|
+
name: value.name,
|
|
2200
|
+
label: typeof value.label === "string" ? value.label : value.name,
|
|
2201
|
+
count: typeof value.count === "number" ? value.count : 0,
|
|
2202
|
+
source: isSource(value.source) ? value.source : "inferred",
|
|
2203
|
+
fields
|
|
2204
|
+
};
|
|
2205
|
+
if (typeof value.description === "string")
|
|
2206
|
+
collection.description = value.description;
|
|
2207
|
+
return collection;
|
|
2208
|
+
};
|
|
2209
|
+
const decodeState = (value) => {
|
|
2210
|
+
if (!isRecord2(value) || !Array.isArray(value.collections)) {
|
|
2211
|
+
throw new Error("State response has no collection list");
|
|
2212
|
+
}
|
|
2213
|
+
return {
|
|
2214
|
+
namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
|
|
2215
|
+
storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
|
|
2216
|
+
collections: value.collections.flatMap((item) => {
|
|
2217
|
+
const collection = decodeCollection(item);
|
|
2218
|
+
return collection === void 0 ? [] : [collection];
|
|
2219
|
+
})
|
|
2220
|
+
};
|
|
2221
|
+
};
|
|
2222
|
+
const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
|
|
2223
|
+
if (!isRecord2(item) || typeof item.id !== "string")
|
|
2224
|
+
return [];
|
|
2225
|
+
return [
|
|
2226
|
+
{
|
|
2227
|
+
id: item.id,
|
|
2228
|
+
seq: typeof item.seq === "number" ? item.seq : 0,
|
|
2229
|
+
value: item.value,
|
|
2230
|
+
attrs: ` class="clickable" data-id="${esc(item.id)}"`
|
|
2231
|
+
}
|
|
2232
|
+
];
|
|
2233
|
+
});
|
|
2234
|
+
const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
|
|
2235
|
+
const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
|
|
2236
|
+
if (!isRecord2(item))
|
|
2237
|
+
return [];
|
|
2238
|
+
return [
|
|
2239
|
+
{
|
|
2240
|
+
at: textOf(item.at),
|
|
2241
|
+
method: textOf(item.method),
|
|
2242
|
+
path: textOf(item.path),
|
|
2243
|
+
status: textOf(item.status),
|
|
2244
|
+
durationMs: textOf(item.durationMs),
|
|
2245
|
+
operationId: textOf(item.operationId)
|
|
2246
|
+
}
|
|
2247
|
+
];
|
|
2248
|
+
});
|
|
2249
|
+
const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
|
|
2250
|
+
if (!isRecord2(item))
|
|
2251
|
+
return [];
|
|
2252
|
+
return [
|
|
2253
|
+
{
|
|
2254
|
+
id: textOf(item.id),
|
|
2255
|
+
method: typeof item.method === "string" ? item.method : void 0,
|
|
2256
|
+
pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
|
|
2257
|
+
operationId: typeof item.operationId === "string" ? item.operationId : void 0,
|
|
2258
|
+
status: typeof item.status === "number" ? item.status : void 0,
|
|
2259
|
+
effect: typeof item.effect === "string" ? item.effect : void 0,
|
|
2260
|
+
drop: typeof item.drop === "boolean" ? item.drop : void 0,
|
|
2261
|
+
latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
|
|
2262
|
+
}
|
|
2263
|
+
];
|
|
2264
|
+
});
|
|
2265
|
+
const loadNamespaces = async () => {
|
|
2266
|
+
const data = await api("GET", "/namespaces");
|
|
2267
|
+
const select = el("namespace", HTMLSelectElement);
|
|
2268
|
+
const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
|
|
2269
|
+
const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
|
|
2270
|
+
const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
|
|
2271
|
+
if (!names.includes(state.namespace))
|
|
2272
|
+
names.unshift(state.namespace);
|
|
2273
|
+
select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
|
|
2274
|
+
select.value = state.namespace;
|
|
2275
|
+
};
|
|
2276
|
+
const renderOverview = async () => {
|
|
2277
|
+
const [health, shape, clock, journal] = await Promise.all([
|
|
2278
|
+
originGet("/health"),
|
|
2279
|
+
api("GET", "/state"),
|
|
2280
|
+
api("GET", "/clock"),
|
|
2281
|
+
api("GET", "/requests?limit=8")
|
|
2282
|
+
]);
|
|
2283
|
+
if (!isRecord2(health))
|
|
2284
|
+
throw new Error("Health response was not an object");
|
|
2285
|
+
const decoded = decodeState(shape);
|
|
2286
|
+
state.shape = decoded;
|
|
2287
|
+
const time = decodeClock(clock);
|
|
2288
|
+
el("overview-cards", HTMLElement).innerHTML = cards([
|
|
2289
|
+
{
|
|
2290
|
+
k: "Service",
|
|
2291
|
+
v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
|
|
2292
|
+
},
|
|
2293
|
+
{
|
|
2294
|
+
k: "Status",
|
|
2295
|
+
v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
|
|
2296
|
+
},
|
|
2297
|
+
{ k: "Namespace", v: esc(state.namespace) },
|
|
2298
|
+
{ k: "Collections", v: String(decoded.collections.length) },
|
|
2299
|
+
{
|
|
2300
|
+
k: "Records",
|
|
2301
|
+
v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
|
|
2302
|
+
},
|
|
2303
|
+
{ k: "Clock", v: clockCard(time.now) }
|
|
2304
|
+
]);
|
|
2305
|
+
el("overview-requests", HTMLElement).innerHTML = table([
|
|
2306
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2307
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2308
|
+
{
|
|
2309
|
+
label: "Status",
|
|
2310
|
+
cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
|
|
2311
|
+
},
|
|
2312
|
+
{ label: "Operation", cell: (row) => esc(row.operationId) }
|
|
2313
|
+
], decodeJournal(journal));
|
|
2314
|
+
};
|
|
2315
|
+
const schemaChips = (collection) => {
|
|
2316
|
+
if (collection.fields.length === 0) {
|
|
2317
|
+
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
|
|
2318
|
+
}
|
|
2319
|
+
return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
|
|
2320
|
+
};
|
|
2321
|
+
const renderState = async () => {
|
|
2322
|
+
const shape = decodeState(await api("GET", "/state"));
|
|
2323
|
+
state.shape = shape;
|
|
2324
|
+
const host = el("collections", HTMLElement);
|
|
2325
|
+
if (shape.collections.length === 0) {
|
|
2326
|
+
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
|
|
2327
|
+
el("schema", HTMLElement).innerHTML = "";
|
|
2328
|
+
el("records", HTMLElement).innerHTML = "";
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2332
|
+
const first = shape.collections[0];
|
|
2333
|
+
if (first === void 0)
|
|
2334
|
+
return;
|
|
2335
|
+
state.collection = first.name;
|
|
2336
|
+
}
|
|
2337
|
+
host.innerHTML = shape.collections.map((item) => `<button type="button" class="collection" data-collection="${esc(item.name)}" aria-current="${item.name === state.collection ? "true" : "false"}"><strong>${esc(item.label)}</strong><small>${esc(item.count)} \xB7 ${esc(item.source)}</small></button>`).join("");
|
|
2338
|
+
const selected = shape.collections.find((item) => item.name === state.collection);
|
|
2339
|
+
if (selected === void 0)
|
|
2340
|
+
return;
|
|
2341
|
+
const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
|
|
2342
|
+
el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
|
|
2343
|
+
const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
|
|
2344
|
+
el("records", HTMLElement).innerHTML = table([
|
|
2345
|
+
{ label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
|
|
2346
|
+
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2347
|
+
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2348
|
+
], decodeRecords(page));
|
|
2349
|
+
};
|
|
2350
|
+
const renderClock = async () => {
|
|
2351
|
+
const clock = decodeClock(await api("GET", "/clock"));
|
|
2352
|
+
el("clock-readout", HTMLElement).innerHTML = `<div class="k">Now</div><div class="v">${esc(when(clock.now))}</div><p>${esc(clock.now)} ms \xB7 offset ${esc(clock.offsetMs)} ms ${clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>"}</p>`;
|
|
2353
|
+
const freeze = el("freeze", HTMLButtonElement);
|
|
2354
|
+
freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
|
|
2355
|
+
freeze.dataset.frozen = clock.frozen ? "1" : "0";
|
|
2356
|
+
};
|
|
2357
|
+
const renderFaults = async () => {
|
|
2358
|
+
const [faults, presets] = await Promise.all([
|
|
2359
|
+
api("GET", "/faults"),
|
|
2360
|
+
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2361
|
+
]);
|
|
2362
|
+
const select = el("fault-preset", HTMLSelectElement);
|
|
2363
|
+
const current = select.value;
|
|
2364
|
+
const list = faultPresetList(presets).presets;
|
|
2365
|
+
select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
|
|
2366
|
+
select.value = current;
|
|
2367
|
+
el("fault-list", HTMLElement).innerHTML = table([
|
|
2368
|
+
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2369
|
+
{
|
|
2370
|
+
label: "Match",
|
|
2371
|
+
cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
|
|
2372
|
+
},
|
|
2373
|
+
{
|
|
2374
|
+
label: "Effect",
|
|
2375
|
+
cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
|
|
2376
|
+
}
|
|
2377
|
+
], decodeFaults(faults));
|
|
2378
|
+
};
|
|
2379
|
+
const renderJournal = async () => {
|
|
2380
|
+
const journal = await api("GET", "/requests?limit=100");
|
|
2381
|
+
el("journal", HTMLElement).innerHTML = table([
|
|
2382
|
+
{ label: "When", cell: (row) => esc(row.at) },
|
|
2383
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2384
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2385
|
+
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2386
|
+
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2387
|
+
], decodeJournal(journal));
|
|
2388
|
+
};
|
|
2389
|
+
const renderRoutes = async () => {
|
|
2390
|
+
const data = await api("GET", "/");
|
|
2391
|
+
if (!isRecord2(data))
|
|
2392
|
+
throw new Error("Route list was not an object");
|
|
2393
|
+
const routes = requireList(data.routes, "Routes").map((route) => String(route));
|
|
2394
|
+
el("route-list", HTMLElement).innerHTML = routes.map((route) => {
|
|
2395
|
+
const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
|
|
2396
|
+
const parts = route.split(" ");
|
|
2397
|
+
const method = parts[0] ?? "";
|
|
2398
|
+
const path = parts.slice(1).join(" ");
|
|
2399
|
+
return `<div class="route"><span class="method">${esc(method)}</span><span>${esc(path)}${extra}</span><button class="btn" type="button" data-route="${esc(route)}">Use</button></div>`;
|
|
2400
|
+
}).join("");
|
|
2401
|
+
};
|
|
2402
|
+
const renderers = {
|
|
2403
|
+
overview: renderOverview,
|
|
2404
|
+
state: renderState,
|
|
2405
|
+
clock: renderClock,
|
|
2406
|
+
faults: renderFaults,
|
|
2407
|
+
journal: renderJournal,
|
|
2408
|
+
routes: renderRoutes
|
|
2409
|
+
};
|
|
2410
|
+
async function refresh() {
|
|
2411
|
+
clearError();
|
|
2412
|
+
const render = renderers[state.view];
|
|
2413
|
+
if (render)
|
|
2414
|
+
await render();
|
|
2415
|
+
}
|
|
2416
|
+
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2417
|
+
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2418
|
+
refresh().catch(showError);
|
|
2419
|
+
});
|
|
2420
|
+
keyInput.addEventListener("change", () => {
|
|
2421
|
+
state.key = keyInput.value.trim();
|
|
2422
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
2423
|
+
refresh().catch(showError);
|
|
2424
|
+
});
|
|
2425
|
+
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2426
|
+
refresh().catch(showError);
|
|
2427
|
+
});
|
|
2428
|
+
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2429
|
+
const target = event.target;
|
|
2430
|
+
if (!(target instanceof Element))
|
|
2431
|
+
return;
|
|
2432
|
+
const button = target.closest("[data-collection]");
|
|
2433
|
+
if (!(button instanceof HTMLElement))
|
|
2434
|
+
return;
|
|
2435
|
+
const collection = button.dataset.collection;
|
|
2436
|
+
if (collection === void 0)
|
|
2437
|
+
return;
|
|
2438
|
+
state.collection = collection;
|
|
2439
|
+
renderState().catch(showError);
|
|
2440
|
+
});
|
|
2441
|
+
el("records", HTMLElement).addEventListener("click", (event) => {
|
|
2442
|
+
const target = event.target;
|
|
2443
|
+
if (!(target instanceof Element))
|
|
2444
|
+
return;
|
|
2445
|
+
const row = target.closest("[data-id]");
|
|
2446
|
+
if (!(row instanceof HTMLElement))
|
|
2447
|
+
return;
|
|
2448
|
+
const id = row.dataset.id;
|
|
2449
|
+
if (id === void 0)
|
|
2450
|
+
return;
|
|
2451
|
+
openEditor(id).catch(showError);
|
|
2452
|
+
});
|
|
2453
|
+
const editor = el("editor", HTMLDialogElement);
|
|
2454
|
+
const editorId = el("editor-id", HTMLInputElement);
|
|
2455
|
+
const editorValue = el("editor-value", HTMLTextAreaElement);
|
|
2456
|
+
const editorDelete = el("editor-delete", HTMLButtonElement);
|
|
2457
|
+
async function openEditor(id) {
|
|
2458
|
+
state.editing = id;
|
|
2459
|
+
el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
|
|
2460
|
+
editorId.value = id ?? "";
|
|
2461
|
+
editorId.readOnly = id !== null;
|
|
2462
|
+
editorDelete.hidden = id === null;
|
|
2463
|
+
if (id) {
|
|
2464
|
+
const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
|
|
2465
|
+
if (!isRecord2(record))
|
|
2466
|
+
throw new Error("Record response was not an object");
|
|
2467
|
+
editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
|
|
2468
|
+
} else {
|
|
2469
|
+
editorValue.value = "{\n \n}";
|
|
2470
|
+
}
|
|
2471
|
+
editor.showModal();
|
|
2472
|
+
}
|
|
2473
|
+
el("new-record", HTMLButtonElement).addEventListener("click", () => {
|
|
2474
|
+
openEditor(null).catch(showError);
|
|
2475
|
+
});
|
|
2476
|
+
el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2477
|
+
const submitter = event.submitter;
|
|
2478
|
+
if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
|
|
2479
|
+
return;
|
|
2480
|
+
event.preventDefault();
|
|
2481
|
+
let value;
|
|
2482
|
+
try {
|
|
2483
|
+
value = readJson2(editorValue.value);
|
|
2484
|
+
} catch {
|
|
2485
|
+
showError(new Error("Value is not JSON"));
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
const id = editorId.value.trim();
|
|
2489
|
+
const path = `/state/${encodeURIComponent(state.collection)}`;
|
|
2490
|
+
const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
|
|
2491
|
+
request.then(() => {
|
|
2492
|
+
editor.close();
|
|
2493
|
+
return renderState();
|
|
2494
|
+
}).catch(showError);
|
|
2495
|
+
});
|
|
2496
|
+
editorDelete.addEventListener("click", () => {
|
|
2497
|
+
const id = state.editing;
|
|
2498
|
+
if (!id)
|
|
2499
|
+
return;
|
|
2500
|
+
api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
|
|
2501
|
+
editor.close();
|
|
2502
|
+
return renderState();
|
|
2503
|
+
}).catch(showError);
|
|
2504
|
+
});
|
|
2505
|
+
for (const button of document.querySelectorAll("[data-advance]")) {
|
|
2506
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2507
|
+
continue;
|
|
2508
|
+
button.addEventListener("click", () => {
|
|
2509
|
+
const raw = button.dataset.advance;
|
|
2510
|
+
if (raw === void 0)
|
|
2511
|
+
return;
|
|
2512
|
+
api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
el("freeze", HTMLButtonElement).addEventListener("click", () => {
|
|
2516
|
+
const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
|
|
2517
|
+
api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
|
|
2518
|
+
});
|
|
2519
|
+
el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
|
|
2520
|
+
api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
|
|
2521
|
+
});
|
|
2522
|
+
el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2523
|
+
event.preventDefault();
|
|
2524
|
+
const raw = el("clock-instant", HTMLInputElement).value.trim();
|
|
2525
|
+
const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
|
|
2526
|
+
api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
|
|
2527
|
+
});
|
|
2528
|
+
el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2529
|
+
event.preventDefault();
|
|
2530
|
+
const preset = el("fault-preset", HTMLSelectElement).value;
|
|
2531
|
+
if (preset !== "") {
|
|
2532
|
+
api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2535
|
+
const status = Number(el("fault-status", HTMLInputElement).value);
|
|
2536
|
+
if (!Number.isFinite(status)) {
|
|
2537
|
+
showError(new Error("Status must be a number, or choose a preset"));
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
const path = el("fault-path", HTMLInputElement).value;
|
|
2541
|
+
const method = el("fault-method", HTMLInputElement).value;
|
|
2542
|
+
api("POST", "/faults", {
|
|
2543
|
+
status,
|
|
2544
|
+
...path !== "" ? { pathPrefix: path } : {},
|
|
2545
|
+
...method !== "" ? { method } : {}
|
|
2546
|
+
}).then(() => renderFaults()).catch(showError);
|
|
2547
|
+
});
|
|
2548
|
+
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2549
|
+
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2550
|
+
});
|
|
2551
|
+
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2552
|
+
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2553
|
+
});
|
|
2554
|
+
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2555
|
+
const target = event.target;
|
|
2556
|
+
if (!(target instanceof Element))
|
|
2557
|
+
return;
|
|
2558
|
+
const button = target.closest("[data-route]");
|
|
2559
|
+
if (!(button instanceof HTMLElement))
|
|
2560
|
+
return;
|
|
2561
|
+
const route = button.dataset.route;
|
|
2562
|
+
if (route === void 0)
|
|
2563
|
+
return;
|
|
2564
|
+
state.route = route;
|
|
2565
|
+
el("route-title", HTMLElement).textContent = route;
|
|
2566
|
+
const method = route.split(" ")[0] ?? "";
|
|
2567
|
+
el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
|
|
2568
|
+
});
|
|
2569
|
+
el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2570
|
+
event.preventDefault();
|
|
2571
|
+
const send = async () => {
|
|
2572
|
+
if (state.route === null)
|
|
2573
|
+
throw new Error("Choose a route first");
|
|
2574
|
+
const parts = state.route.split(" ");
|
|
2575
|
+
const method = parts[0];
|
|
2576
|
+
const path = parts.slice(1).join(" ");
|
|
2577
|
+
if (method === void 0)
|
|
2578
|
+
throw new Error("Choose a route first");
|
|
2579
|
+
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
|
|
2580
|
+
let body;
|
|
2581
|
+
const routeBody = el("route-body", HTMLTextAreaElement);
|
|
2582
|
+
if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
|
|
2583
|
+
try {
|
|
2584
|
+
body = readJson2(routeBody.value);
|
|
2585
|
+
} catch {
|
|
2586
|
+
throw new Error("Body is not JSON");
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
const data = await api(method, concrete, body);
|
|
2590
|
+
el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
|
|
2591
|
+
};
|
|
2592
|
+
send().catch(showError);
|
|
2593
|
+
});
|
|
2594
|
+
const decodePanel = (value) => {
|
|
2595
|
+
if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
|
|
2596
|
+
return void 0;
|
|
2597
|
+
}
|
|
2598
|
+
const panel = {
|
|
2599
|
+
id: value.id,
|
|
2600
|
+
title: value.title,
|
|
2601
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
2602
|
+
html: typeof value.html === "string" ? value.html : ""
|
|
2603
|
+
};
|
|
2604
|
+
if (typeof value.script === "string")
|
|
2605
|
+
panel.script = value.script;
|
|
2606
|
+
return panel;
|
|
2607
|
+
};
|
|
2608
|
+
const mountPanels = (panels) => {
|
|
2609
|
+
const nav = el("nav", HTMLElement);
|
|
2610
|
+
const views = el("views", HTMLElement);
|
|
2611
|
+
for (const panel of panels) {
|
|
2612
|
+
const button = document.createElement("button");
|
|
2613
|
+
button.type = "button";
|
|
2614
|
+
button.dataset.view = panel.id;
|
|
2615
|
+
button.textContent = panel.title;
|
|
2616
|
+
nav.append(button);
|
|
2617
|
+
const section = document.createElement("section");
|
|
2618
|
+
section.className = "view";
|
|
2619
|
+
section.id = `view-${panel.id}`;
|
|
2620
|
+
section.hidden = true;
|
|
2621
|
+
const title = document.createElement("h2");
|
|
2622
|
+
title.textContent = panel.title;
|
|
2623
|
+
const lede = document.createElement("p");
|
|
2624
|
+
lede.className = "lede";
|
|
2625
|
+
lede.textContent = panel.description;
|
|
2626
|
+
const body = document.createElement("div");
|
|
2627
|
+
body.className = "panel-body";
|
|
2628
|
+
body.id = `panel-${panel.id}`;
|
|
2629
|
+
body.innerHTML = panel.html;
|
|
2630
|
+
section.append(title, lede, body);
|
|
2631
|
+
views.append(section);
|
|
2632
|
+
if (panel.script === void 0)
|
|
2633
|
+
continue;
|
|
2634
|
+
try {
|
|
2635
|
+
const compiled = new Function("root", "api", panel.script);
|
|
2636
|
+
if (!isPanelScript(compiled))
|
|
2637
|
+
throw new Error("panel script did not compile");
|
|
2638
|
+
compiled(body, panelApi);
|
|
2639
|
+
} catch (error) {
|
|
2640
|
+
const note = document.createElement("p");
|
|
2641
|
+
note.className = "banner show";
|
|
2642
|
+
note.textContent = error instanceof Error ? error.message : String(error);
|
|
2643
|
+
body.append(note);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
startAdminBrand({
|
|
2648
|
+
document,
|
|
2649
|
+
$: (id) => document.getElementById(id),
|
|
2650
|
+
params,
|
|
2651
|
+
location: { href: location.href, origin: location.origin },
|
|
2652
|
+
fetch,
|
|
2653
|
+
brandsUrl: config.brandsUrl
|
|
2654
|
+
});
|
|
2655
|
+
loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
|
|
2656
|
+
const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
|
|
2657
|
+
const decoded = decodePanel(panel);
|
|
2658
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2659
|
+
});
|
|
2660
|
+
mountPanels(panels);
|
|
2661
|
+
return refresh();
|
|
2662
|
+
}).catch(showError);
|
|
2663
|
+
}
|
|
2664
|
+
var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
|
|
2665
|
+
var adminClientSource = (config) => {
|
|
2666
|
+
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2667
|
+
return `<script>
|
|
2668
|
+
${clientPrelude}
|
|
2669
|
+
bootAdmin(${payload});
|
|
2670
|
+
</script>`;
|
|
2671
|
+
};
|
|
2672
|
+
|
|
1447
2673
|
// ../core/dist/surface.js
|
|
1448
2674
|
var defineMock = (runtime) => runtime;
|
|
1449
2675
|
var STANDARD_ADMIN_ROUTES = [
|
|
@@ -1512,94 +2738,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
|
|
|
1512
2738
|
return "'";
|
|
1513
2739
|
}
|
|
1514
2740
|
});
|
|
2741
|
+
var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
|
|
1515
2742
|
var renderAdminDocument = (service) => {
|
|
1516
2743
|
const name = escapeHtml(service);
|
|
1517
|
-
const
|
|
2744
|
+
const script = adminClientSource({
|
|
2745
|
+
standardRoutes: STANDARD_ADMIN_ROUTES,
|
|
2746
|
+
adminPrefix: ADMIN_PREFIX,
|
|
2747
|
+
adminKeyHeader: ADMIN_KEY_HEADER,
|
|
2748
|
+
brandsUrl: ADMIN_BRANDS_URL,
|
|
2749
|
+
service
|
|
2750
|
+
});
|
|
1518
2751
|
return `<!DOCTYPE html>
|
|
1519
2752
|
<html lang="en">
|
|
1520
2753
|
<head>
|
|
1521
2754
|
<meta charset="utf-8">
|
|
1522
2755
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1523
2756
|
<meta name="color-scheme" content="light dark">
|
|
1524
|
-
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='
|
|
2757
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='2' fill='%2327272a'/%3E%3C/svg%3E">
|
|
1525
2758
|
<title>${name} admin</title>
|
|
1526
2759
|
<style>
|
|
2760
|
+
${CSS_RESET}
|
|
1527
2761
|
:root {
|
|
1528
2762
|
color-scheme: light;
|
|
1529
|
-
--bg: #
|
|
2763
|
+
--bg: #fafafa;
|
|
1530
2764
|
--bg-elev: #ffffff;
|
|
1531
|
-
--bg-muted: #
|
|
1532
|
-
--bg-inset: #
|
|
1533
|
-
--line: #
|
|
1534
|
-
--line-strong: #
|
|
1535
|
-
--ink: #
|
|
1536
|
-
--muted: #
|
|
1537
|
-
--faint: #
|
|
1538
|
-
--accent: #
|
|
1539
|
-
--accent-ink: #
|
|
1540
|
-
--accent-soft: #
|
|
1541
|
-
--ok: #
|
|
1542
|
-
--ok-soft: #
|
|
1543
|
-
--warn: #
|
|
1544
|
-
--warn-soft: #
|
|
1545
|
-
--err: #
|
|
1546
|
-
--err-soft: #
|
|
1547
|
-
--shadow:
|
|
1548
|
-
--sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2765
|
+
--bg-muted: #f4f4f5;
|
|
2766
|
+
--bg-inset: #f4f4f5;
|
|
2767
|
+
--line: #dedee3;
|
|
2768
|
+
--line-strong: #c4c4cc;
|
|
2769
|
+
--ink: #18181b;
|
|
2770
|
+
--muted: #62626b;
|
|
2771
|
+
--faint: #71717a;
|
|
2772
|
+
--accent: #27272a;
|
|
2773
|
+
--accent-ink: #ffffff;
|
|
2774
|
+
--accent-soft: #f4f4f5;
|
|
2775
|
+
--ok: #166534;
|
|
2776
|
+
--ok-soft: #f0fdf4;
|
|
2777
|
+
--warn: #a16207;
|
|
2778
|
+
--warn-soft: #fefce8;
|
|
2779
|
+
--err: #a82d32;
|
|
2780
|
+
--err-soft: #fef2f2;
|
|
2781
|
+
--shadow: none;
|
|
2782
|
+
--sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1549
2783
|
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1550
|
-
--radius:
|
|
1551
|
-
--header:
|
|
1552
|
-
--nav:
|
|
2784
|
+
--radius: 10px;
|
|
2785
|
+
--header: 56px;
|
|
2786
|
+
--nav: 200px;
|
|
1553
2787
|
}
|
|
1554
2788
|
:root[data-theme="dark"] {
|
|
1555
2789
|
color-scheme: dark;
|
|
1556
|
-
--bg: #
|
|
1557
|
-
--bg-elev: #
|
|
1558
|
-
--bg-muted: #
|
|
1559
|
-
--bg-inset: #
|
|
1560
|
-
--line: #
|
|
1561
|
-
--line-strong: #
|
|
1562
|
-
--ink: #
|
|
1563
|
-
--muted: #
|
|
1564
|
-
--faint: #
|
|
1565
|
-
--accent: #
|
|
1566
|
-
--accent-ink: #
|
|
1567
|
-
--accent-soft: #
|
|
1568
|
-
--ok: #
|
|
1569
|
-
--ok-soft: #
|
|
1570
|
-
--warn: #
|
|
1571
|
-
--warn-soft: #
|
|
1572
|
-
--err: #
|
|
1573
|
-
--err-soft: #
|
|
1574
|
-
--shadow:
|
|
2790
|
+
--bg: #111113;
|
|
2791
|
+
--bg-elev: #19191c;
|
|
2792
|
+
--bg-muted: #242428;
|
|
2793
|
+
--bg-inset: #111113;
|
|
2794
|
+
--line: #36363c;
|
|
2795
|
+
--line-strong: #52525b;
|
|
2796
|
+
--ink: #f4f4f5;
|
|
2797
|
+
--muted: #a9a9b2;
|
|
2798
|
+
--faint: #71717a;
|
|
2799
|
+
--accent: #e4e4e7;
|
|
2800
|
+
--accent-ink: #18181b;
|
|
2801
|
+
--accent-soft: #242428;
|
|
2802
|
+
--ok: #86efac;
|
|
2803
|
+
--ok-soft: #14241b;
|
|
2804
|
+
--warn: #fde68a;
|
|
2805
|
+
--warn-soft: #2a2410;
|
|
2806
|
+
--err: #ffaaaa;
|
|
2807
|
+
--err-soft: #2c1516;
|
|
2808
|
+
--shadow: none;
|
|
1575
2809
|
}
|
|
1576
2810
|
@media (prefers-color-scheme: dark) {
|
|
1577
2811
|
:root:not([data-theme="light"]) {
|
|
1578
2812
|
color-scheme: dark;
|
|
1579
|
-
--bg: #
|
|
1580
|
-
--bg-elev: #
|
|
1581
|
-
--bg-muted: #
|
|
1582
|
-
--bg-inset: #
|
|
1583
|
-
--line: #
|
|
1584
|
-
--line-strong: #
|
|
1585
|
-
--ink: #
|
|
1586
|
-
--muted: #
|
|
1587
|
-
--faint: #
|
|
1588
|
-
--accent: #
|
|
1589
|
-
--accent-ink: #
|
|
1590
|
-
--accent-soft: #
|
|
1591
|
-
--ok: #
|
|
1592
|
-
--ok-soft: #
|
|
1593
|
-
--warn: #
|
|
1594
|
-
--warn-soft: #
|
|
1595
|
-
--err: #
|
|
1596
|
-
--err-soft: #
|
|
1597
|
-
--shadow:
|
|
2813
|
+
--bg: #111113;
|
|
2814
|
+
--bg-elev: #19191c;
|
|
2815
|
+
--bg-muted: #242428;
|
|
2816
|
+
--bg-inset: #111113;
|
|
2817
|
+
--line: #36363c;
|
|
2818
|
+
--line-strong: #52525b;
|
|
2819
|
+
--ink: #f4f4f5;
|
|
2820
|
+
--muted: #a9a9b2;
|
|
2821
|
+
--faint: #71717a;
|
|
2822
|
+
--accent: #e4e4e7;
|
|
2823
|
+
--accent-ink: #18181b;
|
|
2824
|
+
--accent-soft: #242428;
|
|
2825
|
+
--ok: #86efac;
|
|
2826
|
+
--ok-soft: #14241b;
|
|
2827
|
+
--warn: #fde68a;
|
|
2828
|
+
--warn-soft: #2a2410;
|
|
2829
|
+
--err: #ffaaaa;
|
|
2830
|
+
--err-soft: #2c1516;
|
|
2831
|
+
--shadow: none;
|
|
1598
2832
|
}
|
|
1599
2833
|
}
|
|
1600
|
-
|
|
1601
|
-
html
|
|
2834
|
+
/* Fill the viewport, then grow with the content so the background covers the scroll. */
|
|
2835
|
+
html { height: 100%; }
|
|
1602
2836
|
body {
|
|
2837
|
+
margin: 0;
|
|
2838
|
+
min-height: 100%;
|
|
2839
|
+
display: flex;
|
|
2840
|
+
flex-direction: column;
|
|
1603
2841
|
font-family: var(--sans);
|
|
1604
2842
|
background: var(--bg);
|
|
1605
2843
|
color: var(--ink);
|
|
@@ -1608,24 +2846,37 @@ var renderAdminDocument = (service) => {
|
|
|
1608
2846
|
padding-right: env(safe-area-inset-right);
|
|
1609
2847
|
-webkit-text-size-adjust: 100%;
|
|
1610
2848
|
}
|
|
1611
|
-
button, input, select, textarea { font: inherit; color: inherit; }
|
|
1612
|
-
button { cursor: pointer; }
|
|
1613
2849
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1614
|
-
|
|
1615
|
-
* { scroll-behavior: auto !important; transition: none !important; }
|
|
1616
|
-
}
|
|
1617
|
-
.app { min-height: 100%; display: flex; flex-direction: column; }
|
|
2850
|
+
.app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
|
|
1618
2851
|
header.top {
|
|
1619
2852
|
position: sticky; top: 0; z-index: 5;
|
|
1620
2853
|
display: flex; align-items: center; gap: 12px;
|
|
1621
2854
|
min-height: var(--header); padding: 10px 16px;
|
|
1622
|
-
background:
|
|
2855
|
+
background: var(--bg-elev);
|
|
1623
2856
|
border-bottom: 1px solid var(--line);
|
|
1624
|
-
|
|
1625
|
-
}
|
|
1626
|
-
.brand {
|
|
1627
|
-
|
|
1628
|
-
|
|
2857
|
+
min-width: 0; max-width: 100%;
|
|
2858
|
+
}
|
|
2859
|
+
.brand {
|
|
2860
|
+
display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
|
|
2861
|
+
min-width: 0; max-width: 100%; margin-right: auto;
|
|
2862
|
+
}
|
|
2863
|
+
.brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
|
|
2864
|
+
.brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
|
|
2865
|
+
.brand-id span { color: var(--faint); font-size: 12px; }
|
|
2866
|
+
.vendor {
|
|
2867
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
2868
|
+
min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
|
|
2869
|
+
}
|
|
2870
|
+
.vendor[hidden] { display: none; }
|
|
2871
|
+
/* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
|
|
2872
|
+
.vendor img {
|
|
2873
|
+
width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
|
|
2874
|
+
border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
|
|
2875
|
+
}
|
|
2876
|
+
.vendor-name { color: var(--ink); font-weight: 600; }
|
|
2877
|
+
.vendor-sep { color: var(--faint); }
|
|
2878
|
+
.vendor-link { color: inherit; text-decoration: none; }
|
|
2879
|
+
.vendor-link:hover { color: var(--ink); text-decoration: underline; }
|
|
1629
2880
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
1630
2881
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
1631
2882
|
label.field input, label.field select, .controls input[type="password"] {
|
|
@@ -1643,7 +2894,7 @@ var renderAdminDocument = (service) => {
|
|
|
1643
2894
|
record or the full nav row would widen the page past the viewport. */
|
|
1644
2895
|
.layout {
|
|
1645
2896
|
display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
|
|
1646
|
-
flex: 1
|
|
2897
|
+
flex: 1 0 auto; min-width: 0;
|
|
1647
2898
|
}
|
|
1648
2899
|
nav.side {
|
|
1649
2900
|
border-right: 1px solid var(--line); padding: 12px;
|
|
@@ -1697,8 +2948,8 @@ var renderAdminDocument = (service) => {
|
|
|
1697
2948
|
.banner.show { display: block; }
|
|
1698
2949
|
.empty { color: var(--muted); padding: 24px 8px; }
|
|
1699
2950
|
dialog {
|
|
1700
|
-
border: 1px solid var(--line); border-radius:
|
|
1701
|
-
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow:
|
|
2951
|
+
border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
|
|
2952
|
+
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
|
|
1702
2953
|
}
|
|
1703
2954
|
dialog::backdrop { background: rgb(20 19 17 / 0.45); }
|
|
1704
2955
|
dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -1725,7 +2976,7 @@ var renderAdminDocument = (service) => {
|
|
|
1725
2976
|
padding-top: max(10px, env(safe-area-inset-top));
|
|
1726
2977
|
}
|
|
1727
2978
|
input, select, textarea { font-size: 16px; }
|
|
1728
|
-
.controls { width: 100%; }
|
|
2979
|
+
.brand, .vendor, .controls { width: 100%; }
|
|
1729
2980
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
1730
2981
|
.layout { grid-template-columns: minmax(0, 1fr); }
|
|
1731
2982
|
nav.side {
|
|
@@ -1756,8 +3007,11 @@ var renderAdminDocument = (service) => {
|
|
|
1756
3007
|
<div class="app" data-service="${name}" data-mockingbird-admin>
|
|
1757
3008
|
<header class="top">
|
|
1758
3009
|
<div class="brand">
|
|
1759
|
-
<
|
|
1760
|
-
|
|
3010
|
+
<div class="brand-id">
|
|
3011
|
+
<strong>${name}</strong>
|
|
3012
|
+
<span>Admin</span>
|
|
3013
|
+
</div>
|
|
3014
|
+
<div class="vendor" id="vendor" hidden></div>
|
|
1761
3015
|
</div>
|
|
1762
3016
|
<div class="controls">
|
|
1763
3017
|
<label class="field">Namespace
|
|
@@ -1887,460 +3141,7 @@ var renderAdminDocument = (service) => {
|
|
|
1887
3141
|
</div>
|
|
1888
3142
|
</form>
|
|
1889
3143
|
</dialog>
|
|
1890
|
-
|
|
1891
|
-
(() => {
|
|
1892
|
-
const STANDARD = new Set(${standard})
|
|
1893
|
-
const state = {
|
|
1894
|
-
namespace: "default",
|
|
1895
|
-
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1896
|
-
view: "overview",
|
|
1897
|
-
collection: "",
|
|
1898
|
-
shape: null,
|
|
1899
|
-
route: null,
|
|
1900
|
-
editing: null
|
|
1901
|
-
}
|
|
1902
|
-
const $ = (id) => document.getElementById(id)
|
|
1903
|
-
const banner = $("banner")
|
|
1904
|
-
const keyInput = $("admin-key")
|
|
1905
|
-
keyInput.value = state.key
|
|
1906
|
-
const params = new URLSearchParams(location.search)
|
|
1907
|
-
if (params.get("key")) {
|
|
1908
|
-
state.key = params.get("key")
|
|
1909
|
-
keyInput.value = state.key
|
|
1910
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1911
|
-
params.delete("key")
|
|
1912
|
-
const next = params.toString()
|
|
1913
|
-
history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
|
|
1914
|
-
}
|
|
1915
|
-
if (params.get("namespace")) state.namespace = params.get("namespace")
|
|
1916
|
-
|
|
1917
|
-
const showError = (error) => {
|
|
1918
|
-
banner.textContent = error instanceof Error ? error.message : String(error)
|
|
1919
|
-
banner.classList.add("show")
|
|
1920
|
-
}
|
|
1921
|
-
const clearError = () => banner.classList.remove("show")
|
|
1922
|
-
|
|
1923
|
-
async function originGet(path) {
|
|
1924
|
-
const response = await fetch(path, { headers: { accept: "application/json" } })
|
|
1925
|
-
if (!response.ok) throw new Error(response.status + " " + path)
|
|
1926
|
-
return response.json()
|
|
1927
|
-
}
|
|
1928
|
-
async function api(method, path, body) {
|
|
1929
|
-
const headers = { accept: "application/json" }
|
|
1930
|
-
if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
|
|
1931
|
-
if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
|
|
1932
|
-
if (body !== undefined) headers["content-type"] = "application/json"
|
|
1933
|
-
const response = await fetch("${ADMIN_PREFIX}" + path, {
|
|
1934
|
-
method,
|
|
1935
|
-
headers,
|
|
1936
|
-
body: body === undefined ? undefined : JSON.stringify(body)
|
|
1937
|
-
})
|
|
1938
|
-
const text = await response.text()
|
|
1939
|
-
let data = null
|
|
1940
|
-
if (text) {
|
|
1941
|
-
try { data = JSON.parse(text) } catch { data = { raw: text } }
|
|
1942
|
-
}
|
|
1943
|
-
if (response.status === 401) {
|
|
1944
|
-
keyInput.focus()
|
|
1945
|
-
throw new Error((data && data.error && data.error.message) || "Admin key required")
|
|
1946
|
-
}
|
|
1947
|
-
if (!response.ok) {
|
|
1948
|
-
throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
|
|
1949
|
-
}
|
|
1950
|
-
return data
|
|
1951
|
-
}
|
|
1952
|
-
const panelApi = {
|
|
1953
|
-
get: (path) => api("GET", path),
|
|
1954
|
-
send: (method, path, body) => api(method, path, body),
|
|
1955
|
-
namespace: () => state.namespace
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
function setTheme(theme) {
|
|
1959
|
-
if (theme) document.documentElement.dataset.theme = theme
|
|
1960
|
-
else delete document.documentElement.dataset.theme
|
|
1961
|
-
}
|
|
1962
|
-
const storedTheme = localStorage.getItem("mockingbird-admin-theme")
|
|
1963
|
-
if (storedTheme) setTheme(storedTheme)
|
|
1964
|
-
$("theme").addEventListener("click", () => {
|
|
1965
|
-
const current = document.documentElement.dataset.theme
|
|
1966
|
-
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
|
|
1967
|
-
const next = dark ? "light" : "dark"
|
|
1968
|
-
localStorage.setItem("mockingbird-admin-theme", next)
|
|
1969
|
-
setTheme(next)
|
|
1970
|
-
})
|
|
1971
|
-
|
|
1972
|
-
function show(view) {
|
|
1973
|
-
state.view = view
|
|
1974
|
-
for (const button of document.querySelectorAll("#nav button")) {
|
|
1975
|
-
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
|
|
1976
|
-
}
|
|
1977
|
-
for (const section of document.querySelectorAll("main .view")) {
|
|
1978
|
-
section.hidden = section.id !== "view-" + view
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
document.getElementById("nav").addEventListener("click", (event) => {
|
|
1982
|
-
const button = event.target.closest("button[data-view]")
|
|
1983
|
-
if (!button) return
|
|
1984
|
-
show(button.dataset.view)
|
|
1985
|
-
refresh().catch(showError)
|
|
1986
|
-
})
|
|
1987
|
-
|
|
1988
|
-
function cards(items) {
|
|
1989
|
-
return '<div class="grid">' + items.map((item) =>
|
|
1990
|
-
'<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
|
|
1991
|
-
).join("") + "</div>"
|
|
1992
|
-
}
|
|
1993
|
-
function table(columns, rows) {
|
|
1994
|
-
if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
|
|
1995
|
-
const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
|
|
1996
|
-
const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
|
|
1997
|
-
'<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
|
|
1998
|
-
).join("") + "</tr>").join("")
|
|
1999
|
-
return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
|
|
2000
|
-
}
|
|
2001
|
-
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
2002
|
-
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
2003
|
-
}[char]))
|
|
2004
|
-
const preview = (value) => {
|
|
2005
|
-
const text = JSON.stringify(value)
|
|
2006
|
-
return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
|
|
2007
|
-
}
|
|
2008
|
-
const when = (epoch) => {
|
|
2009
|
-
const date = new Date(epoch)
|
|
2010
|
-
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
|
|
2011
|
-
}
|
|
2012
|
-
const clockCard = (epoch) => {
|
|
2013
|
-
const date = new Date(epoch)
|
|
2014
|
-
if (Number.isNaN(date.getTime())) return esc(String(epoch))
|
|
2015
|
-
const iso = date.toISOString()
|
|
2016
|
-
return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
async function loadNamespaces() {
|
|
2020
|
-
const data = await api("GET", "/namespaces")
|
|
2021
|
-
const select = $("namespace")
|
|
2022
|
-
const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
|
|
2023
|
-
if (!names.includes(state.namespace)) names.unshift(state.namespace)
|
|
2024
|
-
select.innerHTML = names.map((name) =>
|
|
2025
|
-
'<option value="' + esc(name) + '">' + esc(name) + "</option>"
|
|
2026
|
-
).join("")
|
|
2027
|
-
select.value = state.namespace
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
async function renderOverview() {
|
|
2031
|
-
const [health, shape, clock, journal] = await Promise.all([
|
|
2032
|
-
originGet("/health"),
|
|
2033
|
-
api("GET", "/state"),
|
|
2034
|
-
api("GET", "/clock"),
|
|
2035
|
-
api("GET", "/requests?limit=8")
|
|
2036
|
-
])
|
|
2037
|
-
state.shape = shape
|
|
2038
|
-
$("overview-cards").innerHTML = cards([
|
|
2039
|
-
{ k: "Service", v: esc(health.service || "${name}") },
|
|
2040
|
-
{ k: "Status", v: esc(health.status || "ok") },
|
|
2041
|
-
{ k: "Namespace", v: esc(state.namespace) },
|
|
2042
|
-
{ k: "Collections", v: String(shape.collections.length) },
|
|
2043
|
-
{ k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
|
|
2044
|
-
{ k: "Clock", v: clockCard(clock.now) }
|
|
2045
|
-
])
|
|
2046
|
-
const requests = (journal && journal.requests) || []
|
|
2047
|
-
$("overview-requests").innerHTML = table(
|
|
2048
|
-
[
|
|
2049
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2050
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2051
|
-
{ label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
|
|
2052
|
-
{ label: "Operation", cell: (row) => esc(row.operationId || "") }
|
|
2053
|
-
],
|
|
2054
|
-
requests
|
|
2055
|
-
)
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
function schemaChips(collection) {
|
|
2059
|
-
if (!collection || collection.fields.length === 0) {
|
|
2060
|
-
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
|
|
2061
|
-
}
|
|
2062
|
-
return '<div class="chips">' + collection.fields.map((field) =>
|
|
2063
|
-
'<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
|
|
2064
|
-
).join("") + "</div>"
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
async function renderState() {
|
|
2068
|
-
const shape = await api("GET", "/state")
|
|
2069
|
-
state.shape = shape
|
|
2070
|
-
const host = $("collections")
|
|
2071
|
-
if (shape.collections.length === 0) {
|
|
2072
|
-
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
|
|
2073
|
-
$("schema").innerHTML = ""
|
|
2074
|
-
$("records").innerHTML = ""
|
|
2075
|
-
return
|
|
2076
|
-
}
|
|
2077
|
-
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2078
|
-
state.collection = shape.collections[0].name
|
|
2079
|
-
}
|
|
2080
|
-
host.innerHTML = shape.collections.map((item) =>
|
|
2081
|
-
'<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
|
|
2082
|
-
(item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
|
|
2083
|
-
'</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
|
|
2084
|
-
).join("")
|
|
2085
|
-
const selected = shape.collections.find((item) => item.name === state.collection)
|
|
2086
|
-
$("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
|
|
2087
|
-
(selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
|
|
2088
|
-
schemaChips(selected)
|
|
2089
|
-
const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
|
|
2090
|
-
const rows = page.records.map((record) => ({
|
|
2091
|
-
...record,
|
|
2092
|
-
attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
|
|
2093
|
-
}))
|
|
2094
|
-
$("records").innerHTML = table(
|
|
2095
|
-
[
|
|
2096
|
-
{ label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
|
|
2097
|
-
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2098
|
-
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2099
|
-
],
|
|
2100
|
-
rows
|
|
2101
|
-
)
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
async function renderClock() {
|
|
2105
|
-
const clock = await api("GET", "/clock")
|
|
2106
|
-
$("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
|
|
2107
|
-
"</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
|
|
2108
|
-
(clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
|
|
2109
|
-
$("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
|
|
2110
|
-
$("freeze").dataset.frozen = clock.frozen ? "1" : "0"
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
async function renderFaults() {
|
|
2114
|
-
const [faults, presets] = await Promise.all([
|
|
2115
|
-
api("GET", "/faults"),
|
|
2116
|
-
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2117
|
-
])
|
|
2118
|
-
const select = $("fault-preset")
|
|
2119
|
-
const current = select.value
|
|
2120
|
-
const list = presets.presets || []
|
|
2121
|
-
select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
|
|
2122
|
-
'<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
|
|
2123
|
-
).join("")
|
|
2124
|
-
select.value = current
|
|
2125
|
-
const rules = faults.faults || []
|
|
2126
|
-
$("fault-list").innerHTML = table(
|
|
2127
|
-
[
|
|
2128
|
-
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2129
|
-
{ label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
|
|
2130
|
-
{ label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
|
|
2131
|
-
],
|
|
2132
|
-
rules
|
|
2133
|
-
)
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
async function renderJournal() {
|
|
2137
|
-
const journal = await api("GET", "/requests?limit=100")
|
|
2138
|
-
$("journal").innerHTML = table(
|
|
2139
|
-
[
|
|
2140
|
-
{ label: "When", cell: (row) => esc(row.at || "") },
|
|
2141
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2142
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2143
|
-
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2144
|
-
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2145
|
-
],
|
|
2146
|
-
journal.requests || []
|
|
2147
|
-
)
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
async function renderRoutes() {
|
|
2151
|
-
const data = await api("GET", "/")
|
|
2152
|
-
const routes = data.routes || []
|
|
2153
|
-
$("route-list").innerHTML = routes.map((route) => {
|
|
2154
|
-
const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
|
|
2155
|
-
return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
|
|
2156
|
-
'</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
|
|
2157
|
-
'</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
|
|
2158
|
-
}).join("")
|
|
2159
|
-
}
|
|
2160
|
-
|
|
2161
|
-
const renderers = {
|
|
2162
|
-
overview: renderOverview,
|
|
2163
|
-
state: renderState,
|
|
2164
|
-
clock: renderClock,
|
|
2165
|
-
faults: renderFaults,
|
|
2166
|
-
journal: renderJournal,
|
|
2167
|
-
routes: renderRoutes
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
async function refresh() {
|
|
2171
|
-
clearError()
|
|
2172
|
-
if (state.view in renderers) await renderers[state.view]()
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
$("namespace").addEventListener("change", () => {
|
|
2176
|
-
state.namespace = $("namespace").value
|
|
2177
|
-
refresh().catch(showError)
|
|
2178
|
-
})
|
|
2179
|
-
keyInput.addEventListener("change", () => {
|
|
2180
|
-
state.key = keyInput.value.trim()
|
|
2181
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
2182
|
-
refresh().catch(showError)
|
|
2183
|
-
})
|
|
2184
|
-
$("refresh").addEventListener("click", () => refresh().catch(showError))
|
|
2185
|
-
|
|
2186
|
-
$("collections").addEventListener("click", (event) => {
|
|
2187
|
-
const button = event.target.closest("[data-collection]")
|
|
2188
|
-
if (!button) return
|
|
2189
|
-
state.collection = button.dataset.collection
|
|
2190
|
-
renderState().catch(showError)
|
|
2191
|
-
})
|
|
2192
|
-
$("records").addEventListener("click", (event) => {
|
|
2193
|
-
const row = event.target.closest("[data-id]")
|
|
2194
|
-
if (!row) return
|
|
2195
|
-
openEditor(row.dataset.id).catch(showError)
|
|
2196
|
-
})
|
|
2197
|
-
|
|
2198
|
-
const editor = $("editor")
|
|
2199
|
-
async function openEditor(id) {
|
|
2200
|
-
state.editing = id || null
|
|
2201
|
-
$("editor-title").textContent = id ? "Edit record" : "New record"
|
|
2202
|
-
$("editor-id").value = id || ""
|
|
2203
|
-
$("editor-id").readOnly = Boolean(id)
|
|
2204
|
-
$("editor-delete").hidden = !id
|
|
2205
|
-
if (id) {
|
|
2206
|
-
const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
2207
|
-
$("editor-value").value = JSON.stringify(record.value, null, 2)
|
|
2208
|
-
} else {
|
|
2209
|
-
$("editor-value").value = "{\\n \\n}"
|
|
2210
|
-
}
|
|
2211
|
-
editor.showModal()
|
|
2212
|
-
}
|
|
2213
|
-
$("new-record").addEventListener("click", () => openEditor(null).catch(showError))
|
|
2214
|
-
$("editor-form").addEventListener("submit", (event) => {
|
|
2215
|
-
const submitter = event.submitter
|
|
2216
|
-
if (!submitter || submitter.value !== "save") return
|
|
2217
|
-
event.preventDefault()
|
|
2218
|
-
let value
|
|
2219
|
-
try { value = JSON.parse($("editor-value").value) }
|
|
2220
|
-
catch { showError(new Error("Value is not JSON")); return }
|
|
2221
|
-
const id = $("editor-id").value.trim()
|
|
2222
|
-
const path = "/state/" + encodeURIComponent(state.collection)
|
|
2223
|
-
const request = state.editing
|
|
2224
|
-
? api("PUT", path + "/" + encodeURIComponent(id), { value })
|
|
2225
|
-
: api("POST", path, { id: id || undefined, value })
|
|
2226
|
-
request.then(() => { editor.close(); return renderState() }).catch(showError)
|
|
2227
|
-
})
|
|
2228
|
-
$("editor-delete").addEventListener("click", () => {
|
|
2229
|
-
const id = state.editing
|
|
2230
|
-
if (!id) return
|
|
2231
|
-
api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
2232
|
-
.then(() => { editor.close(); return renderState() })
|
|
2233
|
-
.catch(showError)
|
|
2234
|
-
})
|
|
2235
|
-
|
|
2236
|
-
document.querySelectorAll("[data-advance]").forEach((button) => {
|
|
2237
|
-
button.addEventListener("click", () => {
|
|
2238
|
-
api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
|
|
2239
|
-
})
|
|
2240
|
-
})
|
|
2241
|
-
$("freeze").addEventListener("click", () => {
|
|
2242
|
-
const frozen = $("freeze").dataset.frozen === "1"
|
|
2243
|
-
api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
|
|
2244
|
-
})
|
|
2245
|
-
$("clock-reset").addEventListener("click", () => {
|
|
2246
|
-
api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
|
|
2247
|
-
})
|
|
2248
|
-
$("clock-set").addEventListener("submit", (event) => {
|
|
2249
|
-
event.preventDefault()
|
|
2250
|
-
const raw = $("clock-instant").value.trim()
|
|
2251
|
-
const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
|
|
2252
|
-
api("POST", "/clock", { set }).then(renderClock).catch(showError)
|
|
2253
|
-
})
|
|
2254
|
-
|
|
2255
|
-
$("fault-form").addEventListener("submit", (event) => {
|
|
2256
|
-
event.preventDefault()
|
|
2257
|
-
const preset = $("fault-preset").value
|
|
2258
|
-
const body = preset
|
|
2259
|
-
? { preset }
|
|
2260
|
-
: {
|
|
2261
|
-
status: Number($("fault-status").value),
|
|
2262
|
-
...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
|
|
2263
|
-
...($("fault-method").value ? { method: $("fault-method").value } : {})
|
|
2264
|
-
}
|
|
2265
|
-
if (!preset && !Number.isFinite(body.status)) {
|
|
2266
|
-
showError(new Error("Status must be a number, or choose a preset"))
|
|
2267
|
-
return
|
|
2268
|
-
}
|
|
2269
|
-
api("POST", "/faults", body).then(renderFaults).catch(showError)
|
|
2270
|
-
})
|
|
2271
|
-
$("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
|
|
2272
|
-
$("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
|
|
2273
|
-
|
|
2274
|
-
$("route-list").addEventListener("click", (event) => {
|
|
2275
|
-
const button = event.target.closest("[data-route]")
|
|
2276
|
-
if (!button) return
|
|
2277
|
-
state.route = button.dataset.route
|
|
2278
|
-
$("route-title").textContent = state.route
|
|
2279
|
-
const method = state.route.split(" ")[0]
|
|
2280
|
-
$("route-body").disabled = method === "GET" || method === "DELETE"
|
|
2281
|
-
})
|
|
2282
|
-
$("route-form").addEventListener("submit", async (event) => {
|
|
2283
|
-
event.preventDefault()
|
|
2284
|
-
if (!state.route) return showError(new Error("Choose a route first"))
|
|
2285
|
-
const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
|
|
2286
|
-
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
|
|
2287
|
-
let body
|
|
2288
|
-
if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
|
|
2289
|
-
try { body = JSON.parse($("route-body").value) }
|
|
2290
|
-
catch { return showError(new Error("Body is not JSON")) }
|
|
2291
|
-
}
|
|
2292
|
-
try {
|
|
2293
|
-
const data = await api(method, concrete, body)
|
|
2294
|
-
$("route-result").textContent = JSON.stringify(data, null, 2)
|
|
2295
|
-
} catch (error) {
|
|
2296
|
-
showError(error)
|
|
2297
|
-
}
|
|
2298
|
-
})
|
|
2299
|
-
|
|
2300
|
-
function mountPanels(panels) {
|
|
2301
|
-
const nav = $("nav")
|
|
2302
|
-
const views = $("views")
|
|
2303
|
-
for (const panel of panels) {
|
|
2304
|
-
const button = document.createElement("button")
|
|
2305
|
-
button.type = "button"
|
|
2306
|
-
button.dataset.view = panel.id
|
|
2307
|
-
button.textContent = panel.title
|
|
2308
|
-
nav.append(button)
|
|
2309
|
-
const section = document.createElement("section")
|
|
2310
|
-
section.className = "view"
|
|
2311
|
-
section.id = "view-" + panel.id
|
|
2312
|
-
section.hidden = true
|
|
2313
|
-
const title = document.createElement("h2")
|
|
2314
|
-
title.textContent = panel.title
|
|
2315
|
-
const lede = document.createElement("p")
|
|
2316
|
-
lede.className = "lede"
|
|
2317
|
-
lede.textContent = panel.description || ""
|
|
2318
|
-
const body = document.createElement("div")
|
|
2319
|
-
body.className = "panel-body"
|
|
2320
|
-
body.id = "panel-" + panel.id
|
|
2321
|
-
body.innerHTML = panel.html || ""
|
|
2322
|
-
section.append(title, lede, body)
|
|
2323
|
-
views.append(section)
|
|
2324
|
-
if (panel.script) {
|
|
2325
|
-
try {
|
|
2326
|
-
const run = new Function("root", "api", panel.script)
|
|
2327
|
-
run(body, panelApi)
|
|
2328
|
-
} catch (error) {
|
|
2329
|
-
const note = document.createElement("p")
|
|
2330
|
-
note.className = "banner show"
|
|
2331
|
-
note.textContent = error instanceof Error ? error.message : String(error)
|
|
2332
|
-
body.append(note)
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
loadNamespaces()
|
|
2339
|
-
.then(() => api("GET", "/ui/manifest"))
|
|
2340
|
-
.then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
|
|
2341
|
-
.catch(showError)
|
|
2342
|
-
})()
|
|
2343
|
-
</script>
|
|
3144
|
+
${script}
|
|
2344
3145
|
</body>
|
|
2345
3146
|
</html>`;
|
|
2346
3147
|
};
|
|
@@ -2357,10 +3158,10 @@ var json3 = (body) => new Response(JSON.stringify(body), {
|
|
|
2357
3158
|
});
|
|
2358
3159
|
var adminUiRoutes = (service, ui) => {
|
|
2359
3160
|
const shell = () => renderAdminDocument(service);
|
|
2360
|
-
const
|
|
3161
|
+
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
2361
3162
|
return {
|
|
2362
|
-
"GET /ui": () => htmlResponse(
|
|
2363
|
-
"GET /ui/": () => htmlResponse(
|
|
3163
|
+
"GET /ui": () => htmlResponse(document3()),
|
|
3164
|
+
"GET /ui/": () => htmlResponse(document3()),
|
|
2364
3165
|
"GET /ui/manifest": () => json3({
|
|
2365
3166
|
service,
|
|
2366
3167
|
panels: (ui?.panels ?? []).map((panel) => ({
|
|
@@ -2386,11 +3187,11 @@ var EXTENSION_KEYS = {
|
|
|
2386
3187
|
};
|
|
2387
3188
|
|
|
2388
3189
|
// ../../openapi/metadata/dist/read.js
|
|
2389
|
-
var
|
|
3190
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2390
3191
|
var extensionOf = (holder, key) => holder[key];
|
|
2391
3192
|
var operationMetadata = (operation) => {
|
|
2392
3193
|
const raw = extensionOf(operation, EXTENSION_KEYS.operation);
|
|
2393
|
-
const ext =
|
|
3194
|
+
const ext = isRecord3(raw) ? raw : {};
|
|
2394
3195
|
const supported = ext.supported ?? true;
|
|
2395
3196
|
const parity = ext.parity ?? {};
|
|
2396
3197
|
return {
|
|
@@ -2500,9 +3301,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
2500
3301
|
this.name = "OperationRegistryError";
|
|
2501
3302
|
}
|
|
2502
3303
|
};
|
|
2503
|
-
var verifyOperations = (
|
|
3304
|
+
var verifyOperations = (document3, handlers) => {
|
|
2504
3305
|
const problems = [];
|
|
2505
|
-
const operations = listOperations(
|
|
3306
|
+
const operations = listOperations(document3);
|
|
2506
3307
|
const seen = /* @__PURE__ */ new Set();
|
|
2507
3308
|
for (const operation of operations) {
|
|
2508
3309
|
if (seen.has(operation.operationId))
|
|
@@ -2618,7 +3419,7 @@ var SAMPLE = 25;
|
|
|
2618
3419
|
var WALK_LIMIT = 1e4;
|
|
2619
3420
|
var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
|
|
2620
3421
|
var fail = (status, message) => ({ ok: false, status, message });
|
|
2621
|
-
var
|
|
3422
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2622
3423
|
var kindOf = (value) => {
|
|
2623
3424
|
if (value === null)
|
|
2624
3425
|
return "null";
|
|
@@ -2717,7 +3518,7 @@ var absorb = (accum, value) => {
|
|
|
2717
3518
|
const kind = kindOf(value);
|
|
2718
3519
|
accum.kinds.add(kind);
|
|
2719
3520
|
accum.present++;
|
|
2720
|
-
if (!
|
|
3521
|
+
if (!isRecord4(value))
|
|
2721
3522
|
return;
|
|
2722
3523
|
for (const [key, child] of Object.entries(value)) {
|
|
2723
3524
|
const nested = accum.nested.get(key) ?? blankAccum(key);
|
|
@@ -2742,7 +3543,7 @@ var inferFields = (records) => {
|
|
|
2742
3543
|
if (records.length === 0)
|
|
2743
3544
|
return [];
|
|
2744
3545
|
const sample = records.slice(0, SAMPLE);
|
|
2745
|
-
const objects = sample.filter((record) =>
|
|
3546
|
+
const objects = sample.filter((record) => isRecord4(record.value));
|
|
2746
3547
|
if (objects.length === 0) {
|
|
2747
3548
|
const kinds = new Set(sample.map((record) => kindOf(record.value)));
|
|
2748
3549
|
const list = [...kinds];
|
|
@@ -2929,7 +3730,7 @@ var patchStateRecord = (scope, name, id, value) => {
|
|
|
2929
3730
|
const parsed = jsonValue(value);
|
|
2930
3731
|
if (!parsed.ok)
|
|
2931
3732
|
return parsed;
|
|
2932
|
-
const next =
|
|
3733
|
+
const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
|
|
2933
3734
|
return replaceStateRecord(scope, name, id, next);
|
|
2934
3735
|
};
|
|
2935
3736
|
var deleteStateRecord = (scope, name, id) => {
|
|
@@ -2997,7 +3798,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2997
3798
|
const scope = open(namespace);
|
|
2998
3799
|
if (!scope.ok)
|
|
2999
3800
|
return fromResult(scope, 201);
|
|
3000
|
-
if (!
|
|
3801
|
+
if (!isRecord4(body) || !("value" in body)) {
|
|
3001
3802
|
return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
|
|
3002
3803
|
}
|
|
3003
3804
|
const id = body.id;
|
|
@@ -3015,7 +3816,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
3015
3816
|
const scope = open(namespace);
|
|
3016
3817
|
if (!scope.ok)
|
|
3017
3818
|
return fromResult(scope, 200);
|
|
3018
|
-
if (!
|
|
3819
|
+
if (!isRecord4(body) || !("value" in body))
|
|
3019
3820
|
return adminError2(400, 'expected {"value": <json>}');
|
|
3020
3821
|
const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
3021
3822
|
if (replaced.ok)
|
|
@@ -3026,7 +3827,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
3026
3827
|
const scope = open(namespace);
|
|
3027
3828
|
if (!scope.ok)
|
|
3028
3829
|
return fromResult(scope, 200);
|
|
3029
|
-
if (!
|
|
3830
|
+
if (!isRecord4(body) || !("value" in body))
|
|
3030
3831
|
return adminError2(400, 'expected {"value": <json>}');
|
|
3031
3832
|
const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
3032
3833
|
if (patched.ok)
|
|
@@ -3046,7 +3847,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
3046
3847
|
};
|
|
3047
3848
|
|
|
3048
3849
|
// ../core/dist/version.js
|
|
3049
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3850
|
+
var PACKAGE_VERSION = true ? "1.6.0" : UNRELEASED_VERSION;
|
|
3050
3851
|
|
|
3051
3852
|
// ../core/dist/signing.js
|
|
3052
3853
|
var encoder = new TextEncoder();
|
|
@@ -3383,9 +4184,9 @@ var createWebhookHub = (options) => {
|
|
|
3383
4184
|
};
|
|
3384
4185
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
3385
4186
|
var adminError3 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
3386
|
-
var
|
|
4187
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3387
4188
|
var parseEndpoint = (value) => {
|
|
3388
|
-
if (!
|
|
4189
|
+
if (!isRecord5(value) || typeof value.url !== "string")
|
|
3389
4190
|
return "each endpoint needs a url";
|
|
3390
4191
|
try {
|
|
3391
4192
|
new URL(value.url);
|
|
@@ -3402,12 +4203,12 @@ var parseEndpoint = (value) => {
|
|
|
3402
4203
|
const events = value.events ?? value.enabledEvents;
|
|
3403
4204
|
if (Array.isArray(events))
|
|
3404
4205
|
endpoint.events = events.map(String);
|
|
3405
|
-
if (
|
|
4206
|
+
if (isRecord5(value.tags)) {
|
|
3406
4207
|
endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
|
|
3407
4208
|
}
|
|
3408
4209
|
if (typeof value.account === "string")
|
|
3409
4210
|
endpoint.tags = { ...endpoint.tags, account: value.account };
|
|
3410
|
-
if (
|
|
4211
|
+
if (isRecord5(value.headers)) {
|
|
3411
4212
|
endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
|
|
3412
4213
|
}
|
|
3413
4214
|
return endpoint;
|
|
@@ -3434,7 +4235,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3434
4235
|
return json5(200, { status: "ok" });
|
|
3435
4236
|
},
|
|
3436
4237
|
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
3437
|
-
if (!
|
|
4238
|
+
if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
3438
4239
|
return adminError3(400, "mode must be duplicate, reorder or drop");
|
|
3439
4240
|
}
|
|
3440
4241
|
const fault = { mode: body.mode };
|
|
@@ -3450,7 +4251,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3450
4251
|
}))
|
|
3451
4252
|
}),
|
|
3452
4253
|
"PUT /webhook-endpoints": ({ body, namespace }) => {
|
|
3453
|
-
const list = Array.isArray(body) ? body :
|
|
4254
|
+
const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
|
|
3454
4255
|
if (!Array.isArray(list))
|
|
3455
4256
|
return adminError3(400, "expected [{url, secret?, events?}]");
|
|
3456
4257
|
const parsed = [];
|
|
@@ -3520,8 +4321,8 @@ var DroppedConnectionError = class extends TypeError {
|
|
|
3520
4321
|
this.name = "TypeError";
|
|
3521
4322
|
}
|
|
3522
4323
|
};
|
|
3523
|
-
var operationMatcher = (
|
|
3524
|
-
const matchers = listOperations(
|
|
4324
|
+
var operationMatcher = (document3) => {
|
|
4325
|
+
const matchers = listOperations(document3).map((operation) => ({
|
|
3525
4326
|
operationId: operation.operationId,
|
|
3526
4327
|
method: operation.method.toUpperCase(),
|
|
3527
4328
|
pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
|
|
@@ -3616,10 +4417,10 @@ var createRuntime = (options) => {
|
|
|
3616
4417
|
const ensureBranch = (namespace, branch2, at) => {
|
|
3617
4418
|
if (!BRANCH_PATTERN2.test(branch2))
|
|
3618
4419
|
throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
|
|
3619
|
-
const
|
|
4420
|
+
const history2 = timeline(namespace);
|
|
3620
4421
|
if (branch2 === "main") {
|
|
3621
4422
|
if (at !== void 0) {
|
|
3622
|
-
const point =
|
|
4423
|
+
const point = history2.checkout("main", at);
|
|
3623
4424
|
restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
|
|
3624
4425
|
captured.set(namespace, point.value.snapshot);
|
|
3625
4426
|
rng.setState(point.value.rngState);
|
|
@@ -3632,10 +4433,10 @@ var createRuntime = (options) => {
|
|
|
3632
4433
|
return namespace;
|
|
3633
4434
|
}
|
|
3634
4435
|
const storage = physicalBranch(namespace, branch2);
|
|
3635
|
-
if (!
|
|
4436
|
+
if (!history2.hasBranch(branch2)) {
|
|
3636
4437
|
if (at === void 0)
|
|
3637
|
-
|
|
3638
|
-
const point =
|
|
4438
|
+
history2.commit(capture(namespace));
|
|
4439
|
+
const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
|
|
3639
4440
|
const branchRng = createRng(options.seed ?? 0);
|
|
3640
4441
|
if (point)
|
|
3641
4442
|
branchRng.setState(point.value.rngState);
|
|
@@ -3644,8 +4445,8 @@ var createRuntime = (options) => {
|
|
|
3644
4445
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3645
4446
|
if (point)
|
|
3646
4447
|
captured.set(storage, point.value.snapshot);
|
|
3647
|
-
} else if (at !== void 0 &&
|
|
3648
|
-
const point =
|
|
4448
|
+
} else if (at !== void 0 && history2.head(branch2)?.id !== at) {
|
|
4449
|
+
const point = history2.checkout(branch2, at);
|
|
3649
4450
|
if (!instances.has(storage)) {
|
|
3650
4451
|
const branchRng = createRng(options.seed ?? 0);
|
|
3651
4452
|
branchRng.setState(point.value.rngState);
|
|
@@ -3656,7 +4457,7 @@ var createRuntime = (options) => {
|
|
|
3656
4457
|
captured.set(storage, point.value.snapshot);
|
|
3657
4458
|
} else {
|
|
3658
4459
|
if (!instances.has(storage)) {
|
|
3659
|
-
const point =
|
|
4460
|
+
const point = history2.head(branch2);
|
|
3660
4461
|
const branchRng = createRng(options.seed ?? 0);
|
|
3661
4462
|
if (point)
|
|
3662
4463
|
branchRng.setState(point.value.rngState);
|
|
@@ -3680,8 +4481,8 @@ var createRuntime = (options) => {
|
|
|
3680
4481
|
const checkout = (checkpointId, checkoutOptions = {}) => {
|
|
3681
4482
|
const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
3682
4483
|
const branchName = checkoutOptions.branch ?? "main";
|
|
3683
|
-
const
|
|
3684
|
-
const point =
|
|
4484
|
+
const history2 = timeline(namespace);
|
|
4485
|
+
const point = history2.checkout(branchName, checkpointId);
|
|
3685
4486
|
const storage = ensureBranch(namespace, branchName);
|
|
3686
4487
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3687
4488
|
captured.set(storage, point.value.snapshot);
|
|
@@ -3731,9 +4532,9 @@ var createRuntime = (options) => {
|
|
|
3731
4532
|
instance(name);
|
|
3732
4533
|
restoreNamespace(sqlite, storageNamespace(name), from);
|
|
3733
4534
|
captured.set(name, from);
|
|
3734
|
-
const
|
|
3735
|
-
if (
|
|
3736
|
-
|
|
4535
|
+
const history2 = timelines.get(name);
|
|
4536
|
+
if (history2)
|
|
4537
|
+
history2.commit(capture(name), { branch: "main" });
|
|
3737
4538
|
else
|
|
3738
4539
|
timeline(name);
|
|
3739
4540
|
};
|
|
@@ -3962,10 +4763,10 @@ var createRuntime = (options) => {
|
|
|
3962
4763
|
},
|
|
3963
4764
|
release: (name, checkpointId) => timeline(name).release(checkpointId),
|
|
3964
4765
|
inspect: (name) => {
|
|
3965
|
-
const
|
|
4766
|
+
const history2 = timeline(name);
|
|
3966
4767
|
return {
|
|
3967
|
-
branches:
|
|
3968
|
-
checkpoints:
|
|
4768
|
+
branches: history2.branches(),
|
|
4769
|
+
checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
|
|
3969
4770
|
id,
|
|
3970
4771
|
branch: branchName,
|
|
3971
4772
|
parent,
|
|
@@ -3980,11 +4781,11 @@ var createRuntime = (options) => {
|
|
|
3980
4781
|
} : {},
|
|
3981
4782
|
routes: {
|
|
3982
4783
|
...credentialRoutes(credentials),
|
|
3983
|
-
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3984
4784
|
...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
|
|
3985
4785
|
...options.admin?.(runtime) ?? {},
|
|
3986
|
-
//
|
|
3987
|
-
//
|
|
4786
|
+
// Shell routes win a colliding key. Fault presets are one of them: the admin page
|
|
4787
|
+
// maps `presets`, and a service route must not replace that list with a record.
|
|
4788
|
+
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3988
4789
|
...stateAdminRoutes({
|
|
3989
4790
|
open: stateScope,
|
|
3990
4791
|
afterWrite: (namespace) => {
|
|
@@ -4059,9 +4860,12 @@ var credentialRoutes = (registry) => ({
|
|
|
4059
4860
|
}
|
|
4060
4861
|
});
|
|
4061
4862
|
var presetRoutes = (presets, runtime) => ({
|
|
4062
|
-
"GET /faults/presets": () =>
|
|
4063
|
-
|
|
4064
|
-
|
|
4863
|
+
"GET /faults/presets": () => {
|
|
4864
|
+
const body = {
|
|
4865
|
+
presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
|
|
4866
|
+
};
|
|
4867
|
+
return adminJson(200, body);
|
|
4868
|
+
},
|
|
4065
4869
|
"POST /faults/presets/:name": ({ params, body, namespace }) => {
|
|
4066
4870
|
const name = params.name;
|
|
4067
4871
|
if (!presets[name])
|
|
@@ -4072,7 +4876,7 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
4072
4876
|
});
|
|
4073
4877
|
|
|
4074
4878
|
// src/generated/openapi.ts
|
|
4075
|
-
var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Intercom REST API 2.11 (Mockingbird subset)","description":"Stateful mock subset of the Intercom REST API (version 2.11): contacts (search, create,\\nupdate, get), conversations (create, update, reply as user or admin with attachments,\\nclose/open/snooze/assign, get, search) and the admin identity endpoints. Covers the backend's\\nmember messaging adapter and the Intercom sync adapter.\\n","version":"2.11","x-mockingbird-upstream":{"note":"Trimmed from Intercom's published 2.11 API reference to the operations and fields our consumers use (intercom-messaging.adapter.ts, intercom-api.adapter.ts) and the webhook receivers (messaging.controller.ts, the EMR IntercomWebhookService)."}},"servers":[{"url":"https://api.intercom.io"}],"security":[{"bearerAuth":[]}],"paths":{"/contacts/search":{"post":{"operationId":"SearchContacts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ContactQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"responses":{"200":{"description":"Matching contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/contacts":{"post":{"operationId":"CreateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"200":{"description":"The created contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/contacts/{contact_id}":{"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}],"get":{"operationId":"GetContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdate"}}}},"responses":{"200":{"description":"The updated contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations":{"post":{"operationId":"CreateConversation","description":"A contact-initiated conversation. An optional \`Idempotency-Key\` header replays.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,40}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","body"],"properties":{"from":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string","enum":["user","lead","contact"]},"id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}},"body":{"type":"string","minLength":1,"maxLength":400},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000}}}}}},"responses":{"200":{"description":"The first message of the new conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations/search":{"post":{"operationId":"SearchConversations","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ConversationQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"},"sort_field":{"type":"string","enum":["updated_at","created_at","id","waiting_since"]},"sort_order":{"type":"string","enum":["ascending","descending"]}}}}}},"responses":{"200":{"description":"A page of matching conversations (without conversation parts)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/conversations/{conversation_id}":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"get":{"operationId":"GetConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"responses":{"200":{"description":"The conversation with its parts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"boolean"},"title":{"type":"string","maxLength":120},"custom_attributes":{"type":"object","maxProperties":4,"additionalProperties":{"type":["string","number","boolean","null"]}}}}}}},"responses":{"200":{"description":"The updated conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/reply":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ReplyConversation","description":"Reply as the contact (\`type: user\` + \`intercom_user_id\`) or as an admin (\`type: admin\` + \`admin_id\`), as JSON (attachments as base64 \`attachment_files\`) or multipart (\`attachment_files[]\` file parts). Admin comments fire \`conversation.admin.replied\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyBody"}},"multipart/form-data":{"schema":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string"},"type":{"type":"string"},"intercom_user_id":{"type":"string"},"admin_id":{"type":"string"},"body":{"type":"string"},"attachment_files[]":{"type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/parts":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ManageConversation","description":"Close, open, snooze or assign; close and open fire \`conversation.admin.closed\` / \`opened\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message_type","type","admin_id"],"properties":{"message_type":{"type":"string","enum":["close","open","snoozed","assignment"]},"type":{"type":"string","enum":["admin"]},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"body":{"type":"string","maxLength":400},"snoozed_until":{"type":"integer","minimum":1600000000,"maximum":1900000000},"assignee_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admins":{"get":{"operationId":"ListAdmins","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The workspace's admins","content":{"application/json":{"schema":{"type":"object","required":["type","admins"],"properties":{"type":{"type":"string","enum":["admin.list"]},"admins":{"type":"array","items":{"$ref":"#/components/schemas/Admin"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me":{"get":{"operationId":"GetMe","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The admin that owns the access token","content":{"application/json":{"schema":{"type":"object","required":["type","id","email","app"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"email_verified":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"avatar":{"type":"object"},"app":{"type":"object","required":["type","id_code"],"properties":{"type":{"type":"string"},"id_code":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"integer"},"secure":{"type":"boolean"},"identity_verification":{"type":"boolean"},"timezone":{"type":"string"},"region":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"ConversationId":{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"conversation","missing":"999999999999999"}}},"DisplayAs":{"name":"display_as","in":"query","schema":{"type":"string","enum":["plaintext"]}}},"responses":{"BadRequest":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Unauthorized":{"description":"Missing or invalid access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"NotFound":{"description":"Unknown resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Conflict":{"description":"A contact with that external_id or email already exists (or an idempotency conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}}},"schemas":{"ErrorList":{"type":"object","required":["type","errors"],"properties":{"type":{"type":"string","enum":["error.list"]},"request_id":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"errors":{"type":"array","minItems":1,"items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"Pagination":{"type":"object","properties":{"per_page":{"type":"integer","minimum":1,"maximum":150},"starting_after":{"type":"string","maxLength":80}}},"ContactFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["external_id","email","id","name","role","phone"]},"operator":{"type":"string","enum":["=","!=","~","!~","^","$","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ContactQuery":{"oneOf":[{"$ref":"#/components/schemas/ContactFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ContactFilter"}}}}]},"ConversationFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["id","contact_ids","admin_assignee_id","team_assignee_id","state","open","read","created_at","updated_at","source.author.email","source.delivered_as"]},"operator":{"type":"string","enum":["=","!=","<",">","~","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"integer"},{"type":"boolean"},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ConversationQuery":{"oneOf":[{"$ref":"#/components/schemas/ConversationFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ConversationFilter"}}}}]},"CustomAttributes":{"type":"object","maxProperties":30,"additionalProperties":{"type":["string","number","boolean","null"]}},"ContactCreate":{"type":"object","properties":{"role":{"type":"string","enum":["user","lead"]},"external_id":{"description":"Any string. The first branch steers the walk generator toward a few values, so random walks reach the duplicate (409) path.","anyOf":[{"type":"string","pattern":"^[1-4]$"},{"type":"string","minLength":1,"maxLength":64}]},"email":{"type":"string","format":"email","maxLength":120},"name":{"type":["string","null"],"maxLength":80},"phone":{"type":["string","null"],"maxLength":40},"signed_up_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"last_seen_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"custom_attributes":{"$ref":"#/components/schemas/CustomAttributes"}}},"ContactUpdate":{"allOf":[{"$ref":"#/components/schemas/ContactCreate"}]},"ReplyBody":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string","enum":["comment","note","quick_reply"]},"type":{"type":"string","enum":["user","admin"]},"body":{"type":"string","maxLength":400},"intercom_user_id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}},"user_id":{"type":"string","maxLength":64},"email":{"type":"string","maxLength":120},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000},"attachment_urls":{"type":"array","maxItems":3,"items":{"type":"string","maxLength":200}},"attachment_files":{"type":"array","maxItems":3,"items":{"type":"object","required":["content_type","data","name"],"properties":{"content_type":{"type":"string","maxLength":80},"data":{"type":"string","maxLength":400},"name":{"type":"string","minLength":1,"maxLength":80}}}}}},"Contact":{"type":"object","required":["type","id","role","created_at","updated_at","custom_attributes"],"properties":{"type":{"type":"string","enum":["contact"]},"id":{"type":"string","x-mockingbird-resource":{"type":"contact","identity":true}},"workspace_id":{"type":"string"},"external_id":{"type":["string","null"]},"role":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"name":{"type":["string","null"]},"avatar":{"type":["string","null"]},"owner_id":{"type":["integer","null"]},"has_hard_bounced":{"type":"boolean"},"marked_email_as_spam":{"type":"boolean"},"unsubscribed_from_emails":{"type":"boolean"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"signed_up_at":{"type":["integer","null"]},"last_seen_at":{"type":["integer","null"]},"custom_attributes":{"type":"object"},"tags":{"type":"object"},"notes":{"type":"object"},"companies":{"type":"object"},"location":{"type":"object"},"social_profiles":{"type":"object"}}},"Pages":{"type":"object","required":["type","page","per_page","total_pages"],"properties":{"type":{"type":"string","enum":["pages"]},"page":{"type":"integer"},"per_page":{"type":"integer"},"total_pages":{"type":"integer"},"next":{"type":"object","required":["page","starting_after"],"properties":{"page":{"type":"integer"},"starting_after":{"type":"string"}}}}},"ContactList":{"type":"object","required":["type","data","total_count","pages"],"properties":{"type":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total_count":{"type":"integer"},"pages":{"$ref":"#/components/schemas/Pages"}}},"Author":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"name":{"type":["string","null"]},"email":{"type":["string","null"]}}},"Attachment":{"type":"object","required":["type","name","url","content_type"],"properties":{"type":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"content_type":{"type":"string"},"filesize":{"type":"integer"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]}}},"ConversationPart":{"type":"object","required":["type","id","part_type","created_at","author"],"properties":{"type":{"type":"string","enum":["conversation_part"]},"id":{"type":"string"},"part_type":{"type":"string"},"body":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"notified_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"assigned_to":{"type":["object","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"external_id":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"Conversation":{"type":"object","required":["type","id","created_at","updated_at","open","state","read","source","contacts"],"properties":{"type":{"type":"string","enum":["conversation"]},"id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}},"title":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"waiting_since":{"type":["integer","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"snoozed_until":{"type":["integer","null"]},"open":{"type":"boolean"},"state":{"type":"string","enum":["open","closed","snoozed"]},"read":{"type":"boolean"},"priority":{"type":"string"},"admin_assignee_id":{"type":["integer","null"]},"team_assignee_id":{"type":["string","null"]},"tags":{"type":"object"},"conversation_rating":{"type":["object","null"]},"source":{"type":"object","required":["type","id","delivered_as","body","author"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"delivered_as":{"type":"string"},"subject":{"type":"string"},"body":{"type":["string","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"url":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"contacts":{"type":"object","required":["type","contacts"],"properties":{"type":{"type":"string"},"contacts":{"type":"array","items":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"external_id":{"type":["string","null"]}}}}}},"teammates":{"type":"object"},"custom_attributes":{"type":"object"},"first_contact_reply":{"type":["object","null"]},"sla_applied":{"type":["object","null"]},"statistics":{"type":["object","null"]},"ai_agent_participated":{"type":"boolean"},"conversation_parts":{"type":"object","required":["type","conversation_parts","total_count"],"properties":{"type":{"type":"string"},"conversation_parts":{"type":"array","items":{"$ref":"#/components/schemas/ConversationPart"}},"total_count":{"type":"integer"}}}}},"ConversationList":{"type":"object","required":["type","pages","total_count","conversations"],"properties":{"type":{"type":"string","enum":["conversation.list"]},"pages":{"$ref":"#/components/schemas/Pages"},"total_count":{"type":"integer"},"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}}},"Message":{"type":"object","required":["type","id","created_at","body","message_type","conversation_id"],"properties":{"type":{"type":"string","enum":["user_message"]},"id":{"type":"string"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"body":{"type":"string"},"message_type":{"type":"string","enum":["inapp"]},"conversation_id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}}}},"Admin":{"type":"object","required":["type","id","name","email"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string","x-mockingbird-resource":{"type":"admin","identity":true}},"name":{"type":"string"},"email":{"type":"string"},"job_title":{"type":["string","null"]},"away_mode_enabled":{"type":"boolean"},"away_mode_reassign":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"team_ids":{"type":"array","items":{"type":"integer"}}}}}}}`);
|
|
4879
|
+
var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Intercom REST API 2.11 (Mockingbird subset)","description":"Stateful mock subset of the Intercom REST API (version 2.11): contacts (search, create,\\nupdate, get), conversations (create, update, reply as user or admin with attachments,\\nclose/open/snooze/assign, get, search) and the admin identity endpoints. Covers the backend's\\nmember messaging adapter and the Intercom sync adapter.\\n","version":"2.11","x-mockingbird-upstream":{"note":"Trimmed from Intercom's published 2.11 API reference to the operations and fields our consumers use (intercom-messaging.adapter.ts, intercom-api.adapter.ts) and the webhook receivers (messaging.controller.ts, the EMR IntercomWebhookService)."}},"servers":[{"url":"https://api.intercom.io"}],"security":[{"bearerAuth":[]}],"paths":{"/contacts/search":{"post":{"operationId":"SearchContacts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ContactQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"responses":{"200":{"description":"Matching contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/contacts":{"post":{"operationId":"CreateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"200":{"description":"The created contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/contacts/{contact_id}":{"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}],"get":{"operationId":"GetContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdate"}}}},"responses":{"200":{"description":"The updated contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations":{"post":{"operationId":"CreateConversation","description":"A contact-initiated conversation. An optional \`Idempotency-Key\` header replays.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,40}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","body"],"properties":{"from":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string","enum":["user","lead","contact"]},"id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}},"body":{"type":"string","minLength":1,"maxLength":400},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000}}}}}},"responses":{"200":{"description":"The first message of the new conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations/search":{"post":{"operationId":"SearchConversations","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ConversationQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"},"sort_field":{"type":"string","enum":["updated_at","created_at","id","waiting_since"]},"sort_order":{"type":"string","enum":["ascending","descending"]}}}}}},"responses":{"200":{"description":"A page of matching conversations (without conversation parts)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/conversations/{conversation_id}":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"get":{"operationId":"GetConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"responses":{"200":{"description":"The conversation with its parts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"boolean"},"title":{"type":"string","maxLength":120},"custom_attributes":{"type":"object","maxProperties":4,"additionalProperties":{"type":["string","number","boolean","null"]}}}}}}},"responses":{"200":{"description":"The updated conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/reply":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ReplyConversation","description":"Reply as the contact (\`type: user\` + \`intercom_user_id\`) or as an admin (\`type: admin\` + \`admin_id\`), as JSON (attachments as base64 \`attachment_files\`) or multipart (\`attachment_files[]\` file parts). Admin comments fire \`conversation.admin.replied\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyBody"}},"multipart/form-data":{"schema":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string"},"type":{"type":"string"},"intercom_user_id":{"type":"string"},"admin_id":{"type":"string"},"body":{"type":"string"},"attachment_files[]":{"type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/parts":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ManageConversation","description":"Close, open, snooze or assign; close and open fire \`conversation.admin.closed\` / \`opened\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message_type","type","admin_id"],"properties":{"message_type":{"type":"string","enum":["close","open","snoozed","assignment"]},"type":{"type":"string","enum":["admin"]},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"body":{"type":"string","maxLength":400},"snoozed_until":{"type":"integer","minimum":1600000000,"maximum":1900000000},"assignee_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admins":{"get":{"operationId":"ListAdmins","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The workspace's admins","content":{"application/json":{"schema":{"type":"object","required":["type","admins"],"properties":{"type":{"type":"string","enum":["admin.list"]},"admins":{"type":"array","items":{"$ref":"#/components/schemas/Admin"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me":{"get":{"operationId":"GetMe","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The admin that owns the access token","content":{"application/json":{"schema":{"type":"object","required":["type","id","email","app"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"email_verified":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"avatar":{"type":"object"},"app":{"type":"object","required":["type","id_code"],"properties":{"type":{"type":"string"},"id_code":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"integer"},"secure":{"type":"boolean"},"identity_verification":{"type":"boolean"},"timezone":{"type":"string"},"region":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"ConversationId":{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"conversation","missing":"999999999999999"}}},"DisplayAs":{"name":"display_as","in":"query","schema":{"type":"string","enum":["plaintext"]}}},"responses":{"BadRequest":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Unauthorized":{"description":"Missing or invalid access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"NotFound":{"description":"Unknown resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Conflict":{"description":"A contact with that external_id or email already exists (or an idempotency conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}}},"schemas":{"ErrorList":{"type":"object","required":["type","errors"],"properties":{"type":{"type":"string","enum":["error.list"]},"request_id":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"errors":{"type":"array","minItems":1,"items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"Pagination":{"type":"object","properties":{"per_page":{"type":"integer","minimum":1,"maximum":150},"starting_after":{"type":"string","maxLength":80}}},"ContactFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["external_id","email","id","name","role","phone"]},"operator":{"type":"string","enum":["=","!=","~","!~","^","$","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ContactQuery":{"oneOf":[{"$ref":"#/components/schemas/ContactFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ContactFilter"}}}}]},"ConversationFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["id","contact_ids","admin_assignee_id","team_assignee_id","state","open","read","created_at","updated_at","source.author.email","source.delivered_as"]},"operator":{"type":"string","enum":["=","!=","<",">","~","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"integer"},{"type":"boolean"},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ConversationQuery":{"oneOf":[{"$ref":"#/components/schemas/ConversationFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ConversationFilter"}}}}]},"CustomAttributes":{"type":"object","maxProperties":30,"additionalProperties":{"type":["string","number","boolean","null"]}},"ContactCreate":{"type":"object","properties":{"role":{"type":"string","enum":["user","lead"]},"external_id":{"description":"Any string. The first branch steers the walk generator toward a few values, so random walks reach the duplicate (409) path.","anyOf":[{"type":"string","pattern":"^[1-4]$"},{"type":"string","minLength":1,"maxLength":64}]},"email":{"type":"string","format":"email","maxLength":120},"name":{"type":["string","null"],"maxLength":80},"phone":{"type":["string","null"],"maxLength":40},"signed_up_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"last_seen_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"custom_attributes":{"$ref":"#/components/schemas/CustomAttributes"}}},"ContactUpdate":{"allOf":[{"$ref":"#/components/schemas/ContactCreate"}]},"ReplyBody":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string","enum":["comment","note","quick_reply"]},"type":{"type":"string","enum":["user","admin"]},"body":{"type":"string","maxLength":400},"intercom_user_id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}},"user_id":{"type":"string","maxLength":64},"email":{"type":"string","maxLength":120},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000},"attachment_urls":{"type":"array","maxItems":3,"items":{"type":"string","maxLength":200}},"attachment_files":{"type":"array","maxItems":3,"items":{"type":"object","required":["content_type","data","name"],"properties":{"content_type":{"type":"string","maxLength":80},"data":{"type":"string","maxLength":400},"name":{"type":"string","minLength":1,"maxLength":80}}}}}},"Contact":{"type":"object","required":["type","id","role","created_at","updated_at","custom_attributes"],"properties":{"type":{"type":"string","enum":["contact"]},"id":{"type":"string","x-mockingbird-resource":{"type":"contact","identity":true}},"workspace_id":{"type":"string"},"external_id":{"type":["string","null"]},"role":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"name":{"type":["string","null"]},"avatar":{"type":["string","null"]},"owner_id":{"type":["integer","null"]},"has_hard_bounced":{"type":"boolean"},"marked_email_as_spam":{"type":"boolean"},"unsubscribed_from_emails":{"type":"boolean"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"signed_up_at":{"type":["integer","null"]},"last_seen_at":{"type":["integer","null"]},"custom_attributes":{"type":"object"},"tags":{"type":"object"},"notes":{"type":"object"},"companies":{"type":"object"},"location":{"type":"object"},"social_profiles":{"type":"object"}}},"Pages":{"type":"object","required":["type","page","per_page","total_pages"],"properties":{"type":{"type":"string","enum":["pages"]},"page":{"type":"integer"},"per_page":{"type":"integer"},"total_pages":{"type":"integer"},"next":{"type":"object","required":["page","starting_after"],"properties":{"page":{"type":"integer"},"starting_after":{"type":"string"}}}}},"ContactList":{"type":"object","required":["type","data","total_count","pages"],"properties":{"type":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total_count":{"type":"integer"},"pages":{"$ref":"#/components/schemas/Pages"}}},"Author":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"name":{"type":["string","null"]},"email":{"type":["string","null"]}}},"Attachment":{"type":"object","required":["type","name","url","content_type"],"properties":{"type":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"content_type":{"type":"string"},"filesize":{"type":"integer"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]}}},"ConversationPart":{"type":"object","required":["type","id","part_type","created_at","author"],"properties":{"type":{"type":"string","enum":["conversation_part"]},"id":{"type":"string"},"part_type":{"type":"string"},"body":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"notified_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"assigned_to":{"type":["object","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"external_id":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"Conversation":{"type":"object","required":["type","id","created_at","updated_at","open","state","read","source","contacts"],"properties":{"type":{"type":"string","enum":["conversation"]},"id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}},"title":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"waiting_since":{"type":["integer","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"snoozed_until":{"type":["integer","null"]},"open":{"type":"boolean"},"state":{"type":"string","enum":["open","closed","snoozed"]},"read":{"type":"boolean"},"priority":{"type":"string"},"admin_assignee_id":{"type":["integer","null"]},"team_assignee_id":{"type":["string","null"]},"tags":{"type":"object"},"conversation_rating":{"type":["object","null"]},"source":{"type":"object","required":["type","id","delivered_as","body","author"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"delivered_as":{"type":"string"},"subject":{"type":"string"},"body":{"type":["string","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"url":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"contacts":{"type":"object","required":["type","contacts"],"properties":{"type":{"type":"string"},"contacts":{"type":"array","items":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"external_id":{"type":["string","null"]}}}}}},"teammates":{"type":"object"},"custom_attributes":{"type":"object"},"first_contact_reply":{"type":["object","null"]},"sla_applied":{"type":["object","null"]},"statistics":{"type":["object","null"]},"ai_agent_participated":{"type":"boolean"},"conversation_parts":{"type":"object","required":["type","conversation_parts","total_count"],"properties":{"type":{"type":"string"},"conversation_parts":{"type":"array","items":{"$ref":"#/components/schemas/ConversationPart"}},"total_count":{"type":"integer"}}}}},"ConversationList":{"type":"object","required":["type","pages","total_count","conversations"],"properties":{"type":{"type":"string","enum":["conversation.list"]},"pages":{"$ref":"#/components/schemas/Pages"},"total_count":{"type":"integer"},"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}}},"Message":{"type":"object","required":["type","id","created_at","body","message_type","conversation_id"],"properties":{"type":{"type":"string","enum":["user_message"]},"id":{"type":"string"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"body":{"type":"string"},"message_type":{"type":"string","enum":["inapp"]},"conversation_id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}}}},"Admin":{"type":"object","required":["type","id","name","email"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string","x-mockingbird-resource":{"type":"admin","identity":true}},"name":{"type":"string"},"email":{"type":"string"},"job_title":{"type":["string","null"]},"away_mode_enabled":{"type":"boolean"},"away_mode_reassign":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"team_ids":{"type":"array","items":{"type":"integer"}}}}}}}`);
|
|
4076
4880
|
var operationIds = ["SearchContacts", "CreateContact", "GetContact", "UpdateContact", "CreateConversation", "SearchConversations", "GetConversation", "UpdateConversation", "ReplyConversation", "ManageConversation", "ListAdmins", "GetMe"];
|
|
4077
4881
|
var supportedOperationIds = ["SearchContacts", "CreateContact", "GetContact", "UpdateContact", "CreateConversation", "SearchConversations", "GetConversation", "UpdateConversation", "ReplyConversation", "ManageConversation", "ListAdmins", "GetMe"];
|
|
4078
4882
|
|
|
@@ -4080,9 +4884,9 @@ var supportedOperationIds = ["SearchContacts", "CreateContact", "GetContact", "U
|
|
|
4080
4884
|
var QueryError = class extends Error {
|
|
4081
4885
|
};
|
|
4082
4886
|
var FILTER_OPERATORS = ["=", "!=", "IN", "NIN", "<", ">", "~", "!~", "^", "$"];
|
|
4083
|
-
var
|
|
4887
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4084
4888
|
var parseQuery = (value, fields, depth = 0) => {
|
|
4085
|
-
if (!
|
|
4889
|
+
if (!isRecord6(value)) throw new QueryError("query must be an object");
|
|
4086
4890
|
const operator = value.operator;
|
|
4087
4891
|
if (operator === "AND" || operator === "OR") {
|
|
4088
4892
|
if (depth >= 2) throw new QueryError("queries can be nested at most two levels deep");
|
|
@@ -4157,7 +4961,7 @@ var decodeCursor = (cursor) => {
|
|
|
4157
4961
|
throw new QueryError("starting_after is not a valid cursor");
|
|
4158
4962
|
};
|
|
4159
4963
|
var paginate = (items, pagination, defaultPerPage) => {
|
|
4160
|
-
const options =
|
|
4964
|
+
const options = isRecord6(pagination) ? pagination : {};
|
|
4161
4965
|
const perPage = options.per_page === void 0 ? defaultPerPage : Number(options.per_page);
|
|
4162
4966
|
if (!Number.isInteger(perPage) || perPage < 1 || perPage > 150) {
|
|
4163
4967
|
throw new QueryError("per_page must be between 1 and 150");
|
|
@@ -4355,7 +5159,7 @@ var INTERCOM_PRESETS = {
|
|
|
4355
5159
|
};
|
|
4356
5160
|
var json6 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
4357
5161
|
var adminError4 = (status, message) => json6(status, { error: { type: "mockingbird_admin", message } });
|
|
4358
|
-
var
|
|
5162
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4359
5163
|
var guard = (run) => {
|
|
4360
5164
|
try {
|
|
4361
5165
|
return run();
|
|
@@ -4373,7 +5177,7 @@ var adminRoutes = (runtime) => {
|
|
|
4373
5177
|
"GET /contacts": ({ namespace }) => json6(200, { contacts: api(namespace).contacts() }),
|
|
4374
5178
|
"GET /conversations": ({ namespace }) => json6(200, { conversations: api(namespace).conversations() }),
|
|
4375
5179
|
"POST /conversations": ({ body, namespace }) => guard(() => {
|
|
4376
|
-
if (!
|
|
5180
|
+
if (!isRecord7(body) || typeof body.body !== "string") {
|
|
4377
5181
|
return adminError4(400, 'expected {"contactId" | "externalId", "adminId"?, "body"}');
|
|
4378
5182
|
}
|
|
4379
5183
|
const state = api(namespace).state;
|
|
@@ -4389,7 +5193,7 @@ var adminRoutes = (runtime) => {
|
|
|
4389
5193
|
"POST /conversations/:id/admin-reply": ({ params, body, namespace }) => guard(() => {
|
|
4390
5194
|
const found = conversation(namespace, params.id);
|
|
4391
5195
|
if (!found) return adminError4(404, `no conversation ${params.id}`);
|
|
4392
|
-
if (!
|
|
5196
|
+
if (!isRecord7(body) || typeof body.body !== "string") {
|
|
4393
5197
|
return adminError4(
|
|
4394
5198
|
400,
|
|
4395
5199
|
'expected {"adminId" | "admin_id"?, "body", "messageType"?: "comment" | "note"}'
|
|
@@ -4411,18 +5215,18 @@ var adminRoutes = (runtime) => {
|
|
|
4411
5215
|
"POST /conversations/:id/close": ({ params, body, namespace }) => guard(() => {
|
|
4412
5216
|
const found = conversation(namespace, params.id);
|
|
4413
5217
|
if (!found) return adminError4(404, `no conversation ${params.id}`);
|
|
4414
|
-
const adminId =
|
|
5218
|
+
const adminId = isRecord7(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
|
|
4415
5219
|
return json6(200, api(namespace).manage(found, { action: "close", adminId }));
|
|
4416
5220
|
}),
|
|
4417
5221
|
"POST /conversations/:id/open": ({ params, body, namespace }) => guard(() => {
|
|
4418
5222
|
const found = conversation(namespace, params.id);
|
|
4419
5223
|
if (!found) return adminError4(404, `no conversation ${params.id}`);
|
|
4420
|
-
const adminId =
|
|
5224
|
+
const adminId = isRecord7(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
|
|
4421
5225
|
return json6(200, api(namespace).manage(found, { action: "open", adminId }));
|
|
4422
5226
|
}),
|
|
4423
5227
|
"PUT /admins": ({ body, namespace }) => {
|
|
4424
|
-
const list = Array.isArray(body) ? body :
|
|
4425
|
-
if (!Array.isArray(list) || !list.every((a) =>
|
|
5228
|
+
const list = Array.isArray(body) ? body : isRecord7(body) ? body.admins : void 0;
|
|
5229
|
+
if (!Array.isArray(list) || !list.every((a) => isRecord7(a) && typeof a.id === "string")) {
|
|
4426
5230
|
return adminError4(400, "expected [{id, name, email}, \u2026]");
|
|
4427
5231
|
}
|
|
4428
5232
|
const state = api(namespace).state;
|
|
@@ -4444,7 +5248,7 @@ var adminRoutes = (runtime) => {
|
|
|
4444
5248
|
},
|
|
4445
5249
|
"GET /settings": ({ namespace }) => json6(200, api(namespace).state.current()),
|
|
4446
5250
|
"PUT /settings": ({ body, namespace }) => {
|
|
4447
|
-
if (!
|
|
5251
|
+
if (!isRecord7(body)) return adminError4(400, "expected a JSON object");
|
|
4448
5252
|
const patch = {};
|
|
4449
5253
|
if (body.tokens !== void 0) {
|
|
4450
5254
|
if (!Array.isArray(body.tokens)) return adminError4(400, "tokens: string[]");
|
|
@@ -4481,7 +5285,7 @@ var createRuntime2 = (options = {}) => {
|
|
|
4481
5285
|
});
|
|
4482
5286
|
const runtime = createRuntime({
|
|
4483
5287
|
name: INTERCOM_NAMESPACE,
|
|
4484
|
-
document,
|
|
5288
|
+
document: document2,
|
|
4485
5289
|
...options.sqlite ? { sqlite: options.sqlite } : {},
|
|
4486
5290
|
...options.clock ? { clock: options.clock } : {},
|
|
4487
5291
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
@@ -4559,7 +5363,7 @@ var CONVERSATION_FIELDS = [
|
|
|
4559
5363
|
"source.author.name",
|
|
4560
5364
|
"source.author.email"
|
|
4561
5365
|
];
|
|
4562
|
-
var
|
|
5366
|
+
var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4563
5367
|
var str = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4564
5368
|
var IntercomError = class extends Error {
|
|
4565
5369
|
constructor(status, code, message) {
|
|
@@ -4640,7 +5444,7 @@ var IntercomAPI = class {
|
|
|
4640
5444
|
})
|
|
4641
5445
|
});
|
|
4642
5446
|
this.service = createService({
|
|
4643
|
-
document,
|
|
5447
|
+
document: document2,
|
|
4644
5448
|
handlers,
|
|
4645
5449
|
sqlite,
|
|
4646
5450
|
namespace,
|
|
@@ -4704,7 +5508,7 @@ var IntercomAPI = class {
|
|
|
4704
5508
|
`${[first.path, missing[1]].filter(Boolean).join(".")} is required`
|
|
4705
5509
|
) : new IntercomError(400, "parameter_invalid", `${first.path || "body"} ${first.message}`);
|
|
4706
5510
|
}
|
|
4707
|
-
return context.body.kind === "json" &&
|
|
5511
|
+
return context.body.kind === "json" && isRecord8(context.body.value) ? context.body.value : {};
|
|
4708
5512
|
}
|
|
4709
5513
|
// ─── Contacts ───────────────────────────────────────────────────────────────────────────
|
|
4710
5514
|
contactBody(contact) {
|
|
@@ -4765,7 +5569,7 @@ var IntercomAPI = class {
|
|
|
4765
5569
|
checkCustomAttributes(attributes) {
|
|
4766
5570
|
if (attributes === void 0) return {};
|
|
4767
5571
|
const defined = this.state.current().customAttributes;
|
|
4768
|
-
const given =
|
|
5572
|
+
const given = isRecord8(attributes) ? attributes : {};
|
|
4769
5573
|
if (defined !== null) {
|
|
4770
5574
|
const unknown = Object.keys(given).find((key) => !defined.includes(key));
|
|
4771
5575
|
if (unknown !== void 0) {
|
|
@@ -4999,7 +5803,7 @@ var IntercomAPI = class {
|
|
|
4999
5803
|
...typeof body.title === "string" ? { title: body.title } : {},
|
|
5000
5804
|
custom_attributes: {
|
|
5001
5805
|
...conversation.custom_attributes,
|
|
5002
|
-
...
|
|
5806
|
+
...isRecord8(body.custom_attributes) ? body.custom_attributes : {}
|
|
5003
5807
|
},
|
|
5004
5808
|
updated_at: this.seconds()
|
|
5005
5809
|
};
|
|
@@ -5319,7 +6123,7 @@ var IntercomAPI = class {
|
|
|
5319
6123
|
if (field.startsWith("source.")) {
|
|
5320
6124
|
let value = conversation.source;
|
|
5321
6125
|
for (const key of field.slice("source.".length).split(".")) {
|
|
5322
|
-
value =
|
|
6126
|
+
value = isRecord8(value) ? value[key] : void 0;
|
|
5323
6127
|
}
|
|
5324
6128
|
return value;
|
|
5325
6129
|
}
|
|
@@ -5403,7 +6207,7 @@ var IntercomAPI = class {
|
|
|
5403
6207
|
};
|
|
5404
6208
|
|
|
5405
6209
|
export {
|
|
5406
|
-
document,
|
|
6210
|
+
document2 as document,
|
|
5407
6211
|
operationIds,
|
|
5408
6212
|
supportedOperationIds,
|
|
5409
6213
|
encodeCursor,
|
|
@@ -5421,4 +6225,4 @@ export {
|
|
|
5421
6225
|
IntercomError,
|
|
5422
6226
|
IntercomAPI
|
|
5423
6227
|
};
|
|
5424
|
-
//# sourceMappingURL=chunk-
|
|
6228
|
+
//# sourceMappingURL=chunk-ZKUDPTGZ.js.map
|