@dszp/netsapiens-lib 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -4
- package/dist/attribution.d.ts +11 -0
- package/dist/attribution.d.ts.map +1 -0
- package/dist/attribution.js +113 -0
- package/dist/attribution.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +59 -5
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +72 -4
- package/dist/inventory.js.map +1 -1
- package/dist/model.d.ts +8 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/nsClient.d.ts +6 -0
- package/dist/nsClient.d.ts.map +1 -1
- package/dist/nsClient.js +27 -0
- package/dist/nsClient.js.map +1 -1
- package/package.json +4 -3
- package/src/attribution.selftest.ts +73 -0
- package/src/attribution.ts +104 -0
- package/src/index.ts +2 -1
- package/src/inventory.selftest.ts +49 -5
- package/src/inventory.ts +98 -9
- package/src/model.ts +8 -0
- package/src/nsClient.selftest.ts +46 -0
- package/src/nsClient.ts +32 -0
package/src/nsClient.selftest.ts
CHANGED
|
@@ -168,6 +168,52 @@ const ok = (c: boolean, msg: string) => {
|
|
|
168
168
|
ok(noDevicesSnap.deviceReadFailures === undefined, 'without includeDevices, deviceReadFailures is absent entirely');
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
// -- per-user SMS numbers: one call per REAL extension, opt-in, failures named like device reads --
|
|
172
|
+
{
|
|
173
|
+
const seen: string[] = [];
|
|
174
|
+
const client = { get: async (p: string) => {
|
|
175
|
+
seen.push(p);
|
|
176
|
+
if (p.endsWith('/users')) return [{ user: '100', 'service-code': '' }, { user: '101', 'service-code': '' }, { user: '700', 'service-code': 'system-aa' }];
|
|
177
|
+
if (p.endsWith('/users/100/smsnumbers')) return [{ number: '13175550100' }];
|
|
178
|
+
if (p.endsWith('/users/101/smsnumbers')) throw new NsApiError('GET .../smsnumbers → 500', 500, p, null);
|
|
179
|
+
if (/\/domains\/[^/]+$/.test(p)) return [{ domain: 'acme.example' }];
|
|
180
|
+
return [];
|
|
181
|
+
} } as unknown as NsClient;
|
|
182
|
+
const snap = await fetchDomainSnapshot(client, 'acme.example', { includeAttendantMenus: false, includeUserSmsNumbers: true });
|
|
183
|
+
ok(JSON.stringify(snap.smsNumbersByUser) === JSON.stringify({ '100': [{ number: '13175550100' }] }), 'per-user SMS numbers are filed under the extension');
|
|
184
|
+
ok(JSON.stringify(snap.smsReadFailures) === JSON.stringify(['101']), 'a failed per-user SMS read is named');
|
|
185
|
+
ok(!seen.some((p) => p.endsWith('/users/700/smsnumbers')), 'system users are not read');
|
|
186
|
+
const plain = await fetchDomainSnapshot(client, 'acme.example', { includeAttendantMenus: false });
|
|
187
|
+
ok(plain.smsNumbersByUser === undefined && plain.smsReadFailures === undefined, 'without the option neither field is present');
|
|
188
|
+
|
|
189
|
+
// PRESENT AND EMPTY when every read answered — the same contract deviceReadFailures has, and the
|
|
190
|
+
// distinction a consumer needs: absent means nobody asked, empty means nothing failed. An absent
|
|
191
|
+
// field read as "nothing failed" would be a page claiming a clean read it never performed.
|
|
192
|
+
const allOk = { get: async (p: string) => {
|
|
193
|
+
if (p.endsWith('/users')) return [{ user: '100', 'service-code': '' }];
|
|
194
|
+
if (p.endsWith('/users/100/smsnumbers')) return [{ number: '13175550100' }];
|
|
195
|
+
if (/\/domains\/[^/]+$/.test(p)) return [{ domain: 'acme.example' }];
|
|
196
|
+
return [];
|
|
197
|
+
} } as unknown as NsClient;
|
|
198
|
+
const clean = await fetchDomainSnapshot(allOk, 'acme.example', { includeAttendantMenus: false, includeUserSmsNumbers: true });
|
|
199
|
+
ok(Array.isArray(clean.smsReadFailures) && clean.smsReadFailures.length === 0,
|
|
200
|
+
'no failures - smsReadFailures is an empty array, not absent');
|
|
201
|
+
|
|
202
|
+
// SORTED, not in completion order. These fill under mapLimit, so the fixture makes the FIRST-sorting
|
|
203
|
+
// extension answer last; an unsorted list would come back ['101','100'] and the panel that prints it
|
|
204
|
+
// would reshuffle between two reads of the same broken domain.
|
|
205
|
+
const twoFails = { get: async (p: string) => {
|
|
206
|
+
if (p.endsWith('/users')) return [{ user: '100', 'service-code': '' }, { user: '101', 'service-code': '' }];
|
|
207
|
+
if (p.endsWith('/users/100/smsnumbers')) { await new Promise((r) => setTimeout(r, 10)); throw new NsApiError('GET .../smsnumbers → 500', 500, p, null); }
|
|
208
|
+
if (p.endsWith('/users/101/smsnumbers')) throw new NsApiError('GET .../smsnumbers → 500', 500, p, null);
|
|
209
|
+
if (/\/domains\/[^/]+$/.test(p)) return [{ domain: 'acme.example' }];
|
|
210
|
+
return [];
|
|
211
|
+
} } as unknown as NsClient;
|
|
212
|
+
const sorted = await fetchDomainSnapshot(twoFails, 'acme.example', { includeAttendantMenus: false, includeUserSmsNumbers: true });
|
|
213
|
+
ok(JSON.stringify(sorted.smsReadFailures) === JSON.stringify(['100', '101']),
|
|
214
|
+
'two failures come back sorted, whichever order they completed in');
|
|
215
|
+
}
|
|
216
|
+
|
|
171
217
|
console.log(`\n${pass} passed, ${fail} failed`);
|
|
172
218
|
process.exit(fail ? 1 : 0);
|
|
173
219
|
})();
|
package/src/nsClient.ts
CHANGED
|
@@ -183,6 +183,12 @@ export interface FetchSnapshotOptions {
|
|
|
183
183
|
* auto attendants and queues, and they hold no seat.
|
|
184
184
|
*/
|
|
185
185
|
includeDevices?: boolean;
|
|
186
|
+
/**
|
|
187
|
+
* Also read each REAL extension's SMS-enabled numbers into `smsNumbersByUser`. One call per
|
|
188
|
+
* extension, like `includeDevices`, and for the same reason: the domain-level list does not say
|
|
189
|
+
* which user a number belongs to, and a consumer splitting a domain by site needs to know.
|
|
190
|
+
*/
|
|
191
|
+
includeUserSmsNumbers?: boolean;
|
|
186
192
|
}
|
|
187
193
|
|
|
188
194
|
/**
|
|
@@ -258,6 +264,30 @@ export async function fetchDomainSnapshot(client: NsClient, domain: string, opts
|
|
|
258
264
|
});
|
|
259
265
|
if (devs.length) devicesByUser![ext] = devs;
|
|
260
266
|
});
|
|
267
|
+
// SORTED before it leaves. These fill under `mapLimit`, so the order is whichever read failed first —
|
|
268
|
+
// a list that reshuffles between two reads of the same broken domain reads as data that changed, and a
|
|
269
|
+
// consumer prints this straight at an operator.
|
|
270
|
+
deviceReadFailures.sort();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Per-user SMS numbers, same shape and reason as devices above: the domain-level list (see
|
|
274
|
+
// includeSmsNumbers) does not say which user a number belongs to.
|
|
275
|
+
let smsNumbersByUser: Record<string, Rec[]> | undefined;
|
|
276
|
+
let smsReadFailures: string[] | undefined;
|
|
277
|
+
if (opts.includeUserSmsNumbers) {
|
|
278
|
+
smsNumbersByUser = {};
|
|
279
|
+
smsReadFailures = [];
|
|
280
|
+
const seats = users.filter((u) => !String(u['service-code'] ?? '').trim().toLowerCase().startsWith('system-'));
|
|
281
|
+
await mapLimit(seats, conc, async (u) => {
|
|
282
|
+
const ext = String(u.user ?? '');
|
|
283
|
+
if (!ext) return;
|
|
284
|
+
const list = await soft(`${base}/users/${enc(ext)}/smsnumbers`).catch(() => {
|
|
285
|
+
smsReadFailures!.push(ext);
|
|
286
|
+
return [];
|
|
287
|
+
});
|
|
288
|
+
if (list.length) smsNumbersByUser![ext] = list;
|
|
289
|
+
});
|
|
290
|
+
smsReadFailures.sort(); // same reason as deviceReadFailures above
|
|
261
291
|
}
|
|
262
292
|
|
|
263
293
|
const answerrulesByUser: Record<string, Rec[]> = {};
|
|
@@ -316,6 +346,8 @@ export async function fetchDomainSnapshot(client: NsClient, domain: string, opts
|
|
|
316
346
|
...(smsnumbers ? { smsnumbers } : {}),
|
|
317
347
|
...(devicesByUser ? { devicesByUser } : {}),
|
|
318
348
|
...(deviceReadFailures ? { deviceReadFailures } : {}),
|
|
349
|
+
...(smsNumbersByUser ? { smsNumbersByUser } : {}),
|
|
350
|
+
...(smsReadFailures ? { smsReadFailures } : {}),
|
|
319
351
|
...(attendantDetailsByUser && Object.keys(attendantDetailsByUser).length ? { attendantDetailsByUser } : {}),
|
|
320
352
|
...(attendantDialrulesByExt && Object.keys(attendantDialrulesByExt).length ? { attendantDialrulesByExt } : {}),
|
|
321
353
|
...(dialrulesByPlan ? { dialrulesByPlan } : {}),
|