@formant/data-sdk 1.14.1 → 1.15.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/dist/data-sdk.cjs.js +2 -2
- package/dist/data-sdk.cjs.js.map +1 -1
- package/dist/data-sdk.es.js +70 -53
- package/dist/data-sdk.es.js.map +1 -1
- package/dist/data-sdk.es6.js +17 -0
- package/dist/data-sdk.umd.js +1 -1
- package/dist/types/data-sdk/src/devices/Device.d.ts +7 -0
- package/package.json +1 -1
package/dist/data-sdk.es.js
CHANGED
|
@@ -4,7 +4,7 @@ var s = (a, e, t) => (de(a, typeof e != "symbol" ? e + "" : e, t), t);
|
|
|
4
4
|
import { decode as G } from "base-64";
|
|
5
5
|
import * as u from "date-fns";
|
|
6
6
|
import { startOfMinute as he, addMinutes as le, roundToNearestMinutes as ue, addSeconds as me } from "date-fns";
|
|
7
|
-
import { RtcClient as
|
|
7
|
+
import { RtcClient as A, SignalingPromiseClient as D } from "@formant/realtime-sdk";
|
|
8
8
|
import { EventEmitter as fe } from "eventemitter3";
|
|
9
9
|
import { deflate as we } from "pako";
|
|
10
10
|
import { fromByteArray as pe } from "base64-js";
|
|
@@ -303,13 +303,13 @@ function Ce() {
|
|
|
303
303
|
type: "hide_analytics_date_picker"
|
|
304
304
|
});
|
|
305
305
|
}
|
|
306
|
-
function
|
|
306
|
+
function Ee(a) {
|
|
307
307
|
T({
|
|
308
308
|
type: "go_to_device",
|
|
309
309
|
deviceId: a
|
|
310
310
|
});
|
|
311
311
|
}
|
|
312
|
-
function
|
|
312
|
+
function Se(a) {
|
|
313
313
|
T({
|
|
314
314
|
type: "go_to_time",
|
|
315
315
|
time: a.getTime()
|
|
@@ -346,7 +346,7 @@ function Re(a, e) {
|
|
|
346
346
|
after: e || 0
|
|
347
347
|
});
|
|
348
348
|
}
|
|
349
|
-
function
|
|
349
|
+
function Ae(a) {
|
|
350
350
|
const e = k();
|
|
351
351
|
if (!e)
|
|
352
352
|
throw new Error("No module context");
|
|
@@ -356,10 +356,10 @@ function be(a) {
|
|
|
356
356
|
menus: a
|
|
357
357
|
});
|
|
358
358
|
}
|
|
359
|
-
function
|
|
359
|
+
function be(a) {
|
|
360
360
|
T({ type: "show_message", message: a });
|
|
361
361
|
}
|
|
362
|
-
function
|
|
362
|
+
function je(a, e) {
|
|
363
363
|
const t = (n) => {
|
|
364
364
|
const i = n.data;
|
|
365
365
|
i.type === "channel_data" && i.channel === a && e({
|
|
@@ -369,7 +369,7 @@ function De(a, e) {
|
|
|
369
369
|
};
|
|
370
370
|
return window.addEventListener("message", t), () => window.removeEventListener("message", t);
|
|
371
371
|
}
|
|
372
|
-
function
|
|
372
|
+
function De(a) {
|
|
373
373
|
const e = (t) => {
|
|
374
374
|
const n = t.data;
|
|
375
375
|
n.type === "module_menu_item_clicked" && a(n.menu);
|
|
@@ -641,8 +641,8 @@ const q = class {
|
|
|
641
641
|
};
|
|
642
642
|
let g = q;
|
|
643
643
|
s(g, "getCurrentModuleContext", k), // senders
|
|
644
|
-
s(g, "disableAnalyticsBottomBar", Ce), s(g, "goToDevice",
|
|
645
|
-
s(g, "addAccessTokenRefreshListener", F), s(g, "addChannelDataListener",
|
|
644
|
+
s(g, "disableAnalyticsBottomBar", Ce), s(g, "goToDevice", Ee), s(g, "goToTime", Se), s(g, "refreshAuthToken", H), s(g, "requestModuleData", ke), s(g, "sendChannelData", Oe), s(g, "setModuleDateTimeRange", Re), s(g, "setupModuleMenus", Ae), s(g, "showMessage", be), // listeners
|
|
645
|
+
s(g, "addAccessTokenRefreshListener", F), s(g, "addChannelDataListener", je), s(g, "addMenuListener", De), s(g, "addModuleConfigurationListener", Ne), s(g, "addModuleDataListener", Pe), s(g, "addOverviewDeviceListener", Le), s(g, "addStreamListener", Ue), // bidirectional
|
|
646
646
|
s(g, "getDate", Je), s(g, "prompt", Ve), s(g, "_isOnline", null);
|
|
647
647
|
function f(a, e) {
|
|
648
648
|
if (a !== void 0)
|
|
@@ -730,8 +730,8 @@ Gt = O;
|
|
|
730
730
|
const P = async () => f(r.token, "Realtime when user isn't authorized"), R = {
|
|
731
731
|
[p.UNKNOWN]: new N({
|
|
732
732
|
ttlMs: 2500,
|
|
733
|
-
createClient: (a) => new
|
|
734
|
-
signalingClient: new
|
|
733
|
+
createClient: (a) => new A({
|
|
734
|
+
signalingClient: new D(h),
|
|
735
735
|
getToken: P,
|
|
736
736
|
sessionType: p.UNKNOWN,
|
|
737
737
|
receive: a
|
|
@@ -739,8 +739,8 @@ const P = async () => f(r.token, "Realtime when user isn't authorized"), R = {
|
|
|
739
739
|
}),
|
|
740
740
|
[p.TELEOP]: new N({
|
|
741
741
|
ttlMs: 2500,
|
|
742
|
-
createClient: (a) => new
|
|
743
|
-
signalingClient: new
|
|
742
|
+
createClient: (a) => new A({
|
|
743
|
+
signalingClient: new D(h),
|
|
744
744
|
getToken: P,
|
|
745
745
|
sessionType: p.TELEOP,
|
|
746
746
|
receive: a
|
|
@@ -748,8 +748,8 @@ const P = async () => f(r.token, "Realtime when user isn't authorized"), R = {
|
|
|
748
748
|
}),
|
|
749
749
|
[p.PORT_FORWARD]: new N({
|
|
750
750
|
ttlMs: 2500,
|
|
751
|
-
createClient: (a) => new
|
|
752
|
-
signalingClient: new
|
|
751
|
+
createClient: (a) => new A({
|
|
752
|
+
signalingClient: new D(h),
|
|
753
753
|
getToken: P,
|
|
754
754
|
sessionType: p.PORT_FORWARD,
|
|
755
755
|
receive: a
|
|
@@ -757,8 +757,8 @@ const P = async () => f(r.token, "Realtime when user isn't authorized"), R = {
|
|
|
757
757
|
}),
|
|
758
758
|
[p.OBSERVE]: new N({
|
|
759
759
|
ttlMs: 2500,
|
|
760
|
-
createClient: (a) => new
|
|
761
|
-
signalingClient: new
|
|
760
|
+
createClient: (a) => new A({
|
|
761
|
+
signalingClient: new D(h),
|
|
762
762
|
getToken: P,
|
|
763
763
|
sessionType: p.OBSERVE,
|
|
764
764
|
receive: a
|
|
@@ -766,8 +766,8 @@ const P = async () => f(r.token, "Realtime when user isn't authorized"), R = {
|
|
|
766
766
|
}),
|
|
767
767
|
[p.HEADLESS]: new N({
|
|
768
768
|
ttlMs: 2500,
|
|
769
|
-
createClient: (a) => new
|
|
770
|
-
signalingClient: new
|
|
769
|
+
createClient: (a) => new A({
|
|
770
|
+
signalingClient: new D(h),
|
|
771
771
|
getToken: P,
|
|
772
772
|
sessionType: p.HEADLESS,
|
|
773
773
|
receive: a
|
|
@@ -814,7 +814,7 @@ class Fe {
|
|
|
814
814
|
});
|
|
815
815
|
}
|
|
816
816
|
}
|
|
817
|
-
function
|
|
817
|
+
function b(a) {
|
|
818
818
|
return new Promise((e) => setTimeout(e, a));
|
|
819
819
|
}
|
|
820
820
|
const We = (a) => a !== void 0 && a.capabilities !== void 0 && a.capabilitySet !== void 0;
|
|
@@ -1000,7 +1000,7 @@ class Xe extends X {
|
|
|
1000
1000
|
i.set(d, !0), n.sendBinary(new Uint8Array([...c, ...t]));
|
|
1001
1001
|
const y = (/* @__PURE__ */ new Date()).getTime();
|
|
1002
1002
|
for (; (/* @__PURE__ */ new Date()).getTime() < y + o; )
|
|
1003
|
-
if (await
|
|
1003
|
+
if (await b(50), i.has(d)) {
|
|
1004
1004
|
const m = i.get(d);
|
|
1005
1005
|
if (m !== !0) {
|
|
1006
1006
|
i.delete(d);
|
|
@@ -1047,7 +1047,7 @@ class Ye extends X {
|
|
|
1047
1047
|
);
|
|
1048
1048
|
const c = (/* @__PURE__ */ new Date()).getTime();
|
|
1049
1049
|
for (; (/* @__PURE__ */ new Date()).getTime() < c + i; )
|
|
1050
|
-
if (await
|
|
1050
|
+
if (await b(50), n.has(o)) {
|
|
1051
1051
|
const d = n.get(o);
|
|
1052
1052
|
if (d !== !0) {
|
|
1053
1053
|
n.delete(o);
|
|
@@ -1571,7 +1571,7 @@ async function mt(a) {
|
|
|
1571
1571
|
}
|
|
1572
1572
|
)).json();
|
|
1573
1573
|
}
|
|
1574
|
-
class
|
|
1574
|
+
class E extends Y {
|
|
1575
1575
|
constructor(e, t, n, i) {
|
|
1576
1576
|
super(), this.id = e, this.name = t, this.organizationId = n, this.tags = i;
|
|
1577
1577
|
}
|
|
@@ -1622,6 +1622,23 @@ class S extends Y {
|
|
|
1622
1622
|
}
|
|
1623
1623
|
), (await t.json()).document;
|
|
1624
1624
|
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Asynchronously retrieves the device's agent version string
|
|
1627
|
+
*
|
|
1628
|
+
* @returns {Promise<string | undefined | null>} A promise that resolves to the agent version
|
|
1629
|
+
* @throws {Error} Throws an error if the device info cannot be fetched
|
|
1630
|
+
*/
|
|
1631
|
+
async getAgentVersion() {
|
|
1632
|
+
var n;
|
|
1633
|
+
const t = await (await fetch(`${h}/v1/admin/devices/${this.id}`, {
|
|
1634
|
+
method: "GET",
|
|
1635
|
+
headers: {
|
|
1636
|
+
"Content-Type": "application/json",
|
|
1637
|
+
Authorization: "Bearer " + r.token
|
|
1638
|
+
}
|
|
1639
|
+
})).json();
|
|
1640
|
+
return (n = t == null ? void 0 : t.state) == null ? void 0 : n.agentVersion;
|
|
1641
|
+
}
|
|
1625
1642
|
async getFileUrl(e) {
|
|
1626
1643
|
return (await (await fetch(`${h}/v1/admin/files/query`, {
|
|
1627
1644
|
method: "POST",
|
|
@@ -1667,19 +1684,19 @@ class S extends Y {
|
|
|
1667
1684
|
), m = async () => {
|
|
1668
1685
|
if ("isReady" in c)
|
|
1669
1686
|
for (; !c.isReady(); )
|
|
1670
|
-
this.assertNotCancelled(d), await
|
|
1687
|
+
this.assertNotCancelled(d), await b(100);
|
|
1671
1688
|
const w = await this.getRemoteDevicePeerId(c);
|
|
1672
1689
|
this.assertNotCancelled(d);
|
|
1673
1690
|
let v;
|
|
1674
|
-
for (let
|
|
1675
|
-
|
|
1691
|
+
for (let j = 0; j < i && (v = await c.connect(w), !v); j++)
|
|
1692
|
+
b(100), this.assertNotCancelled(d);
|
|
1676
1693
|
if (!v)
|
|
1677
1694
|
throw new Error(
|
|
1678
1695
|
`Session could not be created: exhausted ${i} retries`
|
|
1679
1696
|
);
|
|
1680
1697
|
let C = 0;
|
|
1681
1698
|
for (; !d && c.getConnectionStatus(w) !== "connected"; )
|
|
1682
|
-
await
|
|
1699
|
+
await b(100), C += 1;
|
|
1683
1700
|
return this.assertNotCancelled(d), console.debug(
|
|
1684
1701
|
`${(/* @__PURE__ */ new Date()).toISOString()} :: Connection completed after ${C} retries`
|
|
1685
1702
|
), w;
|
|
@@ -1906,7 +1923,7 @@ class S extends Y {
|
|
|
1906
1923
|
return e.scope.deviceIds = [this.id], await ee(e, t);
|
|
1907
1924
|
}
|
|
1908
1925
|
}
|
|
1909
|
-
s(
|
|
1926
|
+
s(E, "createDevice", dt), s(E, "patchDevice", ht), s(E, "getDevicesData", lt), s(E, "queryDevicesData", ut), s(E, "disableDevice", mt);
|
|
1910
1927
|
class ft extends Y {
|
|
1911
1928
|
constructor(t) {
|
|
1912
1929
|
super();
|
|
@@ -1947,8 +1964,8 @@ class ft extends Y {
|
|
|
1947
1964
|
if (m.length > 0) {
|
|
1948
1965
|
const v = JSON.parse(m);
|
|
1949
1966
|
if ((w = v.result) != null && w.datapoint) {
|
|
1950
|
-
const C = v.result.datapoint,
|
|
1951
|
-
delete C.stream, this.streamTelemetry[
|
|
1967
|
+
const C = v.result.datapoint, j = C.stream;
|
|
1968
|
+
delete C.stream, this.streamTelemetry[j] = C;
|
|
1952
1969
|
}
|
|
1953
1970
|
}
|
|
1954
1971
|
});
|
|
@@ -1971,13 +1988,13 @@ class ft extends Y {
|
|
|
1971
1988
|
this.rtcClient && console.warn(
|
|
1972
1989
|
"overwriting existing rtcClient due to missing connectionMonitorInterval"
|
|
1973
1990
|
);
|
|
1974
|
-
const n = new
|
|
1991
|
+
const n = new A({
|
|
1975
1992
|
lanOnlyMode: !0,
|
|
1976
1993
|
receive: this.handleMessage,
|
|
1977
1994
|
sessionType: t
|
|
1978
1995
|
});
|
|
1979
1996
|
for (await n.connectLan(this.peerUrl); n.getConnectionStatus(this.peerUrl) !== "connected"; )
|
|
1980
|
-
await
|
|
1997
|
+
await b(100);
|
|
1981
1998
|
this.rtcClient = n, this.initConnectionMonitoring();
|
|
1982
1999
|
}
|
|
1983
2000
|
initConnectionMonitoring() {
|
|
@@ -2114,7 +2131,7 @@ async function se(a) {
|
|
|
2114
2131
|
Authorization: "Bearer " + r.token
|
|
2115
2132
|
}
|
|
2116
2133
|
})).json()).items.map(
|
|
2117
|
-
(n) => new
|
|
2134
|
+
(n) => new E(n.id, n.name, n.organizationId, n.tags)
|
|
2118
2135
|
);
|
|
2119
2136
|
}
|
|
2120
2137
|
async function Ct() {
|
|
@@ -2140,7 +2157,7 @@ async function Ct() {
|
|
|
2140
2157
|
type: "default"
|
|
2141
2158
|
});
|
|
2142
2159
|
}
|
|
2143
|
-
async function
|
|
2160
|
+
async function Et(a) {
|
|
2144
2161
|
if (!r.token)
|
|
2145
2162
|
throw new Error("Not authenticated");
|
|
2146
2163
|
const t = await (await fetch(`${h}/v1/admin/devices/${a}`, {
|
|
@@ -2150,7 +2167,7 @@ async function St(a) {
|
|
|
2150
2167
|
Authorization: "Bearer " + r.token
|
|
2151
2168
|
}
|
|
2152
2169
|
})).json(), n = t.name;
|
|
2153
|
-
return new
|
|
2170
|
+
return new E(a, n, t.organizationId, t.tags);
|
|
2154
2171
|
}
|
|
2155
2172
|
async function M() {
|
|
2156
2173
|
if (!r.token)
|
|
@@ -2164,7 +2181,7 @@ async function M() {
|
|
|
2164
2181
|
}
|
|
2165
2182
|
})).json();
|
|
2166
2183
|
return e.items, e.items.map(
|
|
2167
|
-
(t) => new
|
|
2184
|
+
(t) => new E(
|
|
2168
2185
|
t.id,
|
|
2169
2186
|
t.name,
|
|
2170
2187
|
t.organizationId,
|
|
@@ -2172,7 +2189,7 @@ async function M() {
|
|
|
2172
2189
|
)
|
|
2173
2190
|
);
|
|
2174
2191
|
}
|
|
2175
|
-
async function
|
|
2192
|
+
async function St(a) {
|
|
2176
2193
|
if (!r.token)
|
|
2177
2194
|
throw new Error("Not authenticated");
|
|
2178
2195
|
return (await (await fetch(
|
|
@@ -2223,7 +2240,7 @@ async function Rt(a) {
|
|
|
2223
2240
|
}
|
|
2224
2241
|
})).json()).items;
|
|
2225
2242
|
}
|
|
2226
|
-
async function
|
|
2243
|
+
async function At() {
|
|
2227
2244
|
if (!r.token)
|
|
2228
2245
|
throw new Error("Not authenticated");
|
|
2229
2246
|
return (await (await fetch(
|
|
@@ -2237,7 +2254,7 @@ async function bt() {
|
|
|
2237
2254
|
}
|
|
2238
2255
|
)).json()).items;
|
|
2239
2256
|
}
|
|
2240
|
-
async function
|
|
2257
|
+
async function bt(...a) {
|
|
2241
2258
|
const e = a.flat().filter((i) => !!i);
|
|
2242
2259
|
return e.length === 0 ? [] : (await (await fetch(
|
|
2243
2260
|
`${h}/v1/queries/stream-current-value`,
|
|
@@ -2253,7 +2270,7 @@ async function At(...a) {
|
|
|
2253
2270
|
}
|
|
2254
2271
|
)).json()).items;
|
|
2255
2272
|
}
|
|
2256
|
-
async function
|
|
2273
|
+
async function jt() {
|
|
2257
2274
|
if (!r.token)
|
|
2258
2275
|
throw new Error("Not authenticated");
|
|
2259
2276
|
const t = (await (await fetch(`${h}/v1/queries/online-devices`, {
|
|
@@ -2265,7 +2282,7 @@ async function Dt() {
|
|
|
2265
2282
|
})).json()).items;
|
|
2266
2283
|
return (await M()).filter((i) => t.includes(i.id));
|
|
2267
2284
|
}
|
|
2268
|
-
async function
|
|
2285
|
+
async function Dt() {
|
|
2269
2286
|
if (!r.token)
|
|
2270
2287
|
throw new Error("Not authenticated");
|
|
2271
2288
|
const t = (await (await fetch(`${h}/v1/signaling/peers`, {
|
|
@@ -2435,14 +2452,14 @@ async function Ut(a, e) {
|
|
|
2435
2452
|
}
|
|
2436
2453
|
)).json();
|
|
2437
2454
|
}
|
|
2438
|
-
const
|
|
2455
|
+
const S = class {
|
|
2439
2456
|
static async setDefaultDevice(e) {
|
|
2440
|
-
|
|
2457
|
+
S.defaultDeviceId = e;
|
|
2441
2458
|
}
|
|
2442
2459
|
static async getCurrentDevice() {
|
|
2443
2460
|
if (!r.token)
|
|
2444
2461
|
throw new Error("Not authenticated");
|
|
2445
|
-
if (!
|
|
2462
|
+
if (!S.defaultDeviceId)
|
|
2446
2463
|
throw new Error("No known default device");
|
|
2447
2464
|
const n = (await (await fetch(
|
|
2448
2465
|
`${h}/v1/admin/device-details/query`,
|
|
@@ -2454,26 +2471,26 @@ const E = class {
|
|
|
2454
2471
|
}
|
|
2455
2472
|
}
|
|
2456
2473
|
)).json()).items.find(
|
|
2457
|
-
(c) => c.id ===
|
|
2458
|
-
), i = n.name, o = new
|
|
2459
|
-
|
|
2474
|
+
(c) => c.id === S.defaultDeviceId
|
|
2475
|
+
), i = n.name, o = new E(
|
|
2476
|
+
S.defaultDeviceId,
|
|
2460
2477
|
i,
|
|
2461
2478
|
f(r.currentOrganization),
|
|
2462
2479
|
n.tags
|
|
2463
2480
|
);
|
|
2464
|
-
return
|
|
2481
|
+
return S.knownContext.push(new WeakRef(o)), o;
|
|
2465
2482
|
}
|
|
2466
2483
|
static async getPeerDevice(e) {
|
|
2467
2484
|
const t = new ft(e);
|
|
2468
2485
|
return t.id = await t.getDeviceId(), t;
|
|
2469
2486
|
}
|
|
2470
2487
|
static async getDevice(e) {
|
|
2471
|
-
const t = await
|
|
2472
|
-
return
|
|
2488
|
+
const t = await Et(e);
|
|
2489
|
+
return S.knownContext.push(new WeakRef(t)), t;
|
|
2473
2490
|
}
|
|
2474
2491
|
};
|
|
2475
|
-
let l =
|
|
2476
|
-
s(l, "defaultDeviceId"), s(l, "knownContext", []), s(l, "createFleet", Mt), s(l, "listFleets", It), s(l, "getFleet", Ot), s(l, "patchFleet", _t), s(l, "deleteFleet", yt), s(l, "addDeviceToFleet", wt), s(l, "getFleetDevices", Rt), s(l, "aggregateTelemetry", pt), s(l, "createShareLink", ee), s(l, "eventsCounter", ne), s(l, "getAnalyticStreams", gt), s(l, "getAnalyticsModules", vt), s(l, "getAnalyticsRows", Tt), s(l, "getAnnotationCount", B), s(l, "getAnnotationCountByIntervals", ae), s(l, "getCurrentGroup", Ct), s(l, "getDevices", M), s(l, "getEvent",
|
|
2492
|
+
let l = S;
|
|
2493
|
+
s(l, "defaultDeviceId"), s(l, "knownContext", []), s(l, "createFleet", Mt), s(l, "listFleets", It), s(l, "getFleet", Ot), s(l, "patchFleet", _t), s(l, "deleteFleet", yt), s(l, "addDeviceToFleet", wt), s(l, "getFleetDevices", Rt), s(l, "aggregateTelemetry", pt), s(l, "createShareLink", ee), s(l, "eventsCounter", ne), s(l, "getAnalyticStreams", gt), s(l, "getAnalyticsModules", vt), s(l, "getAnalyticsRows", Tt), s(l, "getAnnotationCount", B), s(l, "getAnnotationCountByIntervals", ae), s(l, "getCurrentGroup", Ct), s(l, "getDevices", M), s(l, "getEvent", St), s(l, "getFileUrl", kt), s(l, "getInterventions", At), s(l, "getLatestTelemetry", bt), s(l, "getOnlineDevices", jt), s(l, "getPeers", re), s(l, "getRealtimeDevices", Dt), s(l, "getRealtimeSessions", oe), s(l, "getStreams", Nt), s(l, "getTaskReportRows", Pt), s(l, "getTaskReportTables", Lt), s(l, "getTelemetry", ie), s(l, "getViews", Z), s(l, "patchStream", $t), s(l, "patchView", zt), s(l, "queryAnalytics", Bt), s(l, "queryDevices", se), s(l, "queryEvents", z), s(l, "queryTelemetry", _), s(l, "getAllEventTriggerGroup", qt), s(l, "getEventTriggerGroup", xt), s(l, "patchEventTriggergroup", Ut);
|
|
2477
2494
|
class an {
|
|
2478
2495
|
static async set(e, t, n) {
|
|
2479
2496
|
try {
|
|
@@ -2883,7 +2900,7 @@ export {
|
|
|
2883
2900
|
Xe as BinaryRequestDataChannel,
|
|
2884
2901
|
Fe as CaptureStream,
|
|
2885
2902
|
Ke as DataChannel,
|
|
2886
|
-
|
|
2903
|
+
E as Device,
|
|
2887
2904
|
l as Fleet,
|
|
2888
2905
|
an as KeyValue,
|
|
2889
2906
|
Qe as Manipulator,
|