@7365admin1/core 3.84.0 → 3.86.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/index.mjs CHANGED
@@ -19668,8 +19668,8 @@ function useDahuaService() {
19668
19668
  }
19669
19669
  }
19670
19670
  }
19671
- async function handleBuffer(chunk, onDetected2) {
19672
- partialBuffer = Buffer.concat([partialBuffer, chunk]);
19671
+ async function handleBuffer(chunk2, onDetected2) {
19672
+ partialBuffer = Buffer.concat([partialBuffer, chunk2]);
19673
19673
  while (true) {
19674
19674
  const boundaryIndex = partialBuffer.indexOf(Buffer.from(boundary));
19675
19675
  if (boundaryIndex === -1)
@@ -19920,12 +19920,12 @@ function useDahuaService() {
19920
19920
  resolve("aborted");
19921
19921
  };
19922
19922
  signal.addEventListener("abort", onAbort, { once: true });
19923
- response.res.on("data", (chunk) => {
19923
+ response.res.on("data", (chunk2) => {
19924
19924
  if (signal.aborted) {
19925
19925
  response.res.destroy();
19926
19926
  return;
19927
19927
  }
19928
- bufferQueue.enqueue(chunk);
19928
+ bufferQueue.enqueue(chunk2);
19929
19929
  });
19930
19930
  response.res.on("end", () => {
19931
19931
  signal.removeEventListener("abort", onAbort);
@@ -22529,10 +22529,10 @@ function buildAdapter(params) {
22529
22529
  return;
22530
22530
  let buffered = "";
22531
22531
  const decoder = new TextDecoder();
22532
- for await (const chunk of body) {
22532
+ for await (const chunk2 of body) {
22533
22533
  if (params2.signal.aborted)
22534
22534
  return;
22535
- buffered += decoder.decode(chunk, { stream: true });
22535
+ buffered += decoder.decode(chunk2, { stream: true });
22536
22536
  let at2 = buffered.indexOf(marker);
22537
22537
  while (at2 >= 0) {
22538
22538
  const next = buffered.indexOf(marker, at2 + marker.length);
@@ -22844,16 +22844,16 @@ function useCameraViewService() {
22844
22844
  () => finish(new Error("camera-timeout")),
22845
22845
  CAMERA_RTSP_TIMEOUT_MS
22846
22846
  );
22847
- child.stdout.on("data", (chunk) => {
22848
- bytes += chunk.length;
22847
+ child.stdout.on("data", (chunk2) => {
22848
+ bytes += chunk2.length;
22849
22849
  if (bytes > CAMERA_SNAPSHOT_MAX_BYTES) {
22850
22850
  finish(new Error("camera-picture-too-large"));
22851
22851
  return;
22852
22852
  }
22853
- chunks.push(chunk);
22853
+ chunks.push(chunk2);
22854
22854
  });
22855
- child.stderr?.on("data", (chunk) => {
22856
- const text2 = carry + chunk.toString("latin1");
22855
+ child.stderr?.on("data", (chunk2) => {
22856
+ const text2 = carry + chunk2.toString("latin1");
22857
22857
  if (/401\s*unauthorized|\bunauthorized\b/i.test(text2))
22858
22858
  rejected = true;
22859
22859
  carry = text2.slice(-32);
@@ -32276,8 +32276,8 @@ function useOccurrenceBookRepo() {
32276
32276
  const chunkSize = 500;
32277
32277
  let totalInserted = 0;
32278
32278
  for (let i = 0; i < payloads.length; i += chunkSize) {
32279
- const chunk = payloads.slice(i, i + chunkSize);
32280
- const operations = chunk.map((item) => ({
32279
+ const chunk2 = payloads.slice(i, i + chunkSize);
32280
+ const operations = chunk2.map((item) => ({
32281
32281
  insertOne: {
32282
32282
  document: {
32283
32283
  ...item,
@@ -51702,9 +51702,9 @@ function useVisitorTransactionService() {
51702
51702
  expectedCheckIn
51703
51703
  } = value;
51704
51704
  for (let i = 0; i < value.members.length; i += chunkSize) {
51705
- const chunk = value.members.slice(i, i + chunkSize);
51705
+ const chunk2 = value.members.slice(i, i + chunkSize);
51706
51706
  await Promise.all(
51707
- chunk.map(async (member) => {
51707
+ chunk2.map(async (member) => {
51708
51708
  const clonedMember = structuredClone(member);
51709
51709
  const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
51710
51710
  if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
@@ -51946,9 +51946,9 @@ function useVisitorTransactionService() {
51946
51946
  unitName
51947
51947
  } = value;
51948
51948
  for (let i = 0; i < value.members.length; i += chunkSize) {
51949
- const chunk = value.members.slice(i, i + chunkSize);
51949
+ const chunk2 = value.members.slice(i, i + chunkSize);
51950
51950
  await Promise.all(
51951
- chunk.map(async (member) => {
51951
+ chunk2.map(async (member) => {
51952
51952
  const clonedMember = structuredClone(member);
51953
51953
  const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
51954
51954
  if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
@@ -56409,8 +56409,8 @@ function useAccessManagementSvc() {
56409
56409
  return new Promise((resolve, reject) => {
56410
56410
  const fileStream = Readable.from(bufferFile);
56411
56411
  let fileBuffer = Buffer.alloc(0);
56412
- fileStream.on("data", (chunk) => {
56413
- fileBuffer = Buffer.concat([fileBuffer, chunk]);
56412
+ fileStream.on("data", (chunk2) => {
56413
+ fileBuffer = Buffer.concat([fileBuffer, chunk2]);
56414
56414
  });
56415
56415
  fileStream.on("end", () => {
56416
56416
  try {
@@ -59266,10 +59266,10 @@ function parseRobotCommand(body) {
59266
59266
  function createSseParser() {
59267
59267
  let buffer = "";
59268
59268
  let dataLines = [];
59269
- function push(chunk) {
59270
- if (!chunk)
59269
+ function push(chunk2) {
59270
+ if (!chunk2)
59271
59271
  return { events: [], sawActivity: false };
59272
- buffer += chunk;
59272
+ buffer += chunk2;
59273
59273
  const events = [];
59274
59274
  buffer = buffer.replace(/\r\n|\r/g, "\n");
59275
59275
  const lines = buffer.split("\n");
@@ -59535,26 +59535,44 @@ function commandSupported(capabilities, kind) {
59535
59535
  return false;
59536
59536
  return supports(capabilities, needed);
59537
59537
  }
59538
- var adapters = /* @__PURE__ */ new Map();
59539
- function registerRobotAdapter(adapter) {
59540
- if (!adapter?.brand)
59541
- throw new Error("A robot adapter must name its brand.");
59542
- adapters.set(adapter.brand, adapter);
59538
+ var brands = /* @__PURE__ */ new Map();
59539
+ function registerRobotBrand(brand) {
59540
+ if (!brand?.brand)
59541
+ throw new Error("A robot brand must name itself.");
59542
+ if (typeof brand.create !== "function") {
59543
+ throw new Error(`Robot brand "${brand.brand}" has no way to build an adapter.`);
59544
+ }
59545
+ brands.set(brand.brand, brand);
59543
59546
  }
59544
- function robotAdapterFor(brand) {
59547
+ function robotBrandFor(brand) {
59545
59548
  if (typeof brand !== "string" || !brand)
59546
59549
  return null;
59547
- return adapters.get(brand) ?? null;
59550
+ return brands.get(brand) ?? null;
59551
+ }
59552
+ function adapterForRobot(robot) {
59553
+ const brandName = (robot?.brand ?? "").trim();
59554
+ const brand = robotBrandFor(brandName);
59555
+ if (!brand) {
59556
+ throw new Error(
59557
+ brandName ? `No robot adapter is registered for the brand "${brandName}".` : `Robot "${robot?.name ?? "unknown"}" does not say which brand it is.`
59558
+ );
59559
+ }
59560
+ const appid = (robot.appKey ?? "").trim();
59561
+ const secret = (robot.appToken ?? "").trim();
59562
+ if (!appid || !secret) {
59563
+ throw new Error(`Robot "${robot?.name ?? brandName}" has no cloud credentials saved.`);
59564
+ }
59565
+ return brand.create({ appid, secret });
59548
59566
  }
59549
59567
  function registeredRobotBrands() {
59550
- return [...adapters.values()].map((a) => ({
59551
- brand: a.brand,
59552
- displayName: a.displayName,
59553
- tier: robotTier(a.capabilities)
59568
+ return [...brands.values()].map((b) => ({
59569
+ brand: b.brand,
59570
+ displayName: b.displayName,
59571
+ tier: robotTier(b.capabilities)
59554
59572
  }));
59555
59573
  }
59556
- function clearRobotAdapters() {
59557
- adapters.clear();
59574
+ function clearRobotBrands() {
59575
+ brands.clear();
59558
59576
  }
59559
59577
 
59560
59578
  // src/utils/lift-contract.util.ts
@@ -60010,122 +60028,6 @@ function useRobotCloudService(credentials, options = {}) {
60010
60028
  };
60011
60029
  }
60012
60030
 
60013
- // src/services/robot-fleet.service.ts
60014
- function credentialsOf(robot) {
60015
- const appid = (robot.appKey ?? "").trim();
60016
- const secret = (robot.appToken ?? "").trim();
60017
- if (!appid || !secret) {
60018
- throw new BadRequestError134(
60019
- `Robot "${robot.name}" has no cloud credentials saved.`
60020
- );
60021
- }
60022
- return { appid, secret };
60023
- }
60024
- function useRobotFleetService(deps) {
60025
- const cloudFor = deps?.cloudFor ?? useRobotCloudService;
60026
- function clientFor(robot) {
60027
- return cloudFor(credentialsOf(robot));
60028
- }
60029
- async function resolveRobotSn(robot) {
60030
- const stored = (robot.robotSn ?? "").trim();
60031
- if (stored)
60032
- return stored;
60033
- const fleet = await clientFor(robot).getRobots();
60034
- if (fleet.length === 0) {
60035
- throw new NotFoundError43(
60036
- `Robot "${robot.name}": the cloud reports no robots for these credentials.`
60037
- );
60038
- }
60039
- if (fleet.length > 1) {
60040
- throw new BadRequestError134(
60041
- `Robot "${robot.name}": these credentials cover ${fleet.length} robots, so the serial cannot be determined automatically. Set it explicitly.`
60042
- );
60043
- }
60044
- const robotSn = fleet[0].robotSn;
60045
- if (!robotSn) {
60046
- throw new BadRequestError134(
60047
- `Robot "${robot.name}": the cloud returned a robot with no serial.`
60048
- );
60049
- }
60050
- const id = robot._id ? String(robot._id) : "";
60051
- if (id && deps?.saveRobotSn) {
60052
- await deps.saveRobotSn(id, robotSn).catch(() => void 0);
60053
- }
60054
- return robotSn;
60055
- }
60056
- async function getFleetRobot(robot) {
60057
- const robotSn = await resolveRobotSn(robot);
60058
- const fleet = await clientFor(robot).getRobots();
60059
- const match = fleet.find(
60060
- (r) => r.robotSn === robotSn
60061
- );
60062
- if (!match) {
60063
- return {
60064
- id: robot._id ? String(robot._id) : "",
60065
- name: robot.name,
60066
- robotSn,
60067
- statusFlag: null,
60068
- statusName: "Not found in the robot cloud",
60069
- mode: "UNAVAILABLE",
60070
- online: false,
60071
- dispatchable: false
60072
- };
60073
- }
60074
- const mode = robotModeFromList(match);
60075
- return {
60076
- id: robot._id ? String(robot._id) : "",
60077
- name: robot.name,
60078
- robotSn,
60079
- statusFlag: typeof match.statusFlag === "number" ? match.statusFlag : null,
60080
- statusName: match.statusName ?? "",
60081
- mode,
60082
- online: match.onlineFlag !== false,
60083
- dispatchable: mode === "IDLE" || mode === "CHARGED"
60084
- };
60085
- }
60086
- async function getMaps(robot) {
60087
- const robotSn = await resolveRobotSn(robot);
60088
- return clientFor(robot).getMaps(robotSn);
60089
- }
60090
- async function getMapImages(robot, fileKeys) {
60091
- const robotSn = await resolveRobotSn(robot);
60092
- return clientFor(robot).getFileUrls(robotSn, fileKeys);
60093
- }
60094
- async function dispatch(robot, command) {
60095
- const robotSn = await resolveRobotSn(robot);
60096
- const cloud = clientFor(robot);
60097
- if (command.kind === "return") {
60098
- return cloud.returnToBase(robotSn);
60099
- }
60100
- const state = await getFleetRobot(robot);
60101
- if (!state.dispatchable) {
60102
- throw new BadRequestError134(
60103
- `Robot "${robot.name}" is ${state.statusName || state.mode} and cannot take a new job.`
60104
- );
60105
- }
60106
- switch (command.kind) {
60107
- case "full":
60108
- return cloud.startFullCleaning(robotSn, command.options);
60109
- case "area":
60110
- return cloud.startAreaCleaning(robotSn, command.areas, command.options);
60111
- case "spot":
60112
- return cloud.startSpotCleaning(robotSn, command.points, command.options);
60113
- }
60114
- }
60115
- async function getPlans(robot) {
60116
- const robotSn = await resolveRobotSn(robot);
60117
- return clientFor(robot).getPlans(robotSn);
60118
- }
60119
- return {
60120
- resolveRobotSn,
60121
- getFleetRobot,
60122
- getMaps,
60123
- getMapImages,
60124
- dispatch,
60125
- getPlans
60126
- };
60127
- }
60128
-
60129
60031
  // src/services/ounitech.adapter.ts
60130
60032
  var OUNITECH_BRAND = "ounitech";
60131
60033
  var OUNITECH_CAPABILITIES = {
@@ -60229,24 +60131,550 @@ function createOunitechAdapter(credentials, options) {
60229
60131
  async getMaps(robotSn) {
60230
60132
  return cloud.getMaps(robotSn);
60231
60133
  },
60134
+ async getMapImages(robotSn, fileKeys) {
60135
+ return cloud.getFileUrls(robotSn, fileKeys);
60136
+ },
60232
60137
  async getPlans(robotSn) {
60233
60138
  return cloud.getPlans(robotSn);
60234
60139
  }
60235
60140
  };
60236
60141
  }
60237
60142
  function registerOunitechBrand() {
60238
- const unconfigured = () => {
60239
- throw new Error(
60240
- "No Ounitech credentials for this robot. Add the app key and token on the robot's record."
60241
- );
60242
- };
60243
- registerRobotAdapter({
60143
+ registerRobotBrand({
60244
60144
  brand: OUNITECH_BRAND,
60245
60145
  displayName: "Ounitech (Q3-G / Q3-W)",
60246
60146
  capabilities: OUNITECH_CAPABILITIES,
60247
- listRobots: async () => unconfigured(),
60248
- getRobot: async () => unconfigured()
60147
+ create: (credentials) => createOunitechAdapter(credentials)
60148
+ });
60149
+ }
60150
+ registerOunitechBrand();
60151
+
60152
+ // src/utils/png-encoder.util.ts
60153
+ import { deflateSync } from "zlib";
60154
+ function crc32(buffer) {
60155
+ let crc = ~0;
60156
+ for (let i = 0; i < buffer.length; i++) {
60157
+ crc ^= buffer[i];
60158
+ for (let bit = 0; bit < 8; bit++) {
60159
+ crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
60160
+ }
60161
+ }
60162
+ return ~crc >>> 0;
60163
+ }
60164
+ function chunk(type, data) {
60165
+ const length = Buffer.alloc(4);
60166
+ length.writeUInt32BE(data.length, 0);
60167
+ const typed = Buffer.concat([Buffer.from(type, "ascii"), data]);
60168
+ const crc = Buffer.alloc(4);
60169
+ crc.writeUInt32BE(crc32(typed), 0);
60170
+ return Buffer.concat([length, typed, crc]);
60171
+ }
60172
+ function encodeGreyscalePng(pixels, width, height) {
60173
+ if (width <= 0 || height <= 0)
60174
+ throw new Error("A PNG needs a positive size.");
60175
+ if (pixels.length < width * height) {
60176
+ throw new Error("A PNG needs one byte per pixel.");
60177
+ }
60178
+ const raw = Buffer.alloc((width + 1) * height);
60179
+ for (let y = 0; y < height; y++) {
60180
+ raw[y * (width + 1)] = 0;
60181
+ for (let x = 0; x < width; x++) {
60182
+ raw[y * (width + 1) + 1 + x] = pixels[y * width + x];
60183
+ }
60184
+ }
60185
+ const ihdr = Buffer.alloc(13);
60186
+ ihdr.writeUInt32BE(width, 0);
60187
+ ihdr.writeUInt32BE(height, 4);
60188
+ ihdr[8] = 8;
60189
+ ihdr[9] = 0;
60190
+ ihdr[10] = 0;
60191
+ ihdr[11] = 0;
60192
+ ihdr[12] = 0;
60193
+ return Buffer.concat([
60194
+ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
60195
+ chunk("IHDR", ihdr),
60196
+ chunk("IDAT", deflateSync(raw)),
60197
+ chunk("IEND", Buffer.alloc(0))
60198
+ ]);
60199
+ }
60200
+ function greyscalePngDataUri(pixels, width, height) {
60201
+ return `data:image/png;base64,${encodeGreyscalePng(pixels, width, height).toString("base64")}`;
60202
+ }
60203
+
60204
+ // src/services/demo.adapter.ts
60205
+ var DEMO_BRAND = "demo";
60206
+ var DEMO_CAPABILITIES = {
60207
+ readLocation: true,
60208
+ navigateToPoint: true,
60209
+ pauseResume: true,
60210
+ pathAndEta: true,
60211
+ battery: true,
60212
+ sendToDock: true,
60213
+ relocalize: true,
60214
+ startProcess: true,
60215
+ pauseProcess: true,
60216
+ processCompletion: true,
60217
+ floorReporting: true,
60218
+ multiFloor: true
60219
+ };
60220
+ var SCALE = 0.05;
60221
+ var PLAN_W = 260;
60222
+ var PLAN_H = 180;
60223
+ var DEMO_ROBOTS = [
60224
+ { robotSn: "DEMO-0001", name: "Demo Cleaner 01", model: "Demo S1", floorNo: 1, phase: 0 },
60225
+ { robotSn: "DEMO-0002", name: "Demo Cleaner 02", model: "Demo S1", floorNo: 1, phase: 0.37 },
60226
+ { robotSn: "DEMO-0003", name: "Demo Scrubber 03", model: "Demo X2", floorNo: 2, phase: 0.68 }
60227
+ ];
60228
+ var commanded = /* @__PURE__ */ new Map();
60229
+ function demoPlanPixels(floorNo) {
60230
+ const px = new Uint8Array(PLAN_W * PLAN_H).fill(255);
60231
+ const set = (x, y) => {
60232
+ if (x >= 0 && x < PLAN_W && y >= 0 && y < PLAN_H)
60233
+ px[y * PLAN_W + x] = 0;
60234
+ };
60235
+ for (let x = 0; x < PLAN_W; x++) {
60236
+ for (const t of [0, 1]) {
60237
+ set(x, t);
60238
+ set(x, PLAN_H - 1 - t);
60239
+ }
60240
+ }
60241
+ for (let y = 0; y < PLAN_H; y++) {
60242
+ for (const t of [0, 1]) {
60243
+ set(t, y);
60244
+ set(PLAN_W - 1 - t, y);
60245
+ }
60246
+ }
60247
+ const wallX = Math.floor(PLAN_W * (floorNo === 1 ? 0.45 : 0.6));
60248
+ for (let y = 2; y < PLAN_H - 2; y++) {
60249
+ if (y > PLAN_H * 0.45 && y < PLAN_H * 0.6)
60250
+ continue;
60251
+ set(wallX, y);
60252
+ set(wallX + 1, y);
60253
+ }
60254
+ if (floorNo === 2) {
60255
+ const wallY = Math.floor(PLAN_H * 0.5);
60256
+ for (let x = 2; x < wallX; x++) {
60257
+ if (x > PLAN_W * 0.15 && x < PLAN_W * 0.25)
60258
+ continue;
60259
+ set(x, wallY);
60260
+ set(x, wallY + 1);
60261
+ }
60262
+ }
60263
+ return px;
60264
+ }
60265
+ function demoPosition(robot, now) {
60266
+ const t = (now / 9e4 + robot.phase) % 1;
60267
+ const angle = t * Math.PI * 2;
60268
+ const cx = PLAN_W * 0.5 * SCALE;
60269
+ const cy = PLAN_H * 0.5 * SCALE;
60270
+ const rx = PLAN_W * 0.28 * SCALE;
60271
+ const ry = PLAN_H * 0.28 * SCALE;
60272
+ return {
60273
+ x: cx + Math.cos(angle) * rx,
60274
+ y: cy + Math.sin(angle) * ry,
60275
+ // Facing along the direction of travel, which is the tangent.
60276
+ theta: angle + Math.PI / 2
60277
+ };
60278
+ }
60279
+ function demoBattery(robot, now) {
60280
+ const t = (now / 36e5 + robot.phase) % 1;
60281
+ return Math.round(t < 0.8 ? 100 - t / 0.8 * 75 : 25 + (t - 0.8) / 0.2 * 75);
60282
+ }
60283
+ function demoState(robot, now) {
60284
+ const order = commanded.get(robot.robotSn);
60285
+ const active = order && order.until > now ? order.mode : null;
60286
+ const battery = demoBattery(robot, now);
60287
+ const mode = battery <= 25 ? "CHARGING_CYCLE" : active ?? "IDLE";
60288
+ return {
60289
+ robotSn: robot.robotSn,
60290
+ name: robot.name,
60291
+ model: robot.model,
60292
+ online: true,
60293
+ mode,
60294
+ statusName: mode === "WORKING" ? "Cleaning" : mode === "CHARGING_CYCLE" ? "Charging" : "Idle",
60295
+ batteryPercent: battery,
60296
+ floorNo: robot.floorNo,
60297
+ // The demo DOES know its floor - unlike Ounitech today - so the map can
60298
+ // draw it. That contrast is part of what the demo shows.
60299
+ floorKnown: true,
60300
+ position: demoPosition(robot, now)
60301
+ };
60302
+ }
60303
+ function createDemoAdapter(options) {
60304
+ const now = options?.now ?? Date.now;
60305
+ return {
60306
+ brand: DEMO_BRAND,
60307
+ displayName: "Demo robots (simulated)",
60308
+ capabilities: DEMO_CAPABILITIES,
60309
+ async listRobots() {
60310
+ const at2 = now();
60311
+ return DEMO_ROBOTS.map((r) => demoState(r, at2));
60312
+ },
60313
+ async getRobot(robotSn) {
60314
+ const robot = DEMO_ROBOTS.find((r) => r.robotSn === robotSn);
60315
+ return robot ? demoState(robot, now()) : null;
60316
+ },
60317
+ async sendCommand(robotSn, command) {
60318
+ const robot = DEMO_ROBOTS.find((r) => r.robotSn === robotSn);
60319
+ if (!robot)
60320
+ throw new Error(`No demo robot with serial ${robotSn}.`);
60321
+ const at2 = now();
60322
+ switch (command.kind) {
60323
+ case "full":
60324
+ case "area":
60325
+ case "spot":
60326
+ commanded.set(robotSn, { mode: "WORKING", until: at2 + 10 * 6e4 });
60327
+ return;
60328
+ case "return":
60329
+ commanded.set(robotSn, { mode: "CHARGING_CYCLE", until: at2 + 5 * 6e4 });
60330
+ return;
60331
+ case "pause":
60332
+ commanded.set(robotSn, { mode: "PAUSED", until: at2 + 10 * 6e4 });
60333
+ return;
60334
+ case "resume":
60335
+ commanded.set(robotSn, { mode: "WORKING", until: at2 + 10 * 6e4 });
60336
+ return;
60337
+ case "cancel":
60338
+ commanded.delete(robotSn);
60339
+ return;
60340
+ case "navigate":
60341
+ case "relocalize":
60342
+ commanded.set(robotSn, { mode: "WORKING", until: at2 + 6e4 });
60343
+ return;
60344
+ }
60345
+ },
60346
+ async getMaps() {
60347
+ return DEMO_ROBOTS.map((r) => r.floorNo).filter((floorNo, i, all) => all.indexOf(floorNo) === i).sort((a, b) => a - b).map((floorNo) => ({
60348
+ floorNo,
60349
+ mapName: `Demo Building L${floorNo}`,
60350
+ backgroundFileKey: `demo/L${floorNo}.png`,
60351
+ partitionFileKey: `demo/L${floorNo}.png`,
60352
+ originX: 0,
60353
+ originY: 0,
60354
+ resolution: SCALE,
60355
+ totalArea: Math.round(PLAN_W * PLAN_H * SCALE * SCALE),
60356
+ baseX: 12,
60357
+ baseY: 3,
60358
+ baseTheta: 0,
60359
+ createTime: (/* @__PURE__ */ new Date(0)).toISOString(),
60360
+ areaList: [
60361
+ { areaName: `L${floorNo}-LOBBY`, rgba: "FF0B0B83", x: 60, y: 60 },
60362
+ { areaName: `L${floorNo}-OFFICE`, rgba: "B7CAEFFF", x: 190, y: 60 },
60363
+ { areaName: `L${floorNo}-CORRIDOR`, rgba: "BA144BFF", x: 120, y: 140 }
60364
+ ]
60365
+ }));
60366
+ },
60367
+ /**
60368
+ * Data URIs rather than signed links.
60369
+ *
60370
+ * A demo must work with no storage account and no network, and a `data:`
60371
+ * URI is same-origin so the canvas is not tainted — which matters, because
60372
+ * the layout is built by reading these pixels back.
60373
+ */
60374
+ async getMapImages(_robotSn, fileKeys) {
60375
+ return fileKeys.map((fileKey) => {
60376
+ const floorNo = fileKey.includes("L2") ? 2 : 1;
60377
+ return {
60378
+ fileKey,
60379
+ url: greyscalePngDataUri(demoPlanPixels(floorNo), PLAN_W, PLAN_H)
60380
+ };
60381
+ });
60382
+ },
60383
+ async getPlans() {
60384
+ return [
60385
+ {
60386
+ planId: 1,
60387
+ dateList: [1, 2, 3, 4, 5],
60388
+ startTime: "08:00",
60389
+ endTime: "10:00",
60390
+ enableFlag: true,
60391
+ jobList: [
60392
+ {
60393
+ floorNo: 1,
60394
+ rangeFlag: 3,
60395
+ cleanTimes: 1,
60396
+ pointList: [],
60397
+ colorList: [{ r: 255, g: 11, b: 11, a: 131 }],
60398
+ modeFlag: 1,
60399
+ subModeFlag: 0,
60400
+ levelFlag: 1
60401
+ }
60402
+ ]
60403
+ }
60404
+ ];
60405
+ }
60406
+ };
60407
+ }
60408
+ function registerDemoBrand() {
60409
+ registerRobotBrand({
60410
+ brand: DEMO_BRAND,
60411
+ displayName: "Demo robots (simulated)",
60412
+ capabilities: DEMO_CAPABILITIES,
60413
+ create: () => createDemoAdapter()
60414
+ });
60415
+ }
60416
+ registerDemoBrand();
60417
+
60418
+ // src/services/robot-fleet.service.ts
60419
+ function useRobotFleetService(deps) {
60420
+ const adapterFor = deps?.adapterFor ?? ((robot) => adapterForRobot(robot));
60421
+ function capabilitiesOf(robot) {
60422
+ return robotBrandFor(robot.brand ?? "")?.capabilities ?? {};
60423
+ }
60424
+ async function resolveRobotSn(robot) {
60425
+ const stored = (robot.robotSn ?? "").trim();
60426
+ if (stored)
60427
+ return stored;
60428
+ const fleet = await adapterFor(robot).listRobots();
60429
+ if (fleet.length === 0) {
60430
+ throw new NotFoundError43(
60431
+ `Robot "${robot.name}": the robot cloud reports no robots for these credentials.`
60432
+ );
60433
+ }
60434
+ if (fleet.length > 1) {
60435
+ throw new BadRequestError134(
60436
+ `Robot "${robot.name}": these credentials cover ${fleet.length} robots, so the serial cannot be determined automatically. Set it explicitly.`
60437
+ );
60438
+ }
60439
+ const robotSn = fleet[0].robotSn;
60440
+ if (!robotSn) {
60441
+ throw new BadRequestError134(`Robot "${robot.name}": the cloud returned a robot with no serial.`);
60442
+ }
60443
+ const id = robot._id ? String(robot._id) : "";
60444
+ if (id && deps?.saveRobotSn) {
60445
+ await deps.saveRobotSn(id, robotSn).catch(() => void 0);
60446
+ }
60447
+ return robotSn;
60448
+ }
60449
+ async function getFleetRobot(robot) {
60450
+ const adapter = adapterFor(robot);
60451
+ const robotSn = await resolveRobotSn(robot);
60452
+ const found = await adapter.getRobot(robotSn);
60453
+ const id = robot._id ? String(robot._id) : "";
60454
+ const brand = adapter.brand;
60455
+ if (!found) {
60456
+ return {
60457
+ id,
60458
+ name: robot.name,
60459
+ brand,
60460
+ robotSn,
60461
+ statusName: "Not found in the robot cloud",
60462
+ mode: "UNAVAILABLE",
60463
+ online: false,
60464
+ dispatchable: false,
60465
+ batteryPercent: null,
60466
+ floorNo: null,
60467
+ floorKnown: false,
60468
+ position: null
60469
+ };
60470
+ }
60471
+ const free = found.mode === "IDLE" || found.mode === "CHARGED";
60472
+ return {
60473
+ id,
60474
+ name: robot.name,
60475
+ brand,
60476
+ robotSn,
60477
+ statusName: found.statusName ?? "",
60478
+ mode: found.mode,
60479
+ online: found.online,
60480
+ dispatchable: found.online && free && commandSupported(adapter.capabilities, "full"),
60481
+ batteryPercent: found.batteryPercent ?? null,
60482
+ floorNo: found.floorNo ?? null,
60483
+ floorKnown: found.floorKnown === true,
60484
+ position: found.position ?? null
60485
+ };
60486
+ }
60487
+ async function getMaps(robot) {
60488
+ const adapter = adapterFor(robot);
60489
+ if (!adapter.getMaps)
60490
+ return [];
60491
+ return adapter.getMaps(await resolveRobotSn(robot));
60492
+ }
60493
+ async function getMapImages(robot, fileKeys) {
60494
+ const adapter = adapterFor(robot);
60495
+ if (!adapter.getMapImages || !fileKeys.length)
60496
+ return [];
60497
+ return adapter.getMapImages(await resolveRobotSn(robot), fileKeys);
60498
+ }
60499
+ async function getPlans(robot) {
60500
+ const adapter = adapterFor(robot);
60501
+ if (!adapter.getPlans)
60502
+ return [];
60503
+ return adapter.getPlans(await resolveRobotSn(robot));
60504
+ }
60505
+ async function dispatch(robot, command) {
60506
+ const adapter = adapterFor(robot);
60507
+ if (!adapter.sendCommand) {
60508
+ throw new BadRequestError134(
60509
+ `${adapter.displayName} robots cannot be commanded through their API.`
60510
+ );
60511
+ }
60512
+ if (!commandSupported(adapter.capabilities, command.kind)) {
60513
+ throw new BadRequestError134(
60514
+ `${adapter.displayName} does not support "${command.kind}" through its API yet.`
60515
+ );
60516
+ }
60517
+ const robotSn = await resolveRobotSn(robot);
60518
+ if (command.kind === "return") {
60519
+ return adapter.sendCommand(robotSn, command);
60520
+ }
60521
+ const state = await getFleetRobot(robot);
60522
+ if (!state.dispatchable) {
60523
+ throw new BadRequestError134(
60524
+ `Robot "${robot.name}" is ${state.statusName || state.mode} and cannot take a new job.`
60525
+ );
60526
+ }
60527
+ return adapter.sendCommand(robotSn, command);
60528
+ }
60529
+ return {
60530
+ adapterFor,
60531
+ capabilitiesOf,
60532
+ resolveRobotSn,
60533
+ getFleetRobot,
60534
+ getMaps,
60535
+ getMapImages,
60536
+ getPlans,
60537
+ dispatch
60538
+ };
60539
+ }
60540
+
60541
+ // src/services/robot-tracking.service.ts
60542
+ import axios4 from "axios";
60543
+ async function openWithAxios(url, body) {
60544
+ const response = await axios4.post(url, body, {
60545
+ responseType: "stream",
60546
+ // No read timeout: this connection is SUPPOSED to stay open and mostly
60547
+ // silent. A request timeout here would sever a healthy stream on a quiet
60548
+ // robot. The idle watchdog is what decides a stream is dead, because it can
60549
+ // tell silence from a closed socket.
60550
+ timeout: 0,
60551
+ headers: { "Content-Type": "application/json", Accept: "text/event-stream" }
60249
60552
  });
60553
+ return response.data;
60554
+ }
60555
+ function startRobotTracking(options) {
60556
+ const {
60557
+ credentials,
60558
+ robotSn,
60559
+ onEvent,
60560
+ baseUrl = ROBOT_CLOUD_BASE_URL,
60561
+ idleTimeoutMs = TRACKING_IDLE_TIMEOUT_MS,
60562
+ openStream = openWithAxios,
60563
+ now = Date.now
60564
+ } = options;
60565
+ if (!baseUrl)
60566
+ throw new Error("Robot cloud: ROBOT_CLOUD_BASE_URL is not configured.");
60567
+ let stopped = false;
60568
+ let attempt = 0;
60569
+ let current = null;
60570
+ let watchdog = null;
60571
+ let retry = null;
60572
+ let lastActivityAt = now();
60573
+ let everConnected = false;
60574
+ const parser = createSseParser();
60575
+ function clearTimers() {
60576
+ if (watchdog) {
60577
+ clearInterval(watchdog);
60578
+ watchdog = null;
60579
+ }
60580
+ if (retry) {
60581
+ clearTimeout(retry);
60582
+ retry = null;
60583
+ }
60584
+ }
60585
+ function dropUpstream() {
60586
+ if (!current)
60587
+ return;
60588
+ try {
60589
+ current.destroy();
60590
+ } catch {
60591
+ }
60592
+ current = null;
60593
+ }
60594
+ function lose(reason) {
60595
+ if (stopped)
60596
+ return;
60597
+ onEvent({ type: "lost", reason, at: now() });
60598
+ dropUpstream();
60599
+ scheduleReconnect();
60600
+ }
60601
+ function scheduleReconnect() {
60602
+ if (stopped)
60603
+ return;
60604
+ clearTimers();
60605
+ const delay2 = trackingBackoffMs(attempt++);
60606
+ retry = setTimeout(connect2, delay2);
60607
+ }
60608
+ async function connect2() {
60609
+ if (stopped)
60610
+ return;
60611
+ const body = robotCloudBody(
60612
+ credentials.appid,
60613
+ credentials.secret,
60614
+ ROBOT_CLOUD_PATHS.tracking,
60615
+ { robotSn }
60616
+ );
60617
+ let stream;
60618
+ try {
60619
+ stream = await openStream(
60620
+ `${baseUrl.replace(/\/+$/, "")}${ROBOT_CLOUD_PATHS.tracking}`,
60621
+ body
60622
+ );
60623
+ } catch (error) {
60624
+ lose(error?.message ?? "Could not open the robot's tracking stream.");
60625
+ return;
60626
+ }
60627
+ if (stopped) {
60628
+ try {
60629
+ stream.destroy();
60630
+ } catch {
60631
+ }
60632
+ return;
60633
+ }
60634
+ parser.reset();
60635
+ current = stream;
60636
+ lastActivityAt = now();
60637
+ if (everConnected)
60638
+ onEvent({ type: "reconnected", at: now() });
60639
+ everConnected = true;
60640
+ attempt = 0;
60641
+ stream.on("data", (chunk2) => {
60642
+ if (stopped)
60643
+ return;
60644
+ const { events, sawActivity } = parser.push(chunk2.toString("utf8"));
60645
+ if (sawActivity)
60646
+ lastActivityAt = now();
60647
+ for (const event of events) {
60648
+ const pose = decodeTrackingFrame(event.data);
60649
+ if (pose)
60650
+ onEvent({ type: "pose", pose, at: now() });
60651
+ }
60652
+ });
60653
+ stream.on("error", (error) => lose(error?.message ?? "The tracking stream failed."));
60654
+ stream.on("end", () => lose("The robot cloud closed the tracking stream."));
60655
+ stream.on("close", () => {
60656
+ if (!stopped && current === stream)
60657
+ lose("The tracking stream closed.");
60658
+ });
60659
+ clearTimers();
60660
+ watchdog = setInterval(() => {
60661
+ if (stopped)
60662
+ return;
60663
+ if (now() - lastActivityAt >= idleTimeoutMs) {
60664
+ lose("The robot stopped reporting.");
60665
+ }
60666
+ }, Math.max(1e3, Math.floor(idleTimeoutMs / 3)));
60667
+ }
60668
+ void connect2();
60669
+ return {
60670
+ stop() {
60671
+ if (stopped)
60672
+ return;
60673
+ stopped = true;
60674
+ clearTimers();
60675
+ dropUpstream();
60676
+ }
60677
+ };
60250
60678
  }
60251
60679
 
60252
60680
  // src/controllers/robot-fleet.controller.ts
@@ -60327,7 +60755,62 @@ function useRobotFleetController(deps) {
60327
60755
  fail(next, error);
60328
60756
  }
60329
60757
  }
60330
- return { getState, getMaps, getMapImages, getPlans, sendCommand: sendCommand2 };
60758
+ async function trackRobot(req, res, next) {
60759
+ let robot;
60760
+ let robotSn;
60761
+ try {
60762
+ robot = await entitleRobot(req, req.params.id);
60763
+ robotSn = await getFleet().resolveRobotSn(robot);
60764
+ } catch (error) {
60765
+ fail(next, error);
60766
+ return;
60767
+ }
60768
+ res.writeHead(200, {
60769
+ "Content-Type": "text/event-stream",
60770
+ "Cache-Control": "no-cache, no-transform",
60771
+ Connection: "keep-alive",
60772
+ // Nginx buffers proxied responses by default, which holds an SSE stream
60773
+ // until the buffer fills - the stream appears dead for minutes.
60774
+ "X-Accel-Buffering": "no"
60775
+ });
60776
+ res.flushHeaders?.();
60777
+ const send2 = (event) => {
60778
+ if (res.writableEnded)
60779
+ return;
60780
+ res.write(`data: ${JSON.stringify(event)}
60781
+
60782
+ `);
60783
+ };
60784
+ const handle = startRobotTracking({
60785
+ credentials: {
60786
+ appid: (robot.appKey ?? "").trim(),
60787
+ secret: (robot.appToken ?? "").trim()
60788
+ },
60789
+ robotSn,
60790
+ onEvent: send2
60791
+ });
60792
+ const keepAlive = setInterval(() => {
60793
+ if (!res.writableEnded)
60794
+ res.write(": keep-alive\n\n");
60795
+ }, 15e3);
60796
+ const close = () => {
60797
+ clearInterval(keepAlive);
60798
+ handle.stop();
60799
+ if (!res.writableEnded)
60800
+ res.end();
60801
+ };
60802
+ req.on("close", close);
60803
+ req.on("aborted", close);
60804
+ res.on("error", close);
60805
+ }
60806
+ async function getBrands(_req, res, next) {
60807
+ try {
60808
+ res.json({ items: registeredRobotBrands() });
60809
+ } catch (error) {
60810
+ fail(next, error);
60811
+ }
60812
+ }
60813
+ return { getState, getMaps, getMapImages, getPlans, sendCommand: sendCommand2, trackRobot, getBrands };
60331
60814
  }
60332
60815
 
60333
60816
  // src/models/patrol-question.model.ts
@@ -80953,7 +81436,7 @@ import {
80953
81436
  } from "@7365admin1/node-server-utils";
80954
81437
 
80955
81438
  // src/utils/hrmlabs-attendance.util.ts
80956
- import axios4 from "axios";
81439
+ import axios5 from "axios";
80957
81440
  import moment8 from "moment-timezone";
80958
81441
  var default_early_checkIn = 4;
80959
81442
  async function hrmLabsAuthentication({
@@ -80962,7 +81445,7 @@ async function hrmLabsAuthentication({
80962
81445
  password,
80963
81446
  domain
80964
81447
  }) {
80965
- const res = await axios4.post(
81448
+ const res = await axios5.post(
80966
81449
  authUrl,
80967
81450
  {
80968
81451
  grant_type: "password",
@@ -80992,7 +81475,7 @@ async function fetchAttendanceData({
80992
81475
  search
80993
81476
  }) {
80994
81477
  try {
80995
- const res = await axios4.post(
81478
+ const res = await axios5.post(
80996
81479
  attendanceUrl,
80997
81480
  {
80998
81481
  start: startDate,
@@ -81192,7 +81675,7 @@ async function fetchSites({ siteUrl, token }) {
81192
81675
  do {
81193
81676
  try {
81194
81677
  attempt++;
81195
- const res = await axios4.get(siteUrl, {
81678
+ const res = await axios5.get(siteUrl, {
81196
81679
  headers: { Authorization: `Bearer ${token}` },
81197
81680
  timeout: 15e3
81198
81681
  });
@@ -84840,7 +85323,7 @@ async function manpowerEvents(io) {
84840
85323
  }
84841
85324
 
84842
85325
  // src/services/reddot-payment.service.ts
84843
- import axios5 from "axios";
85326
+ import axios6 from "axios";
84844
85327
 
84845
85328
  // src/utils/payment-signature.util.ts
84846
85329
  import * as crypto9 from "crypto";
@@ -85482,7 +85965,7 @@ var useRedDotPaymentSvc = () => {
85482
85965
  );
85483
85966
  }
85484
85967
  const url = process.env.MERCHANT_PAYMENT;
85485
- const response = await axios5.post(url, dataString, { headers });
85968
+ const response = await axios6.post(url, dataString, { headers });
85486
85969
  const result = response.data;
85487
85970
  return result;
85488
85971
  } catch (error) {
@@ -85516,7 +85999,7 @@ var useRedDotPaymentSvc = () => {
85516
85999
  );
85517
86000
  }
85518
86001
  const url = process.env.MERCHANT_ENQUIRY;
85519
- const response = await axios5.post(url, dataString, { headers });
86002
+ const response = await axios6.post(url, dataString, { headers });
85520
86003
  const result = response.data;
85521
86004
  const transactionId = result.transaction_id;
85522
86005
  const amount = result.request_amount || 0;
@@ -94165,6 +94648,8 @@ export {
94165
94648
  DEFAULT_RING_SECONDS,
94166
94649
  DEFAULT_SCALE_M_PER_PX,
94167
94650
  DEFAULT_SITE_TIMEZONE,
94651
+ DEMO_BRAND,
94652
+ DEMO_CAPABILITIES,
94168
94653
  DEVICE_STATUS,
94169
94654
  DOBStatus,
94170
94655
  DUPLICATE_TERMS_VERSION_MESSAGE,
@@ -94334,6 +94819,7 @@ export {
94334
94819
  ROBOT_CAPABILITIES,
94335
94820
  ROBOT_CLOUD_DOC_CONFIRMATIONS,
94336
94821
  ROBOT_CLOUD_PATHS,
94822
+ ROBOT_CLOUD_TIMEOUT_MS,
94337
94823
  ROBOT_MODULE,
94338
94824
  ROBOT_PERMISSIONS,
94339
94825
  ROBOT_STATUS_NAMES,
@@ -94385,6 +94871,7 @@ export {
94385
94871
  VisitorStatus,
94386
94872
  WALL_HEIGHT_M,
94387
94873
  WALL_THICKNESS_M,
94874
+ adapterForRobot,
94388
94875
  addressSchema,
94389
94876
  allowedFieldsSite,
94390
94877
  allowedNatures,
@@ -94424,13 +94911,14 @@ export {
94424
94911
  chatSchema,
94425
94912
  claimReadsAsStale,
94426
94913
  clampPtzSpeed,
94427
- clearRobotAdapters,
94914
+ clearRobotBrands,
94428
94915
  clockDriftSeconds,
94429
94916
  commandSupported,
94430
94917
  consoleRoleAllows,
94431
94918
  consoleRoleAllowsAll,
94432
94919
  consoleSpellings,
94433
94920
  console_audit_namespace_collection,
94921
+ createDemoAdapter,
94434
94922
  createLiftSimulator,
94435
94923
  createManpowerRemarksDaily,
94436
94924
  createOunitechAdapter,
@@ -94454,6 +94942,7 @@ export {
94454
94942
  disconnectedState,
94455
94943
  emitNotificationCreated,
94456
94944
  emptyPersonalEmergencyChain,
94945
+ encodeGreyscalePng,
94457
94946
  encodeHidPacsCard,
94458
94947
  entitledSiteScope,
94459
94948
  events_namespace_collection,
@@ -94472,6 +94961,7 @@ export {
94472
94961
  getIO,
94473
94962
  getSessionIdFromRequest,
94474
94963
  grabWithSubStreamFallback,
94964
+ greyscalePngDataUri,
94475
94965
  guests_namespace_collection,
94476
94966
  hasAnyCapability,
94477
94967
  hasAnyPermission,
@@ -94573,8 +95063,9 @@ export {
94573
95063
  recordConsoleAction,
94574
95064
  refuseServiceProviderInviteAction,
94575
95065
  registerCameraTransport,
95066
+ registerDemoBrand,
94576
95067
  registerOunitechBrand,
94577
- registerRobotAdapter,
95068
+ registerRobotBrand,
94578
95069
  registeredRobotBrands,
94579
95070
  relayForRecorder,
94580
95071
  remarksSchema,
@@ -94593,7 +95084,7 @@ export {
94593
95084
  resolveSiteTimezone,
94594
95085
  resolveStaffRole,
94595
95086
  robotActionsFor,
94596
- robotAdapterFor,
95087
+ robotBrandFor,
94597
95088
  robotCloudBody,
94598
95089
  robotCloudSign,
94599
95090
  robotCloudSigningString,
@@ -94772,6 +95263,7 @@ export {
94772
95263
  snapshotEndpoint,
94773
95264
  snapshotRefusalReason,
94774
95265
  staffMayBypass,
95266
+ startRobotTracking,
94775
95267
  startTodaysBookForSite,
94776
95268
  stringifyHidJson,
94777
95269
  stripFacialImageMetadata,