@dszp/netsapiens-lib 0.4.0 → 0.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/README.md +16 -2
- package/dist/attribution.d.ts +13 -1
- package/dist/attribution.d.ts.map +1 -1
- package/dist/attribution.js +30 -27
- package/dist/attribution.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +50 -4
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +63 -1
- package/dist/inventory.js.map +1 -1
- package/package.json +1 -1
- package/src/attribution.selftest.ts +28 -4
- package/src/attribution.ts +39 -20
- package/src/index.ts +1 -1
- package/src/inventory.selftest.ts +49 -5
- package/src/inventory.ts +88 -6
package/README.md
CHANGED
|
@@ -131,6 +131,12 @@ never appear, though names and sites now do (that's the point of a list). Each i
|
|
|
131
131
|
| `e911Addresses` | `addr:<emergency-address-id>` |
|
|
132
132
|
| `smsNumbers` | `sms:<number>` |
|
|
133
133
|
|
|
134
|
+
A number carries `destination` — where it routes, in words (`to user 100 — Ann Lee`, `to queue 701 —
|
|
135
|
+
Sales`), built by the exported `destinationOf(p, userByExt)` over `usersByExt(users)` — and `description`
|
|
136
|
+
(`dial-rule-description`, trimmed); an extension carries `devices`, an `{ name, model, teams }` per
|
|
137
|
+
device it has (handset and Teams connector alike), in record order — still never the MAC, SIP password
|
|
138
|
+
or email.
|
|
139
|
+
|
|
134
140
|
`itemsFor(detail, path)` returns the items behind one of `countDomainInventory`'s dotted-path counts —
|
|
135
141
|
the same vocabulary, so a UI that lets an operator drill from a count into the records behind it needs
|
|
136
142
|
no separate lookup table:
|
|
@@ -166,9 +172,17 @@ against one of them needs to know which inventory items are actually its own. `a
|
|
|
166
172
|
answers that: it's pure (no account knowledge, just the snapshot) and labels every extension, number,
|
|
167
173
|
address and SMS number either `own-site` (the item's own site matches), `via-user:<ext>` /
|
|
168
174
|
`via-users:<exts>` (it's reachable only through another item that has a site), or an
|
|
169
|
-
`unattributed:<reason>` — `no-site`, `routed-to:<x>`, `
|
|
175
|
+
`unattributed:<reason>` — `no-site`, `routed-to:<x>`, `unreferenced`, or
|
|
170
176
|
`sms-user-unknown` when a domain-level SMS number can't be matched to a user because the snapshot was
|
|
171
|
-
never fetched with `includeUserSmsNumbers`.
|
|
177
|
+
never fetched with `includeUserSmsNumbers`.
|
|
178
|
+
|
|
179
|
+
Each verdict carries **`sites: string[]`** as well as `site`. They agree wherever there is one site,
|
|
180
|
+
and only an **E911 address** can carry more than one: an address is a fact about a place, and users on
|
|
181
|
+
four sites can all reference it, so `sites` names every one of them while `site` stays `null`. A
|
|
182
|
+
consumer splitting a domain between billing accounts should read `sites` — placing such an address on
|
|
183
|
+
exactly one account leaves every other referencing account's E911 line short.
|
|
184
|
+
|
|
185
|
+
Filter `listDomainInventory(snapshot)`'s items by that
|
|
172
186
|
attribution to scope a domain down to one site, then run `countInventoryDetail(detail)` over what's left
|
|
173
187
|
to get counts that agree with what you kept, rather than recomputing `countDomainInventory` against the
|
|
174
188
|
whole domain and hoping the numbers happen to match.
|
package/dist/attribution.d.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import type { Snapshot } from './model.js';
|
|
2
2
|
export interface ItemAttribution {
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* The single site this item belongs to; `null` when there is not exactly one — nothing placed it, or
|
|
5
|
+
* (addresses only) several did. Read {@link sites} to tell those two apart.
|
|
6
|
+
*/
|
|
4
7
|
site: string | null;
|
|
8
|
+
/**
|
|
9
|
+
* EVERY site this item belongs to, unique and sorted. One entry wherever {@link site} is set, none
|
|
10
|
+
* where nothing placed it, and — for an address referenced from several sites — one per site.
|
|
11
|
+
*
|
|
12
|
+
* The wider field, and the one a consumer splitting a domain between accounts should read: only an
|
|
13
|
+
* ADDRESS can carry more than one, because only an address is a fact about a place rather than about
|
|
14
|
+
* a user, a number or a route.
|
|
15
|
+
*/
|
|
16
|
+
sites: string[];
|
|
5
17
|
/** `own-site` | `via-user:<ext>` | `via-users:<ext,ext>` | `unattributed:<reason>`. */
|
|
6
18
|
how: string;
|
|
7
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAO,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC;CACb;AACD,MAAM,WAAW,iBAAiB;IAAG,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAAE;AAI7E,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,GAAG,iBAAiB,CA8D9E"}
|
package/dist/attribution.js
CHANGED
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
* ## The rules
|
|
14
14
|
*
|
|
15
15
|
* - An extension: its own `site` (`own-site`), or `unattributed:no-site` when blank.
|
|
16
|
-
* - A number: the site of its `dial-rule-translation-destination-user` when that names a
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* `routed-to:<that user's service-code>`; when
|
|
20
|
-
*
|
|
21
|
-
* - An address:
|
|
22
|
-
*
|
|
23
|
-
*
|
|
16
|
+
* - A number: the site of its `dial-rule-translation-destination-user` when that names a user WITH a
|
|
17
|
+
* site (`via-user:<ext>`) — real or system, whatever the application (`to-user`, `to-single-device`);
|
|
18
|
+
* a queue at the North site is a North number. Only a site-less system user falls back to
|
|
19
|
+
* `routed-to:<that user's service-code>`; when the destination names nobody, `routed-to:<application>`;
|
|
20
|
+
* a site-less real user is `no-site`.
|
|
21
|
+
* - An address: EVERY site a REAL extension referencing it sits on (`via-users:<exts>`), in `sites`.
|
|
22
|
+
* An address is a fact about a PLACE, and users on four sites can legitimately reference one — so
|
|
23
|
+
* the multi-site case is not a failure to attribute, it is the answer. `site` is the single site
|
|
24
|
+
* when there is exactly one and `null` otherwise, so a consumer that can only hold one still reads
|
|
25
|
+
* the unambiguous case correctly. `unreferenced` when no real extension names it; `no-site` when the
|
|
26
|
+
* ones that do have no site.
|
|
24
27
|
* - An SMS number: the site of the user whose per-user list carries it (`via-user:<ext>`), or
|
|
25
28
|
* `sms-user-unknown` when no per-user list does — including when the per-user read was never
|
|
26
29
|
* made. Never guessed from the domain-level list, which does not say.
|
|
@@ -31,8 +34,8 @@
|
|
|
31
34
|
* put an item's KEY (which may be a derived `~hash`) beside its RECORD's routing fields. That
|
|
32
35
|
* invariant is this library's own, and this is the one place allowed to lean on it.
|
|
33
36
|
*/
|
|
34
|
-
import { isSystemUser, listDomainInventory, str } from './inventory.js';
|
|
35
|
-
const none = (reason) => ({ site: null, how: `unattributed:${reason}` });
|
|
37
|
+
import { isSystemUser, listDomainInventory, str, usersByExt } from './inventory.js';
|
|
38
|
+
const none = (reason) => ({ site: null, sites: [], how: `unattributed:${reason}` });
|
|
36
39
|
export function attributeDomainInventory(snapshot) {
|
|
37
40
|
const users = Array.isArray(snapshot.users) ? snapshot.users : [];
|
|
38
41
|
const phonenumbers = Array.isArray(snapshot.phonenumbers) ? snapshot.phonenumbers : [];
|
|
@@ -41,15 +44,12 @@ export function attributeDomainInventory(snapshot) {
|
|
|
41
44
|
const byUser = snapshot.smsNumbersByUser;
|
|
42
45
|
const d = listDomainInventory(snapshot);
|
|
43
46
|
const items = {};
|
|
44
|
-
const userByExt =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (ext && !userByExt.has(ext))
|
|
48
|
-
userByExt.set(ext, u);
|
|
49
|
-
}
|
|
47
|
+
const userByExt = usersByExt(users);
|
|
48
|
+
/** The single-site verdict, with `sites` kept in step so the two fields can never disagree. */
|
|
49
|
+
const one = (site, how) => ({ site, sites: [site], how });
|
|
50
50
|
// Extensions: the detail already carries `site`, so no record is needed here.
|
|
51
51
|
for (const x of d.extensions)
|
|
52
|
-
items[x.key] = x.site ?
|
|
52
|
+
items[x.key] = x.site ? one(x.site, 'own-site') : none('no-site');
|
|
53
53
|
// Numbers, by index against `phonenumbers`.
|
|
54
54
|
for (let i = 0; i < d.dids.length; i++) {
|
|
55
55
|
const key = d.dids[i].key, p = phonenumbers[i] ?? {};
|
|
@@ -60,12 +60,12 @@ export function attributeDomainInventory(snapshot) {
|
|
|
60
60
|
items[key] = none(`routed-to:${app}`);
|
|
61
61
|
continue;
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const site = str(u.site);
|
|
64
|
+
if (!site) {
|
|
65
|
+
items[key] = isSystemUser(u) ? none(`routed-to:${str(u['service-code'])}`) : none('no-site');
|
|
65
66
|
continue;
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
-
items[key] = site ? { site, how: `via-user:${dest}` } : none('no-site');
|
|
68
|
+
items[key] = one(site, `via-user:${dest}`);
|
|
69
69
|
}
|
|
70
70
|
// Addresses, by index against `addresses`; referenced by REAL extensions only.
|
|
71
71
|
const refs = new Map();
|
|
@@ -87,12 +87,15 @@ export function attributeDomainInventory(snapshot) {
|
|
|
87
87
|
continue;
|
|
88
88
|
}
|
|
89
89
|
const sites = [...new Set(who.map((u) => str(u.site)).filter(Boolean))].sort();
|
|
90
|
-
if (sites.length === 0)
|
|
90
|
+
if (sites.length === 0) {
|
|
91
91
|
items[key] = none('no-site');
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// SEVERAL SITES IS AN ANSWER, not a failure. The referencing users name every place this address
|
|
95
|
+
// is used, and a consumer billing per site needs all of them; `site` still answers only the
|
|
96
|
+
// unambiguous case, which is what keeps a one-site consumer correct without reading `sites`.
|
|
97
|
+
const how = `via-users:${who.map((u) => str(u.user)).filter(Boolean).sort().join(',')}`;
|
|
98
|
+
items[key] = sites.length === 1 ? one(sites[0], how) : { site: null, sites, how };
|
|
96
99
|
}
|
|
97
100
|
// SMS numbers, by index against `smsnumbers`, joined to a user through the per-user lists.
|
|
98
101
|
const extBySms = new Map();
|
|
@@ -112,7 +115,7 @@ export function attributeDomainInventory(snapshot) {
|
|
|
112
115
|
continue;
|
|
113
116
|
}
|
|
114
117
|
const site = str(u.site);
|
|
115
|
-
items[key] = site ?
|
|
118
|
+
items[key] = site ? one(site, `via-user:${ext}`) : none('no-site');
|
|
116
119
|
}
|
|
117
120
|
return { items };
|
|
118
121
|
}
|
package/dist/attribution.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attribution.js","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"attribution.js","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export { THEMES, DEFAULT_LIGHT_THEME, DEFAULT_DARK_THEME, NODE_LIGHT, NODE_DARK,
|
|
|
16
16
|
export { renderGalleryHtml, renderFlowCards, renderFlowCard, mermaidBootstrap, flowAnchorId, type GalleryOptions, type CardOptions, } from './html.js';
|
|
17
17
|
export { resolveSvgSize, rasterizerScript } from './raster.js';
|
|
18
18
|
export { NsClient, NsApiError, assertBareServer, fetchDomainSnapshot, listDomains, asArray, type NsClientConfig, type FetchSnapshotOptions } from './nsClient.js';
|
|
19
|
-
export { countDomainInventory, countInventoryDetail, listDomainInventory, itemsFor, itemLabel, type DomainInventory, type DomainInventoryDetail, type ExtensionItem, type NumberItem, type AddressItem, type SmsItem, type InventoryItem } from './inventory.js';
|
|
19
|
+
export { countDomainInventory, countInventoryDetail, listDomainInventory, itemsFor, itemLabel, destinationOf, usersByExt, type DomainInventory, type DomainInventoryDetail, type ExtensionItem, type NumberItem, type AddressItem, type SmsItem, type InventoryItem } from './inventory.js';
|
|
20
20
|
export { attributeDomainInventory, type DomainAttribution, type ItemAttribution } from './attribution.js';
|
|
21
21
|
export { NsWriteClient, type NsWriteClientConfig } from './nsWriteClient.js';
|
|
22
22
|
export { supportsSynchronous, SYNCHRONOUS_OPERATIONS, type SynchronousMethod, type SynchronousOperation, } from './nsSynchronous.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EACL,MAAM,EACN,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,UAAU,EACV,SAAS,EACT,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAClK,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,qBAAqB,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { THEMES, DEFAULT_LIGHT_THEME, DEFAULT_DARK_THEME, NODE_LIGHT, NODE_DARK,
|
|
|
15
15
|
export { renderGalleryHtml, renderFlowCards, renderFlowCard, mermaidBootstrap, flowAnchorId, } from './html.js';
|
|
16
16
|
export { resolveSvgSize, rasterizerScript } from './raster.js';
|
|
17
17
|
export { NsClient, NsApiError, assertBareServer, fetchDomainSnapshot, listDomains, asArray } from './nsClient.js';
|
|
18
|
-
export { countDomainInventory, countInventoryDetail, listDomainInventory, itemsFor, itemLabel } from './inventory.js';
|
|
18
|
+
export { countDomainInventory, countInventoryDetail, listDomainInventory, itemsFor, itemLabel, destinationOf, usersByExt } from './inventory.js';
|
|
19
19
|
export { attributeDomainInventory } from './attribution.js';
|
|
20
20
|
export { NsWriteClient } from './nsWriteClient.js';
|
|
21
21
|
export { supportsSynchronous, SYNCHRONOUS_OPERATIONS, } from './nsSynchronous.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAkB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAuC,MAAM,cAAc,CAAC;AAC9E,OAAO,EACL,MAAM,EACN,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,UAAU,EACV,SAAS,GAKV,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,YAAY,GAGb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,WAAW,EAAE,OAAO,EAAkD,MAAM,eAAe,CAAC;AAClK,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,QAAQ,EAAE,SAAS,EAA6I,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAkB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAuC,MAAM,cAAc,CAAC;AAC9E,OAAO,EACL,MAAM,EACN,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,UAAU,EACV,SAAS,GAKV,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,YAAY,GAGb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,WAAW,EAAE,OAAO,EAAkD,MAAM,eAAe,CAAC;AAClK,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAA6I,MAAM,gBAAgB,CAAC;AAC5R,OAAO,EAAE,wBAAwB,EAAgD,MAAM,kBAAkB,CAAC;AAC1G,OAAO,EAAE,aAAa,EAA4B,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EACL,mBAAmB,EACnB,sBAAsB,GAGvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,YAAY,GAIb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAiD,MAAM,mBAAmB,CAAC;AAC7G,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAUlB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,QAAQ,EACR,kBAAkB,GAOnB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAwB,cAAc,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EACL,WAAW,EACX,aAAa,EACb,eAAe,EACf,YAAY,GAIb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EACX,SAAS,EACT,GAAG,GAIJ,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,GAOpB,MAAM,kBAAkB,CAAC"}
|
package/dist/inventory.d.ts
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* ## Counts, and the lists behind them
|
|
8
8
|
*
|
|
9
9
|
* A device record from NetSapiens carries the SIP registration password. `listDomainInventory` builds
|
|
10
|
-
* per-item lists — an extension's name and site, a number's kind, an address's
|
|
11
|
-
* allowlist of named fields, and `countDomainInventory` is a fold over those same
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* per-item lists — an extension's name and site, a number's kind and routing destination, an address's
|
|
11
|
+
* label — from an allowlist of named fields, and `countDomainInventory` is a fold over those same
|
|
12
|
+
* lists. The allowlist now includes a device's NAME (its `aor` local part, e.g. `101b` — the same
|
|
13
|
+
* short id the portal shows) alongside its model; a device's MAC, SIP password and email never appear,
|
|
14
|
+
* and nothing here should be added that carries one.
|
|
14
15
|
*
|
|
15
16
|
* ## Every countable dimension is a numeric leaf
|
|
16
17
|
*
|
|
@@ -97,6 +98,19 @@ export interface ExtensionItem {
|
|
|
97
98
|
deviceCount: number;
|
|
98
99
|
/** `device-models-model` per handset, `(unknown)` when blank. Never the MAC. */
|
|
99
100
|
deviceModels: string[];
|
|
101
|
+
/**
|
|
102
|
+
* Every device on this extension, in record order, connector included: `name` is the `aor` local
|
|
103
|
+
* part (`sip:101b@acme.example` → `101b`) — the device NAME as the portal shows it — `model` is
|
|
104
|
+
* `device-models-model` (`(unknown)` when blank on a handset, `''` for the Teams connector, which
|
|
105
|
+
* has no model), and `teams` marks the connector entry itself. `deviceCount`/`deviceModels`/`teams`
|
|
106
|
+
* above stay handset-only; this list is the one place a connector's own row shows up. Never the MAC
|
|
107
|
+
* or the SIP password.
|
|
108
|
+
*/
|
|
109
|
+
devices: Array<{
|
|
110
|
+
name: string;
|
|
111
|
+
model: string;
|
|
112
|
+
teams: boolean;
|
|
113
|
+
}>;
|
|
100
114
|
/** `deviceCount > 0 || teams` — has a device of any kind, handset or connector. */
|
|
101
115
|
anyDevice: boolean;
|
|
102
116
|
}
|
|
@@ -104,6 +118,10 @@ export interface NumberItem {
|
|
|
104
118
|
key: string;
|
|
105
119
|
number: string;
|
|
106
120
|
kind: 'local' | 'tollFree';
|
|
121
|
+
/** Where the number routes, for a person: see {@link destinationOf}. `''` when the record says nothing. */
|
|
122
|
+
destination: string;
|
|
123
|
+
/** `dial-rule-description` trimmed — the note the portal writes ("Portal Created: User - 1001"); `''` when blank. */
|
|
124
|
+
description: string;
|
|
107
125
|
}
|
|
108
126
|
export interface AddressItem {
|
|
109
127
|
key: string;
|
|
@@ -126,6 +144,34 @@ export interface DomainInventoryDetail {
|
|
|
126
144
|
export declare const str: (v: unknown) => string;
|
|
127
145
|
/** Is this user one of NetSapiens' internal routing objects rather than a seat? */
|
|
128
146
|
export declare function isSystemUser(user: Rec): boolean;
|
|
147
|
+
/**
|
|
148
|
+
* One record per non-blank `user`, first one wins. The same rule `attribution.ts` needs to join a
|
|
149
|
+
* number's `dial-rule-translation-destination-user` (or an SMS number, or an address) back to the
|
|
150
|
+
* user it belongs to — extracted here so there is exactly one copy of it in the library.
|
|
151
|
+
*/
|
|
152
|
+
export declare function usersByExt(users: Rec[]): Map<string, Rec>;
|
|
153
|
+
/**
|
|
154
|
+
* Where a phone number routes, in words a person reads at a glance — not the raw NetSapiens dial
|
|
155
|
+
* rule fields. Pure; looks the destination user up in `userByExt` ({@link usersByExt}) so it can
|
|
156
|
+
* name a real extension or a system object (`system-queue` → `queue`, `system-aa` → `aa`, and so
|
|
157
|
+
* on) rather than just echoing back an extension number.
|
|
158
|
+
*
|
|
159
|
+
* - The destination names a REAL extension → `to user <ext> — <First Last>` (the name is omitted,
|
|
160
|
+
* dash and all, when both name fields are blank).
|
|
161
|
+
* - The destination names a SYSTEM user (a queue, an attendant, a time-of-day router) →
|
|
162
|
+
* `to <kind> <ext> — <name>`, `kind` being the `service-code` with its `system-` prefix stripped
|
|
163
|
+
* (`queue`, `aa`, `tod`, the raw code for anything else, or `system` when stripping leaves
|
|
164
|
+
* nothing — a bare `system-` service code); name omitted the same way.
|
|
165
|
+
* - The destination is set but names nobody NetSapiens knows about → `to <application> <dest>`
|
|
166
|
+
* (`dial-rule-application` with a leading `to-` stripped, so `to-user` reads as `user`; falls back
|
|
167
|
+
* to `user` itself when the application is blank), plus `@<host>` whenever
|
|
168
|
+
* `dial-rule-translation-destination-host` is non-empty — this module has no domain to compare it
|
|
169
|
+
* against, so any non-empty host is shown.
|
|
170
|
+
* - No destination but an application is set → `to <application>` (`to-connection` → `to connection`,
|
|
171
|
+
* `to-voicemail` → `to voicemail`).
|
|
172
|
+
* - Neither is set → `''`.
|
|
173
|
+
*/
|
|
174
|
+
export declare function destinationOf(p: Rec, userByExt: Map<string, Rec>): string;
|
|
129
175
|
/**
|
|
130
176
|
* The items behind every count. Pure. Fields are copied by name from an allowlist; no record passes
|
|
131
177
|
* through, so a device's MAC or SIP password cannot reach a consumer by accident.
|
package/dist/inventory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inventory.d.ts","sourceRoot":"","sources":["../src/inventory.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"inventory.d.ts","sourceRoot":"","sources":["../src/inventory.ts"],"names":[],"mappings":"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"}
|
package/dist/inventory.js
CHANGED
|
@@ -72,9 +72,68 @@ function extensionItem(u, devices) {
|
|
|
72
72
|
// A device whose model is blank is listed under a named bucket rather than dropped: a missing
|
|
73
73
|
// model is a provisioning gap worth seeing, and a silently smaller total hides it.
|
|
74
74
|
deviceModels: handsets.map((d) => str(d['device-models-model']) || '(unknown)'),
|
|
75
|
+
// Every device, including the Teams connector — this is a display list, not a seat count.
|
|
76
|
+
devices: devices.map((d) => {
|
|
77
|
+
const isTeams = ext ? aorLocal(d) === `${ext}t` : false;
|
|
78
|
+
return { name: aorLocal(d), model: isTeams ? '' : str(d['device-models-model']) || '(unknown)', teams: isTeams };
|
|
79
|
+
}),
|
|
75
80
|
anyDevice: handsets.length > 0 || teams,
|
|
76
81
|
};
|
|
77
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* One record per non-blank `user`, first one wins. The same rule `attribution.ts` needs to join a
|
|
85
|
+
* number's `dial-rule-translation-destination-user` (or an SMS number, or an address) back to the
|
|
86
|
+
* user it belongs to — extracted here so there is exactly one copy of it in the library.
|
|
87
|
+
*/
|
|
88
|
+
export function usersByExt(users) {
|
|
89
|
+
const map = new Map();
|
|
90
|
+
for (const u of users) {
|
|
91
|
+
const ext = str(u.user);
|
|
92
|
+
if (ext && !map.has(ext))
|
|
93
|
+
map.set(ext, u);
|
|
94
|
+
}
|
|
95
|
+
return map;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Where a phone number routes, in words a person reads at a glance — not the raw NetSapiens dial
|
|
99
|
+
* rule fields. Pure; looks the destination user up in `userByExt` ({@link usersByExt}) so it can
|
|
100
|
+
* name a real extension or a system object (`system-queue` → `queue`, `system-aa` → `aa`, and so
|
|
101
|
+
* on) rather than just echoing back an extension number.
|
|
102
|
+
*
|
|
103
|
+
* - The destination names a REAL extension → `to user <ext> — <First Last>` (the name is omitted,
|
|
104
|
+
* dash and all, when both name fields are blank).
|
|
105
|
+
* - The destination names a SYSTEM user (a queue, an attendant, a time-of-day router) →
|
|
106
|
+
* `to <kind> <ext> — <name>`, `kind` being the `service-code` with its `system-` prefix stripped
|
|
107
|
+
* (`queue`, `aa`, `tod`, the raw code for anything else, or `system` when stripping leaves
|
|
108
|
+
* nothing — a bare `system-` service code); name omitted the same way.
|
|
109
|
+
* - The destination is set but names nobody NetSapiens knows about → `to <application> <dest>`
|
|
110
|
+
* (`dial-rule-application` with a leading `to-` stripped, so `to-user` reads as `user`; falls back
|
|
111
|
+
* to `user` itself when the application is blank), plus `@<host>` whenever
|
|
112
|
+
* `dial-rule-translation-destination-host` is non-empty — this module has no domain to compare it
|
|
113
|
+
* against, so any non-empty host is shown.
|
|
114
|
+
* - No destination but an application is set → `to <application>` (`to-connection` → `to connection`,
|
|
115
|
+
* `to-voicemail` → `to voicemail`).
|
|
116
|
+
* - Neither is set → `''`.
|
|
117
|
+
*/
|
|
118
|
+
export function destinationOf(p, userByExt) {
|
|
119
|
+
const dest = str(p['dial-rule-translation-destination-user']);
|
|
120
|
+
const app = str(p['dial-rule-application']).replace(/^to-/i, '');
|
|
121
|
+
const host = str(p['dial-rule-translation-destination-host']);
|
|
122
|
+
if (dest) {
|
|
123
|
+
const u = userByExt.get(dest);
|
|
124
|
+
if (u) {
|
|
125
|
+
const name = `${str(u['name-first-name'])} ${str(u['name-last-name'])}`.trim();
|
|
126
|
+
if (isSystemUser(u)) {
|
|
127
|
+
const kind = str(u['service-code']).replace(/^system-/i, '') || 'system';
|
|
128
|
+
return name ? `to ${kind} ${dest} — ${name}` : `to ${kind} ${dest}`;
|
|
129
|
+
}
|
|
130
|
+
return name ? `to user ${dest} — ${name}` : `to user ${dest}`;
|
|
131
|
+
}
|
|
132
|
+
const hostPart = host ? `@${host}` : '';
|
|
133
|
+
return `to ${app || 'user'} ${dest}${hostPart}`;
|
|
134
|
+
}
|
|
135
|
+
return app ? `to ${app}` : '';
|
|
136
|
+
}
|
|
78
137
|
/**
|
|
79
138
|
* The items behind every count. Pure. Fields are copied by name from an allowlist; no record passes
|
|
80
139
|
* through, so a device's MAC or SIP password cannot reach a consumer by accident.
|
|
@@ -112,10 +171,13 @@ export function listDomainInventory(snapshot) {
|
|
|
112
171
|
const item = extensionItem(u, devicesByUser[ext] ?? []);
|
|
113
172
|
(isSystemUser(u) ? systemUsers : extensions).push(item);
|
|
114
173
|
}
|
|
174
|
+
const userByExt = usersByExt(users);
|
|
115
175
|
const dids = phonenumbers.map((p) => {
|
|
116
176
|
const number = str(p.phonenumber);
|
|
117
177
|
const kind = isTollFree(number) ? 'tollFree' : 'local';
|
|
118
|
-
|
|
178
|
+
const destination = destinationOf(p, userByExt);
|
|
179
|
+
const description = str(p['dial-rule-description']);
|
|
180
|
+
return { key: identityKey('did', number, JSON.stringify({ number, kind })), number, kind, destination, description };
|
|
119
181
|
});
|
|
120
182
|
const e911Addresses = addresses.map((a, i) => {
|
|
121
183
|
const id = str(a['emergency-address-id']);
|
package/dist/inventory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inventory.js","sourceRoot":"","sources":["../src/inventory.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"inventory.js","sourceRoot":"","sources":["../src/inventory.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dszp/netsapiens-lib",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Portable, Node-free NetSapiens toolkit: split read/write API clients, JWT (ns_t) validation, and a snapshot -> FlowGraph -> Mermaid call-flow resolver/renderer. Runs unchanged in a Cloudflare Worker, Node, or the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,6 +14,7 @@ const snap: Snapshot = {
|
|
|
14
14
|
{ user: '102', site: 'South', 'service-code': '', 'emergency-address-id': 'a-2' },
|
|
15
15
|
{ user: '103', site: '', 'service-code': '' }, // no site
|
|
16
16
|
{ user: '701', site: 'North', 'service-code': 'system-queue' }, // a queue is a user too
|
|
17
|
+
{ user: '702', site: '', 'service-code': 'system-tod' }, // a site-less system user
|
|
17
18
|
],
|
|
18
19
|
phonenumbers: [
|
|
19
20
|
{ phonenumber: '13175550100', 'dial-rule-application': 'to-user', 'dial-rule-translation-destination-user': '100' },
|
|
@@ -21,6 +22,7 @@ const snap: Snapshot = {
|
|
|
21
22
|
{ phonenumber: '13175550102', 'dial-rule-application': 'to-connection', 'dial-rule-translation-destination-user': '' },
|
|
22
23
|
{ phonenumber: '13175550103', 'dial-rule-application': 'to-user', 'dial-rule-translation-destination-user': '103' },
|
|
23
24
|
{ phonenumber: '13175550104', 'dial-rule-application': 'to-user', 'dial-rule-translation-destination-user': '999' },
|
|
25
|
+
{ phonenumber: '13175550106', 'dial-rule-application': 'to-user', 'dial-rule-translation-destination-user': '702' },
|
|
24
26
|
],
|
|
25
27
|
addresses: [
|
|
26
28
|
{ 'emergency-address-id': 'a-1', 'address-name': 'Shared' },
|
|
@@ -41,14 +43,36 @@ ok(a['ext:100']!.site === 'North' && a['ext:100']!.how === 'own-site', 'an exten
|
|
|
41
43
|
ok(a['ext:103']!.site === null && a['ext:103']!.how === 'unattributed:no-site', 'an extension with a blank site is unattributed:no-site');
|
|
42
44
|
|
|
43
45
|
ok(a['did:13175550100']!.site === 'North' && a['did:13175550100']!.how === 'via-user:100', 'a number routed to a user inherits that user\'s site');
|
|
44
|
-
ok(a['did:13175550101']!.site ===
|
|
46
|
+
ok(a['did:13175550101']!.site === 'North' && a['did:13175550101']!.how === 'via-user:701', 'a number routed to a system user with a site inherits that site, same as a real user');
|
|
47
|
+
ok(a['did:13175550106']!.site === null && a['did:13175550106']!.how === 'unattributed:routed-to:system-tod', 'a number routed to a site-less system user names its service code');
|
|
45
48
|
ok(a['did:13175550102']!.site === null && a['did:13175550102']!.how === 'unattributed:routed-to:to-connection', 'a number routed to a connection names the application');
|
|
46
49
|
ok(a['did:13175550103']!.site === null && a['did:13175550103']!.how === 'unattributed:no-site', 'a number routed to a site-less user is no-site, not routed-to');
|
|
47
50
|
ok(a['did:13175550104']!.site === null && a['did:13175550104']!.how === 'unattributed:routed-to:to-user', 'a number whose destination user does not exist falls back to the application name');
|
|
48
51
|
|
|
49
|
-
ok(a['addr:a-1']!.site === null && a['addr:a-1']!.
|
|
50
|
-
|
|
51
|
-
ok(a['addr:a-
|
|
52
|
+
ok(a['addr:a-1']!.site === null && JSON.stringify(a['addr:a-1']!.sites) === JSON.stringify(['North', 'South']) && a['addr:a-1']!.how === 'via-users:100,101',
|
|
53
|
+
'an address referenced from two sites carries BOTH, sorted, with no single site and no shared-across reason');
|
|
54
|
+
ok(a['addr:a-2']!.site === 'South' && JSON.stringify(a['addr:a-2']!.sites) === JSON.stringify(['South']) && a['addr:a-2']!.how === 'via-users:102', 'an address referenced from one site follows it and names the users');
|
|
55
|
+
ok(a['addr:a-3']!.site === null && a['addr:a-3']!.sites.length === 0 && a['addr:a-3']!.how === 'unattributed:unreferenced', 'an address nobody references is unreferenced');
|
|
56
|
+
|
|
57
|
+
// `sites` is the list form of `site` for every kind that has exactly one, so a consumer can read the
|
|
58
|
+
// one field and never branch on the kind.
|
|
59
|
+
ok(JSON.stringify(a['ext:100']!.sites) === JSON.stringify(['North']), 'an extension carries its own site as a one-entry list');
|
|
60
|
+
ok(a['ext:103']!.sites.length === 0, 'and a site-less one carries an empty list');
|
|
61
|
+
ok(JSON.stringify(a['did:13175550100']!.sites) === JSON.stringify(['North']), 'a number carries the site it inherited');
|
|
62
|
+
ok(a['did:13175550102']!.sites.length === 0, 'and an unattributed number carries none');
|
|
63
|
+
ok(JSON.stringify(a['sms:13175550100']!.sites) === JSON.stringify(['North']), 'an SMS number carries the site of the user it is enabled on');
|
|
64
|
+
ok(a['sms:13175550199']!.sites.length === 0, 'and an unknown one carries none');
|
|
65
|
+
ok(Object.values(a).every((v) => JSON.stringify(v.sites) === JSON.stringify([...new Set(v.sites)].sort())), 'every sites list is unique and sorted');
|
|
66
|
+
ok(Object.values(a).every((v) => (v.site === null ? true : v.sites.length === 1 && v.sites[0] === v.site)), 'a single site is exactly the one-entry list, and never disagrees with it');
|
|
67
|
+
|
|
68
|
+
// An address whose referencing users have no site at all: still unattributed, and the reason says why.
|
|
69
|
+
const noSiteAddr = attributeDomainInventory({
|
|
70
|
+
...snap,
|
|
71
|
+
users: [{ user: '104', site: '', 'service-code': '', 'emergency-address-id': 'a-4' }],
|
|
72
|
+
addresses: [{ 'emergency-address-id': 'a-4' }],
|
|
73
|
+
}).items;
|
|
74
|
+
ok(noSiteAddr['addr:a-4']!.how === 'unattributed:no-site' && noSiteAddr['addr:a-4']!.sites.length === 0,
|
|
75
|
+
'an address whose referencing users have no site is no-site, with no sites to name');
|
|
52
76
|
|
|
53
77
|
ok(a['sms:13175550100']!.site === 'North' && a['sms:13175550100']!.how === 'via-user:100', 'an SMS number follows the user it is enabled on');
|
|
54
78
|
ok(a['sms:13175550102']!.site === null && a['sms:13175550102']!.how === 'unattributed:no-site', 'an SMS number on a site-less user is no-site');
|
package/src/attribution.ts
CHANGED
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
* ## The rules
|
|
14
14
|
*
|
|
15
15
|
* - An extension: its own `site` (`own-site`), or `unattributed:no-site` when blank.
|
|
16
|
-
* - A number: the site of its `dial-rule-translation-destination-user` when that names a
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* `routed-to:<that user's service-code>`; when
|
|
20
|
-
*
|
|
21
|
-
* - An address:
|
|
22
|
-
*
|
|
23
|
-
*
|
|
16
|
+
* - A number: the site of its `dial-rule-translation-destination-user` when that names a user WITH a
|
|
17
|
+
* site (`via-user:<ext>`) — real or system, whatever the application (`to-user`, `to-single-device`);
|
|
18
|
+
* a queue at the North site is a North number. Only a site-less system user falls back to
|
|
19
|
+
* `routed-to:<that user's service-code>`; when the destination names nobody, `routed-to:<application>`;
|
|
20
|
+
* a site-less real user is `no-site`.
|
|
21
|
+
* - An address: EVERY site a REAL extension referencing it sits on (`via-users:<exts>`), in `sites`.
|
|
22
|
+
* An address is a fact about a PLACE, and users on four sites can legitimately reference one — so
|
|
23
|
+
* the multi-site case is not a failure to attribute, it is the answer. `site` is the single site
|
|
24
|
+
* when there is exactly one and `null` otherwise, so a consumer that can only hold one still reads
|
|
25
|
+
* the unambiguous case correctly. `unreferenced` when no real extension names it; `no-site` when the
|
|
26
|
+
* ones that do have no site.
|
|
24
27
|
* - An SMS number: the site of the user whose per-user list carries it (`via-user:<ext>`), or
|
|
25
28
|
* `sms-user-unknown` when no per-user list does — including when the per-user read was never
|
|
26
29
|
* made. Never guessed from the domain-level list, which does not say.
|
|
@@ -31,18 +34,30 @@
|
|
|
31
34
|
* put an item's KEY (which may be a derived `~hash`) beside its RECORD's routing fields. That
|
|
32
35
|
* invariant is this library's own, and this is the one place allowed to lean on it.
|
|
33
36
|
*/
|
|
34
|
-
import { isSystemUser, listDomainInventory, str } from './inventory.js';
|
|
37
|
+
import { isSystemUser, listDomainInventory, str, usersByExt } from './inventory.js';
|
|
35
38
|
import type { Rec, Snapshot } from './model.js';
|
|
36
39
|
|
|
37
40
|
export interface ItemAttribution {
|
|
38
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* The single site this item belongs to; `null` when there is not exactly one — nothing placed it, or
|
|
43
|
+
* (addresses only) several did. Read {@link sites} to tell those two apart.
|
|
44
|
+
*/
|
|
39
45
|
site: string | null;
|
|
46
|
+
/**
|
|
47
|
+
* EVERY site this item belongs to, unique and sorted. One entry wherever {@link site} is set, none
|
|
48
|
+
* where nothing placed it, and — for an address referenced from several sites — one per site.
|
|
49
|
+
*
|
|
50
|
+
* The wider field, and the one a consumer splitting a domain between accounts should read: only an
|
|
51
|
+
* ADDRESS can carry more than one, because only an address is a fact about a place rather than about
|
|
52
|
+
* a user, a number or a route.
|
|
53
|
+
*/
|
|
54
|
+
sites: string[];
|
|
40
55
|
/** `own-site` | `via-user:<ext>` | `via-users:<ext,ext>` | `unattributed:<reason>`. */
|
|
41
56
|
how: string;
|
|
42
57
|
}
|
|
43
58
|
export interface DomainAttribution { items: Record<string, ItemAttribution> }
|
|
44
59
|
|
|
45
|
-
const none = (reason: string): ItemAttribution => ({ site: null, how: `unattributed:${reason}` });
|
|
60
|
+
const none = (reason: string): ItemAttribution => ({ site: null, sites: [], how: `unattributed:${reason}` });
|
|
46
61
|
|
|
47
62
|
export function attributeDomainInventory(snapshot: Snapshot): DomainAttribution {
|
|
48
63
|
const users: Rec[] = Array.isArray(snapshot.users) ? snapshot.users : [];
|
|
@@ -53,11 +68,12 @@ export function attributeDomainInventory(snapshot: Snapshot): DomainAttribution
|
|
|
53
68
|
const d = listDomainInventory(snapshot);
|
|
54
69
|
const items: Record<string, ItemAttribution> = {};
|
|
55
70
|
|
|
56
|
-
const userByExt =
|
|
57
|
-
|
|
71
|
+
const userByExt = usersByExt(users);
|
|
72
|
+
/** The single-site verdict, with `sites` kept in step so the two fields can never disagree. */
|
|
73
|
+
const one = (site: string, how: string): ItemAttribution => ({ site, sites: [site], how });
|
|
58
74
|
|
|
59
75
|
// Extensions: the detail already carries `site`, so no record is needed here.
|
|
60
|
-
for (const x of d.extensions) items[x.key] = x.site ?
|
|
76
|
+
for (const x of d.extensions) items[x.key] = x.site ? one(x.site, 'own-site') : none('no-site');
|
|
61
77
|
|
|
62
78
|
// Numbers, by index against `phonenumbers`.
|
|
63
79
|
for (let i = 0; i < d.dids.length; i++) {
|
|
@@ -66,9 +82,9 @@ export function attributeDomainInventory(snapshot: Snapshot): DomainAttribution
|
|
|
66
82
|
const dest = str(p['dial-rule-translation-destination-user']);
|
|
67
83
|
const u = dest ? userByExt.get(dest) : undefined;
|
|
68
84
|
if (!u) { items[key] = none(`routed-to:${app}`); continue; }
|
|
69
|
-
if (isSystemUser(u)) { items[key] = none(`routed-to:${str(u['service-code'])}`); continue; }
|
|
70
85
|
const site = str(u.site);
|
|
71
|
-
items[key] =
|
|
86
|
+
if (!site) { items[key] = isSystemUser(u) ? none(`routed-to:${str(u['service-code'])}`) : none('no-site'); continue; }
|
|
87
|
+
items[key] = one(site, `via-user:${dest}`);
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
// Addresses, by index against `addresses`; referenced by REAL extensions only.
|
|
@@ -84,9 +100,12 @@ export function attributeDomainInventory(snapshot: Snapshot): DomainAttribution
|
|
|
84
100
|
const who = id ? refs.get(id) ?? [] : [];
|
|
85
101
|
if (!who.length) { items[key] = none('unreferenced'); continue; }
|
|
86
102
|
const sites = [...new Set(who.map((u) => str(u.site)).filter(Boolean))].sort();
|
|
87
|
-
if (sites.length === 0) items[key] = none('no-site');
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
if (sites.length === 0) { items[key] = none('no-site'); continue; }
|
|
104
|
+
// SEVERAL SITES IS AN ANSWER, not a failure. The referencing users name every place this address
|
|
105
|
+
// is used, and a consumer billing per site needs all of them; `site` still answers only the
|
|
106
|
+
// unambiguous case, which is what keeps a one-site consumer correct without reading `sites`.
|
|
107
|
+
const how = `via-users:${who.map((u) => str(u.user)).filter(Boolean).sort().join(',')}`;
|
|
108
|
+
items[key] = sites.length === 1 ? one(sites[0]!, how) : { site: null, sites, how };
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
// SMS numbers, by index against `smsnumbers`, joined to a user through the per-user lists.
|
|
@@ -98,7 +117,7 @@ export function attributeDomainInventory(snapshot: Snapshot): DomainAttribution
|
|
|
98
117
|
const u = ext ? userByExt.get(ext) : undefined;
|
|
99
118
|
if (!u) { items[key] = none('sms-user-unknown'); continue; }
|
|
100
119
|
const site = str(u.site);
|
|
101
|
-
items[key] = site ?
|
|
120
|
+
items[key] = site ? one(site, `via-user:${ext}`) : none('no-site');
|
|
102
121
|
}
|
|
103
122
|
|
|
104
123
|
return { items };
|
package/src/index.ts
CHANGED
|
@@ -37,7 +37,7 @@ export {
|
|
|
37
37
|
} from './html.js';
|
|
38
38
|
export { resolveSvgSize, rasterizerScript } from './raster.js';
|
|
39
39
|
export { NsClient, NsApiError, assertBareServer, fetchDomainSnapshot, listDomains, asArray, type NsClientConfig, type FetchSnapshotOptions } from './nsClient.js';
|
|
40
|
-
export { countDomainInventory, countInventoryDetail, listDomainInventory, itemsFor, itemLabel, type DomainInventory, type DomainInventoryDetail, type ExtensionItem, type NumberItem, type AddressItem, type SmsItem, type InventoryItem } from './inventory.js';
|
|
40
|
+
export { countDomainInventory, countInventoryDetail, listDomainInventory, itemsFor, itemLabel, destinationOf, usersByExt, type DomainInventory, type DomainInventoryDetail, type ExtensionItem, type NumberItem, type AddressItem, type SmsItem, type InventoryItem } from './inventory.js';
|
|
41
41
|
export { attributeDomainInventory, type DomainAttribution, type ItemAttribution } from './attribution.js';
|
|
42
42
|
export { NsWriteClient, type NsWriteClientConfig } from './nsWriteClient.js';
|
|
43
43
|
export {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Offline test for the domain inventory counter. pnpm test:inventory */
|
|
2
|
-
import { countDomainInventory, listDomainInventory, itemsFor, itemLabel } from './inventory.js';
|
|
3
|
-
import type { Snapshot } from './model.js';
|
|
2
|
+
import { countDomainInventory, listDomainInventory, itemsFor, itemLabel, destinationOf, usersByExt } from './inventory.js';
|
|
3
|
+
import type { Rec, Snapshot } from './model.js';
|
|
4
4
|
|
|
5
5
|
let pass = 0, fail = 0;
|
|
6
6
|
const ok = (c: boolean, m: string) => { c ? pass++ : fail++; console.log(`${c ? 'PASS' : 'FAIL'} ${m}`); };
|
|
@@ -14,7 +14,7 @@ const snap: Snapshot = {
|
|
|
14
14
|
{ user: '103', 'user-scope': 'Basic User', 'service-code': '' },
|
|
15
15
|
{ user: '104', 'user-scope': 'Basic User', 'service-code': '' },
|
|
16
16
|
{ user: '700', 'user-scope': 'Basic User', 'service-code': 'system-aa' },
|
|
17
|
-
{ user: '701', 'user-scope': 'Basic User', 'service-code': 'system-queue' },
|
|
17
|
+
{ user: '701', 'user-scope': 'Basic User', 'service-code': 'system-queue', 'name-first-name': 'Sales' },
|
|
18
18
|
{ user: '702', 'user-scope': 'Basic User', 'service-code': 'system-tod' },
|
|
19
19
|
],
|
|
20
20
|
devicesByUser: {
|
|
@@ -28,8 +28,10 @@ const snap: Snapshot = {
|
|
|
28
28
|
'103': [{ aor: 'sip:103t@acme.example', 'device-models-model': 'Teams' }],
|
|
29
29
|
},
|
|
30
30
|
phonenumbers: [
|
|
31
|
-
{ phonenumber: '13175550100'
|
|
32
|
-
{ phonenumber: '
|
|
31
|
+
{ phonenumber: '13175550100', 'dial-rule-translation-destination-user': '100', 'dial-rule-description': ' Portal Created: User - 1001 ' },
|
|
32
|
+
{ phonenumber: '13175550101' },
|
|
33
|
+
{ phonenumber: '18005550102', 'dial-rule-translation-destination-user': '701' },
|
|
34
|
+
{ phonenumber: '18335550103' },
|
|
33
35
|
],
|
|
34
36
|
addresses: [
|
|
35
37
|
{ 'emergency-address-id': 'a-1', 'address-name': 'HQ', 'address-line-1': '1 Main St', 'address-city': 'Springfield' },
|
|
@@ -91,15 +93,33 @@ ok(sysDev.devices.total === 0, 'a system user device is not counted');
|
|
|
91
93
|
ok(e100.site === 'North', '[list] site is carried');
|
|
92
94
|
ok(e100.deviceCount === 1 && e100.deviceModels[0] === 'Yealink T54W', '[list] device count and models, never the MAC');
|
|
93
95
|
ok(e100.teams === false, '[list] a desk phone is not Teams');
|
|
96
|
+
const e101 = d.extensions.find((x) => x.ext === '101')!;
|
|
97
|
+
ok(
|
|
98
|
+
JSON.stringify(e101.devices) === JSON.stringify([
|
|
99
|
+
{ name: '101a', model: 'Yealink T54W', teams: false },
|
|
100
|
+
{ name: '101b', model: 'Yealink T31P', teams: false },
|
|
101
|
+
{ name: '101c', model: '(unknown)', teams: false },
|
|
102
|
+
]),
|
|
103
|
+
'[list] devices lists every device with its name, model and teams flag, in order',
|
|
104
|
+
);
|
|
94
105
|
const e103 = d.extensions.find((x) => x.ext === '103')!;
|
|
95
106
|
ok(e103.teams === true, '[list] a device whose aor local part is <ext>t marks the extension Teams-connected');
|
|
96
107
|
ok(e103.deviceCount === 0 && e103.deviceModels.length === 0, '[list] and that connector is not counted as a device');
|
|
108
|
+
ok(
|
|
109
|
+
JSON.stringify(e103.devices) === JSON.stringify([{ name: '103t', model: '', teams: true }]),
|
|
110
|
+
'[list] devices includes the Teams connector with an empty model, even though deviceCount excludes it',
|
|
111
|
+
);
|
|
112
|
+
ok(d.extensions.find((x) => x.ext === '104')!.devices.length === 0, '[list] no devices means an empty devices list, not a throw');
|
|
97
113
|
ok(d.extensions.find((x) => x.ext === '101')!.transcription === true, '[list] transcription flag');
|
|
98
114
|
ok(d.extensions.find((x) => x.ext === '103')!.name === '', '[list] a user with no name has an empty name, not "undefined undefined"');
|
|
99
115
|
ok(d.extensions.find((x) => x.ext === '103')!.anyDevice === true, '[list] an extension with only the Teams connector still has a device');
|
|
100
116
|
ok(d.extensions.find((x) => x.ext === '104')!.anyDevice === false, '[list] and one with nothing has none');
|
|
101
117
|
ok(d.dids.length === 4 && d.dids.filter((n) => n.kind === 'tollFree').length === 2, '[list] numbers with kind');
|
|
102
118
|
ok(d.dids[0]!.key === 'did:13175550100', '[list] a number key is did:<phonenumber>');
|
|
119
|
+
ok(d.dids[0]!.destination === 'to user 100 — Ann Lee', '[list] a number routed to a real user names them');
|
|
120
|
+
ok(d.dids[0]!.description === 'Portal Created: User - 1001', '[list] dial-rule-description is carried and trimmed');
|
|
121
|
+
ok(d.dids[1]!.destination === '' && d.dids[1]!.description === '', '[list] a number with no routing fields is blank, not "undefined"');
|
|
122
|
+
ok(d.dids[2]!.destination === 'to queue 701 — Sales', '[list] a number routed to a system queue names the queue');
|
|
103
123
|
ok(d.e911Addresses.length === 2, '[list] two address items');
|
|
104
124
|
ok(d.e911Addresses[0]!.key === 'addr:a-1', '[list] an address key is addr:<emergency-address-id>');
|
|
105
125
|
ok(d.e911Addresses[0]!.label === 'HQ — 1 Main St, Springfield', '[list] an address label is name — line 1, city');
|
|
@@ -209,5 +229,29 @@ ok(sysDev.devices.total === 0, 'a system user device is not counted');
|
|
|
209
229
|
ok(derived(forward) === derived(reversed), '[order] reversing the phonenumbers array leaves the did:~ keys unchanged');
|
|
210
230
|
}
|
|
211
231
|
|
|
232
|
+
// ── usersByExt / destinationOf ───────────────────────────────────────────────────────────────────────
|
|
233
|
+
{
|
|
234
|
+
const usersFx: Rec[] = [
|
|
235
|
+
{ user: '100', 'name-first-name': 'Ann', 'name-last-name': 'Lee' },
|
|
236
|
+
{ user: '701', 'service-code': 'system-queue', 'name-first-name': 'Sales' },
|
|
237
|
+
{ user: '702', 'service-code': 'system-' },
|
|
238
|
+
];
|
|
239
|
+
const byExt = usersByExt(usersFx);
|
|
240
|
+
ok(byExt.size === 3, '[usersByExt] one entry per non-blank user');
|
|
241
|
+
const deduped = usersByExt([...usersFx, { user: '100', 'name-first-name': 'Duplicate' }]);
|
|
242
|
+
ok(deduped.get('100')!['name-first-name'] === 'Ann', '[usersByExt] the FIRST record for a repeated extension wins');
|
|
243
|
+
|
|
244
|
+
ok(destinationOf({ 'dial-rule-translation-destination-user': '100' }, byExt) === 'to user 100 — Ann Lee', '[destinationOf] a real extension names the person');
|
|
245
|
+
ok(destinationOf({ 'dial-rule-translation-destination-user': '701' }, byExt) === 'to queue 701 — Sales', '[destinationOf] a system-queue destination strips the system- prefix and names the queue');
|
|
246
|
+
ok(destinationOf({ 'dial-rule-translation-destination-user': '702' }, byExt) === 'to system 702', '[destinationOf] a bare "system-" service code has nothing left to strip, so kind falls back to "system" rather than a double space');
|
|
247
|
+
ok(destinationOf({ 'dial-rule-translation-destination-user': '999', 'dial-rule-application': 'to-user' }, byExt) === 'to user 999', '[destinationOf] an unknown destination falls back to the application, to- stripped');
|
|
248
|
+
ok(
|
|
249
|
+
destinationOf({ 'dial-rule-translation-destination-user': '999', 'dial-rule-translation-destination-host': 'other.example' }, byExt) === 'to user 999@other.example',
|
|
250
|
+
'[destinationOf] an unknown destination with a host appends @host',
|
|
251
|
+
);
|
|
252
|
+
ok(destinationOf({ 'dial-rule-application': 'to-connection' }, byExt) === 'to connection', '[destinationOf] no destination, application only — to- stripped so it does not read "to to-connection"');
|
|
253
|
+
ok(destinationOf({}, byExt) === '', '[destinationOf] neither field set is empty, not "to undefined"');
|
|
254
|
+
}
|
|
255
|
+
|
|
212
256
|
console.log(`\n${pass} passed, ${fail} failed`);
|
|
213
257
|
if (fail) process.exit(1);
|
package/src/inventory.ts
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* ## Counts, and the lists behind them
|
|
8
8
|
*
|
|
9
9
|
* A device record from NetSapiens carries the SIP registration password. `listDomainInventory` builds
|
|
10
|
-
* per-item lists — an extension's name and site, a number's kind, an address's
|
|
11
|
-
* allowlist of named fields, and `countDomainInventory` is a fold over those same
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* per-item lists — an extension's name and site, a number's kind and routing destination, an address's
|
|
11
|
+
* label — from an allowlist of named fields, and `countDomainInventory` is a fold over those same
|
|
12
|
+
* lists. The allowlist now includes a device's NAME (its `aor` local part, e.g. `101b` — the same
|
|
13
|
+
* short id the portal shows) alongside its model; a device's MAC, SIP password and email never appear,
|
|
14
|
+
* and nothing here should be added that carries one.
|
|
14
15
|
*
|
|
15
16
|
* ## Every countable dimension is a numeric leaf
|
|
16
17
|
*
|
|
@@ -89,10 +90,27 @@ export interface ExtensionItem {
|
|
|
89
90
|
deviceCount: number;
|
|
90
91
|
/** `device-models-model` per handset, `(unknown)` when blank. Never the MAC. */
|
|
91
92
|
deviceModels: string[];
|
|
93
|
+
/**
|
|
94
|
+
* Every device on this extension, in record order, connector included: `name` is the `aor` local
|
|
95
|
+
* part (`sip:101b@acme.example` → `101b`) — the device NAME as the portal shows it — `model` is
|
|
96
|
+
* `device-models-model` (`(unknown)` when blank on a handset, `''` for the Teams connector, which
|
|
97
|
+
* has no model), and `teams` marks the connector entry itself. `deviceCount`/`deviceModels`/`teams`
|
|
98
|
+
* above stay handset-only; this list is the one place a connector's own row shows up. Never the MAC
|
|
99
|
+
* or the SIP password.
|
|
100
|
+
*/
|
|
101
|
+
devices: Array<{ name: string; model: string; teams: boolean }>;
|
|
92
102
|
/** `deviceCount > 0 || teams` — has a device of any kind, handset or connector. */
|
|
93
103
|
anyDevice: boolean;
|
|
94
104
|
}
|
|
95
|
-
export interface NumberItem {
|
|
105
|
+
export interface NumberItem {
|
|
106
|
+
key: string /* did:<phonenumber>, or did:~<hash> when the number is blank */;
|
|
107
|
+
number: string;
|
|
108
|
+
kind: 'local' | 'tollFree';
|
|
109
|
+
/** Where the number routes, for a person: see {@link destinationOf}. `''` when the record says nothing. */
|
|
110
|
+
destination: string;
|
|
111
|
+
/** `dial-rule-description` trimmed — the note the portal writes ("Portal Created: User - 1001"); `''` when blank. */
|
|
112
|
+
description: string;
|
|
113
|
+
}
|
|
96
114
|
export interface AddressItem { key: string /* addr:<emergency-address-id>, or addr:~<hash> when the id is blank */; label: string }
|
|
97
115
|
export interface SmsItem { key: string /* sms:<number>, or sms:~<hash> when the number is blank */; number: string }
|
|
98
116
|
export type InventoryItem = ExtensionItem | NumberItem | AddressItem | SmsItem;
|
|
@@ -188,10 +206,71 @@ function extensionItem(u: Rec, devices: Rec[]): ExtensionItem {
|
|
|
188
206
|
// A device whose model is blank is listed under a named bucket rather than dropped: a missing
|
|
189
207
|
// model is a provisioning gap worth seeing, and a silently smaller total hides it.
|
|
190
208
|
deviceModels: handsets.map((d) => str(d['device-models-model']) || '(unknown)'),
|
|
209
|
+
// Every device, including the Teams connector — this is a display list, not a seat count.
|
|
210
|
+
devices: devices.map((d) => {
|
|
211
|
+
const isTeams = ext ? aorLocal(d) === `${ext}t` : false;
|
|
212
|
+
return { name: aorLocal(d), model: isTeams ? '' : str(d['device-models-model']) || '(unknown)', teams: isTeams };
|
|
213
|
+
}),
|
|
191
214
|
anyDevice: handsets.length > 0 || teams,
|
|
192
215
|
};
|
|
193
216
|
}
|
|
194
217
|
|
|
218
|
+
/**
|
|
219
|
+
* One record per non-blank `user`, first one wins. The same rule `attribution.ts` needs to join a
|
|
220
|
+
* number's `dial-rule-translation-destination-user` (or an SMS number, or an address) back to the
|
|
221
|
+
* user it belongs to — extracted here so there is exactly one copy of it in the library.
|
|
222
|
+
*/
|
|
223
|
+
export function usersByExt(users: Rec[]): Map<string, Rec> {
|
|
224
|
+
const map = new Map<string, Rec>();
|
|
225
|
+
for (const u of users) {
|
|
226
|
+
const ext = str(u.user);
|
|
227
|
+
if (ext && !map.has(ext)) map.set(ext, u);
|
|
228
|
+
}
|
|
229
|
+
return map;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Where a phone number routes, in words a person reads at a glance — not the raw NetSapiens dial
|
|
234
|
+
* rule fields. Pure; looks the destination user up in `userByExt` ({@link usersByExt}) so it can
|
|
235
|
+
* name a real extension or a system object (`system-queue` → `queue`, `system-aa` → `aa`, and so
|
|
236
|
+
* on) rather than just echoing back an extension number.
|
|
237
|
+
*
|
|
238
|
+
* - The destination names a REAL extension → `to user <ext> — <First Last>` (the name is omitted,
|
|
239
|
+
* dash and all, when both name fields are blank).
|
|
240
|
+
* - The destination names a SYSTEM user (a queue, an attendant, a time-of-day router) →
|
|
241
|
+
* `to <kind> <ext> — <name>`, `kind` being the `service-code` with its `system-` prefix stripped
|
|
242
|
+
* (`queue`, `aa`, `tod`, the raw code for anything else, or `system` when stripping leaves
|
|
243
|
+
* nothing — a bare `system-` service code); name omitted the same way.
|
|
244
|
+
* - The destination is set but names nobody NetSapiens knows about → `to <application> <dest>`
|
|
245
|
+
* (`dial-rule-application` with a leading `to-` stripped, so `to-user` reads as `user`; falls back
|
|
246
|
+
* to `user` itself when the application is blank), plus `@<host>` whenever
|
|
247
|
+
* `dial-rule-translation-destination-host` is non-empty — this module has no domain to compare it
|
|
248
|
+
* against, so any non-empty host is shown.
|
|
249
|
+
* - No destination but an application is set → `to <application>` (`to-connection` → `to connection`,
|
|
250
|
+
* `to-voicemail` → `to voicemail`).
|
|
251
|
+
* - Neither is set → `''`.
|
|
252
|
+
*/
|
|
253
|
+
export function destinationOf(p: Rec, userByExt: Map<string, Rec>): string {
|
|
254
|
+
const dest = str(p['dial-rule-translation-destination-user']);
|
|
255
|
+
const app = str(p['dial-rule-application']).replace(/^to-/i, '');
|
|
256
|
+
const host = str(p['dial-rule-translation-destination-host']);
|
|
257
|
+
|
|
258
|
+
if (dest) {
|
|
259
|
+
const u = userByExt.get(dest);
|
|
260
|
+
if (u) {
|
|
261
|
+
const name = `${str(u['name-first-name'])} ${str(u['name-last-name'])}`.trim();
|
|
262
|
+
if (isSystemUser(u)) {
|
|
263
|
+
const kind = str(u['service-code']).replace(/^system-/i, '') || 'system';
|
|
264
|
+
return name ? `to ${kind} ${dest} — ${name}` : `to ${kind} ${dest}`;
|
|
265
|
+
}
|
|
266
|
+
return name ? `to user ${dest} — ${name}` : `to user ${dest}`;
|
|
267
|
+
}
|
|
268
|
+
const hostPart = host ? `@${host}` : '';
|
|
269
|
+
return `to ${app || 'user'} ${dest}${hostPart}`;
|
|
270
|
+
}
|
|
271
|
+
return app ? `to ${app}` : '';
|
|
272
|
+
}
|
|
273
|
+
|
|
195
274
|
/**
|
|
196
275
|
* The items behind every count. Pure. Fields are copied by name from an allowlist; no record passes
|
|
197
276
|
* through, so a device's MAC or SIP password cannot reach a consumer by accident.
|
|
@@ -230,10 +309,13 @@ export function listDomainInventory(snapshot: Snapshot): DomainInventoryDetail {
|
|
|
230
309
|
const item = extensionItem(u, devicesByUser[ext] ?? []);
|
|
231
310
|
(isSystemUser(u) ? systemUsers : extensions).push(item);
|
|
232
311
|
}
|
|
312
|
+
const userByExt = usersByExt(users);
|
|
233
313
|
const dids: NumberItem[] = phonenumbers.map((p) => {
|
|
234
314
|
const number = str(p.phonenumber);
|
|
235
315
|
const kind: 'local' | 'tollFree' = isTollFree(number) ? 'tollFree' : 'local';
|
|
236
|
-
|
|
316
|
+
const destination = destinationOf(p, userByExt);
|
|
317
|
+
const description = str(p['dial-rule-description']);
|
|
318
|
+
return { key: identityKey('did', number, JSON.stringify({ number, kind })), number, kind, destination, description };
|
|
237
319
|
});
|
|
238
320
|
const e911Addresses: AddressItem[] = addresses.map((a, i) => {
|
|
239
321
|
const id = str(a['emergency-address-id']);
|