@dszp/netsapiens-lib 0.5.0 → 0.7.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 +44 -8
- package/dist/attribution.d.ts +13 -1
- package/dist/attribution.d.ts.map +1 -1
- package/dist/attribution.js +20 -12
- 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.map +1 -1
- package/dist/inventory.d.ts +64 -11
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +78 -18
- package/dist/inventory.js.map +1 -1
- package/package.json +1 -1
- package/src/attribution.selftest.ts +24 -3
- package/src/attribution.ts +31 -11
- package/src/index.ts +1 -1
- package/src/inventory.selftest.ts +133 -0
- package/src/inventory.ts +128 -28
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ genuine zero from a read that never completed rather than silently undercounting
|
|
|
79
79
|
|
|
80
80
|
### Counting a domain: `countDomainInventory`
|
|
81
81
|
|
|
82
|
-
`countDomainInventory(snapshot)` is pure — it fetches nothing, and turns a `Snapshot` (from
|
|
82
|
+
`countDomainInventory(snapshot, opts?)` is pure — it fetches nothing, and turns a `Snapshot` (from
|
|
83
83
|
`fetchDomainSnapshot`, a backup, or a fixture) into a fixed tree of numeric leaves along the dimensions a
|
|
84
84
|
VoIP operator actually sells on:
|
|
85
85
|
|
|
@@ -92,7 +92,8 @@ VoIP operator actually sells on:
|
|
|
92
92
|
- `transcriptionEnabled` — extensions with voicemail transcription on.
|
|
93
93
|
- `teamsConnected` — extensions with a Microsoft Teams connector device (SIP `aor` local part
|
|
94
94
|
`<ext>t`). That connector is excluded from `devices`/`deviceCount`: it is a connector, not a handset.
|
|
95
|
-
- `dids` — phone numbers
|
|
95
|
+
- `dids` — phone numbers: `total` / `tollFree` / `local`, all three **excluding fax lines**, plus
|
|
96
|
+
`fax` and `all` (everything, `total + fax === all`). See [Fax lines](#fax-lines).
|
|
96
97
|
- `e911Addresses`, `smsNumbers` — record counts.
|
|
97
98
|
- `devices` — `total` and `byModel`, real extensions only (a system user's device is not a seat).
|
|
98
99
|
|
|
@@ -116,9 +117,33 @@ inventory.dids.tollFree; // e.g. 3
|
|
|
116
117
|
inventory.devices.byModel; // e.g. { "Yealink T54W": 12, "(unknown)": 1 }
|
|
117
118
|
```
|
|
118
119
|
|
|
120
|
+
#### Fax lines
|
|
121
|
+
|
|
122
|
+
On the portal's **Fax Server** treatment a fax line is an ordinary phone number whose dial rule hands it
|
|
123
|
+
to a fax server: `dial-rule-application: to-connection` and `dial-rule-translation-destination-host` set
|
|
124
|
+
to that server's host. The API has no fax-account endpoint and the ATA is not a device on any user, so
|
|
125
|
+
that host is the only thing that says "fax line" — and nothing in the API distinguishes an analog fax
|
|
126
|
+
from a digital one.
|
|
127
|
+
|
|
128
|
+
The host belongs to your deployment, so you supply it; **with no hosts, nothing is a fax line** and the
|
|
129
|
+
counts are exactly what they were before 0.7.0:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const inventory = countDomainInventory(snapshot, { faxServerHosts: ['203.0.113.7'] });
|
|
133
|
+
inventory.dids.fax; // 2 — billed as fax lines
|
|
134
|
+
inventory.dids.total; // the DIDs, those two NOT among them
|
|
135
|
+
inventory.dids.all; // total + fax
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Matching is on the trimmed host, case-insensitively, and on nothing else — never the
|
|
139
|
+
`dial-rule-description`, which is a note the portal writes and an operator can edit. A fax line's
|
|
140
|
+
`NumberItem.fax` is `true`, its `destination` reads `to fax server` (the host is deliberately not shown),
|
|
141
|
+
and it keeps its `kind`: a fax number is local or toll-free like any other, it is simply not counted as a
|
|
142
|
+
DID.
|
|
143
|
+
|
|
119
144
|
#### Listing a domain: `listDomainInventory`
|
|
120
145
|
|
|
121
|
-
`countDomainInventory` is a fold over `listDomainInventory(snapshot)
|
|
146
|
+
`countDomainInventory` is a fold over `listDomainInventory(snapshot, opts?)` (same options), which returns the per-item lists
|
|
122
147
|
behind those counts — for anything that shows an operator *which* extension or number a count refers to,
|
|
123
148
|
not just how many. Same allowlist discipline as the counts: a device's MAC, SIP credentials and email
|
|
124
149
|
never appear, though names and sites now do (that's the point of a list). Each item carries a stable
|
|
@@ -131,8 +156,9 @@ never appear, though names and sites now do (that's the point of a list). Each i
|
|
|
131
156
|
| `e911Addresses` | `addr:<emergency-address-id>` |
|
|
132
157
|
| `smsNumbers` | `sms:<number>` |
|
|
133
158
|
|
|
134
|
-
A number carries `destination` — where it routes, in words
|
|
135
|
-
|
|
159
|
+
A number carries `fax` (see [Fax lines](#fax-lines)) and `destination` — where it routes, in words
|
|
160
|
+
(`to user 100 — Ann Lee`, `to queue 701 — Sales`, `to fax server`), built by the exported
|
|
161
|
+
`destinationOf(p, userByExt, faxServerHosts?)` over `usersByExt(users)` — and `description`
|
|
136
162
|
(`dial-rule-description`, trimmed); an extension carries `devices`, an `{ name, model, teams }` per
|
|
137
163
|
device it has (handset and Teams connector alike), in record order — still never the MAC, SIP password
|
|
138
164
|
or email.
|
|
@@ -150,7 +176,9 @@ no separate lookup table:
|
|
|
150
176
|
| `extensions.withAnyDevice` / `extensions.withNoDevice` | extensions with / without any device (handset or Teams connector) |
|
|
151
177
|
| `transcriptionEnabled` | extensions with transcription on |
|
|
152
178
|
| `teamsConnected` | extensions with a Teams connector |
|
|
153
|
-
| `dids.total` / `dids.tollFree` / `dids.local` | phone numbers |
|
|
179
|
+
| `dids.total` / `dids.tollFree` / `dids.local` | phone numbers, **fax lines excluded** |
|
|
180
|
+
| `dids.fax` | fax lines |
|
|
181
|
+
| `dids.all` | every phone number, fax lines included |
|
|
154
182
|
| `e911Addresses` | E911 addresses |
|
|
155
183
|
| `smsNumbers` | SMS numbers |
|
|
156
184
|
|
|
@@ -172,9 +200,17 @@ against one of them needs to know which inventory items are actually its own. `a
|
|
|
172
200
|
answers that: it's pure (no account knowledge, just the snapshot) and labels every extension, number,
|
|
173
201
|
address and SMS number either `own-site` (the item's own site matches), `via-user:<ext>` /
|
|
174
202
|
`via-users:<exts>` (it's reachable only through another item that has a site), or an
|
|
175
|
-
`unattributed:<reason>` — `no-site`, `routed-to:<x>`, `
|
|
203
|
+
`unattributed:<reason>` — `no-site`, `routed-to:<x>`, `unreferenced`, or
|
|
176
204
|
`sms-user-unknown` when a domain-level SMS number can't be matched to a user because the snapshot was
|
|
177
|
-
never fetched with `includeUserSmsNumbers`.
|
|
205
|
+
never fetched with `includeUserSmsNumbers`.
|
|
206
|
+
|
|
207
|
+
Each verdict carries **`sites: string[]`** as well as `site`. They agree wherever there is one site,
|
|
208
|
+
and only an **E911 address** can carry more than one: an address is a fact about a place, and users on
|
|
209
|
+
four sites can all reference it, so `sites` names every one of them while `site` stays `null`. A
|
|
210
|
+
consumer splitting a domain between billing accounts should read `sites` — placing such an address on
|
|
211
|
+
exactly one account leaves every other referencing account's E911 line short.
|
|
212
|
+
|
|
213
|
+
Filter `listDomainInventory(snapshot)`'s items by that
|
|
178
214
|
attribution to scope a domain down to one site, then run `countInventoryDetail(detail)` over what's left
|
|
179
215
|
to get counts that agree with what you kept, rather than recomputing `countDomainInventory` against the
|
|
180
216
|
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
|
@@ -18,9 +18,12 @@
|
|
|
18
18
|
* a queue at the North site is a North number. Only a site-less system user falls back to
|
|
19
19
|
* `routed-to:<that user's service-code>`; when the destination names nobody, `routed-to:<application>`;
|
|
20
20
|
* a site-less real user is `no-site`.
|
|
21
|
-
* - An address:
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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.
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
* invariant is this library's own, and this is the one place allowed to lean on it.
|
|
33
36
|
*/
|
|
34
37
|
import { isSystemUser, listDomainInventory, str, usersByExt } from './inventory.js';
|
|
35
|
-
const none = (reason) => ({ site: null, how: `unattributed:${reason}` });
|
|
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 : [];
|
|
@@ -42,9 +45,11 @@ export function attributeDomainInventory(snapshot) {
|
|
|
42
45
|
const d = listDomainInventory(snapshot);
|
|
43
46
|
const items = {};
|
|
44
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 });
|
|
45
50
|
// Extensions: the detail already carries `site`, so no record is needed here.
|
|
46
51
|
for (const x of d.extensions)
|
|
47
|
-
items[x.key] = x.site ?
|
|
52
|
+
items[x.key] = x.site ? one(x.site, 'own-site') : none('no-site');
|
|
48
53
|
// Numbers, by index against `phonenumbers`.
|
|
49
54
|
for (let i = 0; i < d.dids.length; i++) {
|
|
50
55
|
const key = d.dids[i].key, p = phonenumbers[i] ?? {};
|
|
@@ -60,7 +65,7 @@ export function attributeDomainInventory(snapshot) {
|
|
|
60
65
|
items[key] = isSystemUser(u) ? none(`routed-to:${str(u['service-code'])}`) : none('no-site');
|
|
61
66
|
continue;
|
|
62
67
|
}
|
|
63
|
-
items[key] =
|
|
68
|
+
items[key] = one(site, `via-user:${dest}`);
|
|
64
69
|
}
|
|
65
70
|
// Addresses, by index against `addresses`; referenced by REAL extensions only.
|
|
66
71
|
const refs = new Map();
|
|
@@ -82,12 +87,15 @@ export function attributeDomainInventory(snapshot) {
|
|
|
82
87
|
continue;
|
|
83
88
|
}
|
|
84
89
|
const sites = [...new Set(who.map((u) => str(u.site)).filter(Boolean))].sort();
|
|
85
|
-
if (sites.length === 0)
|
|
90
|
+
if (sites.length === 0) {
|
|
86
91
|
items[key] = none('no-site');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 };
|
|
91
99
|
}
|
|
92
100
|
// SMS numbers, by index against `smsnumbers`, joined to a user through the per-user lists.
|
|
93
101
|
const extBySms = new Map();
|
|
@@ -107,7 +115,7 @@ export function attributeDomainInventory(snapshot) {
|
|
|
107
115
|
continue;
|
|
108
116
|
}
|
|
109
117
|
const site = str(u.site);
|
|
110
|
-
items[key] = site ?
|
|
118
|
+
items[key] = site ? one(site, `via-user:${ext}`) : none('no-site');
|
|
111
119
|
}
|
|
112
120
|
return { items };
|
|
113
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, destinationOf, usersByExt, 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 InventoryOptions, 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,aAAa,EAAE,UAAU,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.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,EAAE,aAAa,EAAE,UAAU,
|
|
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,EAAoK,MAAM,gBAAgB,CAAC;AACnT,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
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* A device record from NetSapiens carries the SIP registration password. `listDomainInventory` builds
|
|
10
10
|
* per-item lists — an extension's name and site, a number's kind and routing destination, an address's
|
|
11
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 (
|
|
13
|
-
* short id the portal shows) alongside its model; a device's MAC, SIP
|
|
14
|
-
* and nothing here should be added that carries one.
|
|
12
|
+
* lists. The allowlist now includes a device's NAME (the local part of its `device` SIP URI, falling back
|
|
13
|
+
* to `aor` — e.g. `101b`, the same short id the portal shows) alongside its model; a device's MAC, SIP
|
|
14
|
+
* password and email never appear, and nothing here should be added that carries one.
|
|
15
15
|
*
|
|
16
16
|
* ## Every countable dimension is a numeric leaf
|
|
17
17
|
*
|
|
@@ -24,6 +24,20 @@
|
|
|
24
24
|
* A user whose `service-code` is empty or does not begin with `system-`. NetSapiens models auto
|
|
25
25
|
* attendants, queues and time-of-day routers as users, and counting them as seats would overstate
|
|
26
26
|
* every domain that has any. They are counted separately, as information, and never compared.
|
|
27
|
+
*
|
|
28
|
+
* ## What counts as a fax line
|
|
29
|
+
*
|
|
30
|
+
* On the portal's "Fax Server" treatment a number is an ordinary phone number whose dial rule hands it
|
|
31
|
+
* to a fax server host — `dial-rule-application: to-connection` with
|
|
32
|
+
* `dial-rule-translation-destination-host` set to that host. There is no fax-account endpoint and the
|
|
33
|
+
* ATA is not a device on any user, so the host is the only thing in the API that says "this is a fax
|
|
34
|
+
* line", and nothing in the API can tell an analog fax from a digital one.
|
|
35
|
+
*
|
|
36
|
+
* The host is therefore the whole test, and it is the CALLER's: pass `{ faxServerHosts }` to
|
|
37
|
+
* {@link listDomainInventory} or {@link countDomainInventory}. Matching is on the trimmed host,
|
|
38
|
+
* case-insensitively, and on nothing else — not the `dial-rule-description`, which is a portal-written
|
|
39
|
+
* note an operator can edit. **With no hosts supplied nothing is a fax line**, because a library that
|
|
40
|
+
* hardcoded one deployment's fax server would be wrong everywhere else.
|
|
27
41
|
*/
|
|
28
42
|
import type { Rec, Snapshot } from './model.js';
|
|
29
43
|
export interface DomainInventory {
|
|
@@ -49,16 +63,28 @@ export interface DomainInventory {
|
|
|
49
63
|
/** Extensions whose `voicemail-transcription-enabled` is anything but empty or `no`. */
|
|
50
64
|
transcriptionEnabled: number;
|
|
51
65
|
/**
|
|
52
|
-
* Extensions with a Microsoft Teams connector device — one whose
|
|
53
|
-
*
|
|
66
|
+
* Extensions with a Microsoft Teams connector device — one whose device NAME is the extension number
|
|
67
|
+
* followed by `t` (`1000t`), which is how the TeamMate connector registers. See {@link deviceName} for
|
|
68
|
+
* which field that name is read from, and why reading the wrong one silently miscounted this.
|
|
54
69
|
* That device is NOT counted under `devices`: it is a connector, not a handset.
|
|
55
70
|
*/
|
|
56
71
|
teamsConnected: number;
|
|
57
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Phone numbers on the domain, split by NANP toll-free prefix — **fax lines excluded**.
|
|
74
|
+
*
|
|
75
|
+
* A number handed to the fax server is billed as a fax line, not as a DID, so `total`, `tollFree`
|
|
76
|
+
* and `local` all leave it out and `fax` counts it instead. `all` is every phone number the domain
|
|
77
|
+
* holds, fax lines included: `total + fax === all`.
|
|
78
|
+
*
|
|
79
|
+
* With no `faxServerHosts` supplied nothing is a fax line, `fax` is 0 and `total === all` — the
|
|
80
|
+
* numbers this returned before 0.7.0, unchanged.
|
|
81
|
+
*/
|
|
58
82
|
dids: {
|
|
59
83
|
total: number;
|
|
60
84
|
tollFree: number;
|
|
61
85
|
local: number;
|
|
86
|
+
fax: number;
|
|
87
|
+
all: number;
|
|
62
88
|
};
|
|
63
89
|
/** E911 address records on the domain. */
|
|
64
90
|
e911Addresses: number;
|
|
@@ -99,8 +125,9 @@ export interface ExtensionItem {
|
|
|
99
125
|
/** `device-models-model` per handset, `(unknown)` when blank. Never the MAC. */
|
|
100
126
|
deviceModels: string[];
|
|
101
127
|
/**
|
|
102
|
-
* Every device on this extension, in record order, connector included: `name` is the
|
|
103
|
-
*
|
|
128
|
+
* Every device on this extension, in record order, connector included: `name` is the local part of its
|
|
129
|
+
* `device` SIP URI, or of `aor` when that is all the record has (`sip:101b@acme.example` → `101b`) —
|
|
130
|
+
* the device NAME as the portal shows it — `model` is
|
|
104
131
|
* `device-models-model` (`(unknown)` when blank on a handset, `''` for the Teams connector, which
|
|
105
132
|
* has no model), and `teams` marks the connector entry itself. `deviceCount`/`deviceModels`/`teams`
|
|
106
133
|
* above stay handset-only; this list is the one place a connector's own row shows up. Never the MAC
|
|
@@ -118,6 +145,14 @@ export interface NumberItem {
|
|
|
118
145
|
key: string;
|
|
119
146
|
number: string;
|
|
120
147
|
kind: 'local' | 'tollFree';
|
|
148
|
+
/**
|
|
149
|
+
* This number is handed to a fax server — see the module doc. `false` whenever the caller supplied no
|
|
150
|
+
* `faxServerHosts`, since without a host list nothing here can tell a fax line from any other number.
|
|
151
|
+
*
|
|
152
|
+
* `kind` is still set on a fax line (a fax number is local or toll-free like any other), but the
|
|
153
|
+
* COUNTS exclude it from `dids.total`/`local`/`tollFree` and count it under `dids.fax` instead.
|
|
154
|
+
*/
|
|
155
|
+
fax: boolean;
|
|
121
156
|
/** Where the number routes, for a person: see {@link destinationOf}. `''` when the record says nothing. */
|
|
122
157
|
destination: string;
|
|
123
158
|
/** `dial-rule-description` trimmed — the note the portal writes ("Portal Created: User - 1001"); `''` when blank. */
|
|
@@ -141,6 +176,19 @@ export interface DomainInventoryDetail {
|
|
|
141
176
|
e911Addresses: AddressItem[];
|
|
142
177
|
smsNumbers: SmsItem[];
|
|
143
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* What the caller has to tell the counter that the API cannot. Optional in full: every option absent is
|
|
181
|
+
* the pre-0.7.0 behaviour, and no option changes what a snapshot has to contain.
|
|
182
|
+
*/
|
|
183
|
+
export interface InventoryOptions {
|
|
184
|
+
/**
|
|
185
|
+
* The hosts a fax line is handed to — an IP or a hostname, as it appears in
|
|
186
|
+
* `dial-rule-translation-destination-host`. Compared trimmed and case-insensitively; blanks are
|
|
187
|
+
* ignored. Absent or empty means NO number is a fax line. See the module doc for why this is the
|
|
188
|
+
* caller's to supply.
|
|
189
|
+
*/
|
|
190
|
+
faxServerHosts?: readonly string[];
|
|
191
|
+
}
|
|
144
192
|
export declare const str: (v: unknown) => string;
|
|
145
193
|
/** Is this user one of NetSapiens' internal routing objects rather than a seat? */
|
|
146
194
|
export declare function isSystemUser(user: Rec): boolean;
|
|
@@ -170,8 +218,13 @@ export declare function usersByExt(users: Rec[]): Map<string, Rec>;
|
|
|
170
218
|
* - No destination but an application is set → `to <application>` (`to-connection` → `to connection`,
|
|
171
219
|
* `to-voicemail` → `to voicemail`).
|
|
172
220
|
* - Neither is set → `''`.
|
|
221
|
+
*
|
|
222
|
+
* A FAX LINE — a number whose destination host is one of `faxServerHosts` — short-circuits all of that
|
|
223
|
+
* and reads `to fax server`, host omitted. Otherwise it would render as `to connection` (which names
|
|
224
|
+
* plumbing, not a destination) or, on a rule that also carries a destination user, as a bare IP address
|
|
225
|
+
* beside a customer's phone number. Nobody reading this line needs the fax server's address.
|
|
173
226
|
*/
|
|
174
|
-
export declare function destinationOf(p: Rec, userByExt: Map<string, Rec
|
|
227
|
+
export declare function destinationOf(p: Rec, userByExt: Map<string, Rec>, faxServerHosts?: readonly string[]): string;
|
|
175
228
|
/**
|
|
176
229
|
* The items behind every count. Pure. Fields are copied by name from an allowlist; no record passes
|
|
177
230
|
* through, so a device's MAC or SIP password cannot reach a consumer by accident.
|
|
@@ -189,9 +242,9 @@ export declare function destinationOf(p: Rec, userByExt: Map<string, Rec>): stri
|
|
|
189
242
|
* countable thing, and one derived row is more honest than two that shuffle. A blank id is a
|
|
190
243
|
* provisioning fault to fix; the fallback only keeps the distinguishable ones apart until it is.
|
|
191
244
|
*/
|
|
192
|
-
export declare function listDomainInventory(snapshot: Snapshot): DomainInventoryDetail;
|
|
245
|
+
export declare function listDomainInventory(snapshot: Snapshot, opts?: InventoryOptions): DomainInventoryDetail;
|
|
193
246
|
/** The counts, as a fold over {@link listDomainInventory} so the two can never disagree. */
|
|
194
|
-
export declare function countDomainInventory(snapshot: Snapshot): DomainInventory;
|
|
247
|
+
export declare function countDomainInventory(snapshot: Snapshot, opts?: InventoryOptions): DomainInventory;
|
|
195
248
|
/**
|
|
196
249
|
* Count an item list. Exposed separately so a consumer that has FILTERED the lists — to one site, to
|
|
197
250
|
* one billing account — gets counts that agree with what it kept, rather than re-counting the snapshot.
|
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
|
@@ -43,17 +43,48 @@ function isTollFree(raw) {
|
|
|
43
43
|
const nanp = digits.length === 11 && digits.startsWith('1') ? digits.slice(1) : digits;
|
|
44
44
|
return nanp.length === 10 && TOLL_FREE.has(nanp.slice(0, 3));
|
|
45
45
|
}
|
|
46
|
-
/** The
|
|
47
|
-
function
|
|
48
|
-
const
|
|
46
|
+
/** The fax-server hosts, trimmed, lower-cased and with blanks dropped — the shape {@link isFaxLine} tests against. */
|
|
47
|
+
function faxHosts(hosts) {
|
|
48
|
+
const out = new Set();
|
|
49
|
+
for (const h of hosts ?? []) {
|
|
50
|
+
const v = str(h).toLowerCase();
|
|
51
|
+
if (v)
|
|
52
|
+
out.add(v);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Is this number handed to a fax server? The `dial-rule-translation-destination-host` alone, matched
|
|
58
|
+
* against the caller's list — never the `dial-rule-description`, which is a note the portal writes and
|
|
59
|
+
* an operator can edit. An empty host set answers `false` for everything, which is the point: this
|
|
60
|
+
* library knows no fax server of its own.
|
|
61
|
+
*/
|
|
62
|
+
function isFaxLine(p, hosts) {
|
|
63
|
+
if (!hosts.size)
|
|
64
|
+
return false;
|
|
65
|
+
return hosts.has(str(p['dial-rule-translation-destination-host']).toLowerCase());
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A device's NAME — the local part of its SIP URI (`sip:103t@acme.example` → `103t`), which is the short
|
|
69
|
+
* id the portal shows and the string the Teams test matches against.
|
|
70
|
+
*
|
|
71
|
+
* **`device` first, `aor` second.** A live `/users/<ext>/devices` record names the device in `device` and
|
|
72
|
+
* frequently carries no `aor` at all; reading `aor` alone therefore returned `''` on live data, which
|
|
73
|
+
* blanked every device name on the page AND broke the `<ext>t` Teams test — so a Teams connector read as
|
|
74
|
+
* `teams: false` and was counted as a handset in `deviceCount` and `devices.total`. Some records carry
|
|
75
|
+
* both, and then `device` wins, being the field the system actually names the device by. Neither, and the
|
|
76
|
+
* name is `''` rather than a guess.
|
|
77
|
+
*/
|
|
78
|
+
function deviceName(device) {
|
|
79
|
+
const a = (str(device.device) || str(device.aor)).replace(/^sip:/i, '');
|
|
49
80
|
const at = a.indexOf('@');
|
|
50
81
|
return at === -1 ? a : a.slice(0, at);
|
|
51
82
|
}
|
|
52
83
|
function extensionItem(u, devices) {
|
|
53
84
|
const ext = str(u.user);
|
|
54
|
-
// The Teams test is `<ext>t`, so a blank ext would read every device
|
|
55
|
-
//
|
|
56
|
-
const handsets = ext ? devices.filter((d) =>
|
|
85
|
+
// The Teams test is `<ext>t`, so a blank ext would read every device NAMED a bare `t` as a connector.
|
|
86
|
+
// No extension number, no Teams claim.
|
|
87
|
+
const handsets = ext ? devices.filter((d) => deviceName(d) !== `${ext}t`) : devices;
|
|
57
88
|
const transcription = str(u['voicemail-transcription-enabled']).toLowerCase();
|
|
58
89
|
const teams = handsets.length !== devices.length;
|
|
59
90
|
const name = `${str(u['name-first-name'])} ${str(u['name-last-name'])}`.trim();
|
|
@@ -74,8 +105,9 @@ function extensionItem(u, devices) {
|
|
|
74
105
|
deviceModels: handsets.map((d) => str(d['device-models-model']) || '(unknown)'),
|
|
75
106
|
// Every device, including the Teams connector — this is a display list, not a seat count.
|
|
76
107
|
devices: devices.map((d) => {
|
|
77
|
-
const
|
|
78
|
-
|
|
108
|
+
const name = deviceName(d);
|
|
109
|
+
const isTeams = ext ? name === `${ext}t` : false;
|
|
110
|
+
return { name, model: isTeams ? '' : str(d['device-models-model']) || '(unknown)', teams: isTeams };
|
|
79
111
|
}),
|
|
80
112
|
anyDevice: handsets.length > 0 || teams,
|
|
81
113
|
};
|
|
@@ -114,8 +146,15 @@ export function usersByExt(users) {
|
|
|
114
146
|
* - No destination but an application is set → `to <application>` (`to-connection` → `to connection`,
|
|
115
147
|
* `to-voicemail` → `to voicemail`).
|
|
116
148
|
* - Neither is set → `''`.
|
|
149
|
+
*
|
|
150
|
+
* A FAX LINE — a number whose destination host is one of `faxServerHosts` — short-circuits all of that
|
|
151
|
+
* and reads `to fax server`, host omitted. Otherwise it would render as `to connection` (which names
|
|
152
|
+
* plumbing, not a destination) or, on a rule that also carries a destination user, as a bare IP address
|
|
153
|
+
* beside a customer's phone number. Nobody reading this line needs the fax server's address.
|
|
117
154
|
*/
|
|
118
|
-
export function destinationOf(p, userByExt) {
|
|
155
|
+
export function destinationOf(p, userByExt, faxServerHosts) {
|
|
156
|
+
if (isFaxLine(p, faxHosts(faxServerHosts)))
|
|
157
|
+
return 'to fax server';
|
|
119
158
|
const dest = str(p['dial-rule-translation-destination-user']);
|
|
120
159
|
const app = str(p['dial-rule-application']).replace(/^to-/i, '');
|
|
121
160
|
const host = str(p['dial-rule-translation-destination-host']);
|
|
@@ -151,7 +190,7 @@ export function destinationOf(p, userByExt) {
|
|
|
151
190
|
* countable thing, and one derived row is more honest than two that shuffle. A blank id is a
|
|
152
191
|
* provisioning fault to fix; the fallback only keeps the distinguishable ones apart until it is.
|
|
153
192
|
*/
|
|
154
|
-
export function listDomainInventory(snapshot) {
|
|
193
|
+
export function listDomainInventory(snapshot, opts) {
|
|
155
194
|
const users = Array.isArray(snapshot.users) ? snapshot.users : [];
|
|
156
195
|
const devicesByUser = (snapshot.devicesByUser ?? {});
|
|
157
196
|
const phonenumbers = Array.isArray(snapshot.phonenumbers) ? snapshot.phonenumbers : [];
|
|
@@ -172,12 +211,17 @@ export function listDomainInventory(snapshot) {
|
|
|
172
211
|
(isSystemUser(u) ? systemUsers : extensions).push(item);
|
|
173
212
|
}
|
|
174
213
|
const userByExt = usersByExt(users);
|
|
214
|
+
// Normalised once, not per number: the host list is the caller's and does not change mid-fold.
|
|
215
|
+
const hosts = faxHosts(opts?.faxServerHosts);
|
|
175
216
|
const dids = phonenumbers.map((p) => {
|
|
176
217
|
const number = str(p.phonenumber);
|
|
177
218
|
const kind = isTollFree(number) ? 'tollFree' : 'local';
|
|
178
|
-
const
|
|
219
|
+
const fax = isFaxLine(p, hosts);
|
|
220
|
+
// The KEY does not carry `fax`. It is a fact about how the number is routed today, and routing a
|
|
221
|
+
// number to the fax server must not orphan every decision a consumer recorded against it.
|
|
222
|
+
const destination = fax ? 'to fax server' : destinationOf(p, userByExt);
|
|
179
223
|
const description = str(p['dial-rule-description']);
|
|
180
|
-
return { key: identityKey('did', number, JSON.stringify({ number, kind })), number, kind, destination, description };
|
|
224
|
+
return { key: identityKey('did', number, JSON.stringify({ number, kind })), number, kind, fax, destination, description };
|
|
181
225
|
});
|
|
182
226
|
const e911Addresses = addresses.map((a, i) => {
|
|
183
227
|
const id = str(a['emergency-address-id']);
|
|
@@ -200,8 +244,8 @@ export function listDomainInventory(snapshot) {
|
|
|
200
244
|
return { extensions, systemUsers, dids, e911Addresses, smsNumbers };
|
|
201
245
|
}
|
|
202
246
|
/** The counts, as a fold over {@link listDomainInventory} so the two can never disagree. */
|
|
203
|
-
export function countDomainInventory(snapshot) {
|
|
204
|
-
return countInventoryDetail(listDomainInventory(snapshot));
|
|
247
|
+
export function countDomainInventory(snapshot, opts) {
|
|
248
|
+
return countInventoryDetail(listDomainInventory(snapshot, opts));
|
|
205
249
|
}
|
|
206
250
|
/**
|
|
207
251
|
* Count an item list. Exposed separately so a consumer that has FILTERED the lists — to one site, to
|
|
@@ -213,7 +257,7 @@ export function countInventoryDetail(d) {
|
|
|
213
257
|
systemUsers: { total: d.systemUsers.length, byServiceCode: {} },
|
|
214
258
|
transcriptionEnabled: 0,
|
|
215
259
|
teamsConnected: 0,
|
|
216
|
-
dids: { total:
|
|
260
|
+
dids: { total: 0, tollFree: 0, local: 0, fax: 0, all: d.dids.length },
|
|
217
261
|
e911Addresses: d.e911Addresses.length,
|
|
218
262
|
smsNumbers: d.smsNumbers.length,
|
|
219
263
|
devices: { total: 0, byModel: {} },
|
|
@@ -239,7 +283,16 @@ export function countInventoryDetail(d) {
|
|
|
239
283
|
for (const m of x.deviceModels)
|
|
240
284
|
bump(inv.devices.byModel, m);
|
|
241
285
|
}
|
|
286
|
+
// A fax line is billed as a fax line, so it lands in `fax` and in NEITHER of the two DID buckets —
|
|
287
|
+
// counting it as both would bill one number twice on a rulebook that has a rule for each. `fax` is
|
|
288
|
+
// read off the item rather than recomputed: an item list a consumer FILTERED still carries it, and a
|
|
289
|
+
// list built by a pre-0.7.0 lib has no `fax` at all, which reads as false and counts as it always did.
|
|
242
290
|
for (const n of d.dids) {
|
|
291
|
+
if (n.fax) {
|
|
292
|
+
inv.dids.fax++;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
inv.dids.total++;
|
|
243
296
|
if (n.kind === 'tollFree')
|
|
244
297
|
inv.dids.tollFree++;
|
|
245
298
|
else
|
|
@@ -276,12 +329,19 @@ export function itemsFor(detail, path) {
|
|
|
276
329
|
return ex.filter((x) => x.transcription);
|
|
277
330
|
if (path === 'teamsConnected')
|
|
278
331
|
return ex.filter((x) => x.teams);
|
|
332
|
+
// The three DID paths exclude fax lines, exactly as the counts do — a `counts: "dids.total"` rule
|
|
333
|
+
// whose observed number left the fax lines out but whose item list showed them would offer an
|
|
334
|
+
// operator rows to accept that the number above them does not count.
|
|
279
335
|
if (path === 'dids.total')
|
|
280
|
-
return detail.dids;
|
|
336
|
+
return detail.dids.filter((n) => !n.fax);
|
|
281
337
|
if (path === 'dids.tollFree')
|
|
282
|
-
return detail.dids.filter((n) => n.kind === 'tollFree');
|
|
338
|
+
return detail.dids.filter((n) => !n.fax && n.kind === 'tollFree');
|
|
283
339
|
if (path === 'dids.local')
|
|
284
|
-
return detail.dids.filter((n) => n.kind === 'local');
|
|
340
|
+
return detail.dids.filter((n) => !n.fax && n.kind === 'local');
|
|
341
|
+
if (path === 'dids.fax')
|
|
342
|
+
return detail.dids.filter((n) => n.fax);
|
|
343
|
+
if (path === 'dids.all')
|
|
344
|
+
return detail.dids;
|
|
285
345
|
if (path === 'e911Addresses')
|
|
286
346
|
return detail.e911Addresses;
|
|
287
347
|
if (path === 'smsNumbers')
|