wldx-cli 0.1.0 → 1.0.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 +279 -27
- package/package.json +1 -1
- package/src/auth.mjs +181 -35
- package/src/chat/format.mjs +97 -0
- package/src/chat/lib.mjs +162 -0
- package/src/chat/watch.mjs +251 -0
- package/src/cli.mjs +167 -47
- package/src/commands/calls.mjs +367 -0
- package/src/commands/channel.mjs +326 -0
- package/src/commands/chat.mjs +388 -60
- package/src/commands/conversations.mjs +251 -0
- package/src/commands/file.mjs +72 -0
- package/src/commands/inbox.mjs +101 -0
- package/src/commands/index.mjs +54 -9
- package/src/commands/live.mjs +404 -0
- package/src/commands/login.mjs +126 -0
- package/src/commands/meetings.mjs +299 -0
- package/src/commands/phone.mjs +452 -0
- package/src/commands/profile.mjs +51 -0
- package/src/commands/reports.mjs +431 -0
- package/src/commands/section.mjs +60 -0
- package/src/commands/status.mjs +95 -0
- package/src/commands/user.mjs +130 -0
- package/src/completion.mjs +47 -20
- package/src/files.mjs +98 -0
- package/src/format.mjs +82 -0
- package/src/history/text.mjs +60 -0
- package/src/history/wda.mjs +161 -0
- package/src/http.mjs +30 -13
- package/src/live/socket.mjs +121 -0
- package/src/live/stats.mjs +211 -0
- package/src/live/text.mjs +219 -0
- package/src/live/views.mjs +321 -0
- package/src/phone/control.mjs +377 -0
- package/src/phone/events.mjs +189 -0
- package/src/phone/pbx.mjs +140 -0
- package/src/phone/sip.mjs +316 -0
- package/src/phone/xml.mjs +56 -0
- package/src/services.mjs +58 -0
- package/src/stream.mjs +4 -11
- package/src/commands/auth.mjs +0 -32
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// A small XML reader for the bodies of the SIP event packages of the PBX (dialog-info, reginfo, active-device):
|
|
2
|
+
// elements, attributes, text. No DTD, no external entities (a DOCTYPE is refused). Element names are matched
|
|
3
|
+
// without their namespace prefix, as ltx does.
|
|
4
|
+
|
|
5
|
+
const TOKEN = /<!--[\s\S]*?-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\?[\s\S]*?\?>|<\/\s*([^\s>]+)\s*>|<([^\s/>!?]+)((?:\s+[^\s=/>]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*(\/?)>|([^<]+)/y;
|
|
6
|
+
const ATTR = /([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
7
|
+
const NAMED = {lt: '<', gt: '>', amp: '&', quot: '"', apos: "'"};
|
|
8
|
+
|
|
9
|
+
function decode(s) {
|
|
10
|
+
return s.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-z]+);/g, (m, e) => {
|
|
11
|
+
if (e[0] === '#') {
|
|
12
|
+
const code = e[1] === 'x' ? parseInt(e.slice(2), 16) : parseInt(e.slice(1), 10);
|
|
13
|
+
return code <= 0x10ffff ? String.fromCodePoint(code) : m;
|
|
14
|
+
}
|
|
15
|
+
return NAMED[e] ?? m;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const local = (name) => name.slice(name.indexOf(':') + 1);
|
|
20
|
+
|
|
21
|
+
// Parses a document and returns its root element: {name, attrs, children, text}. Throws on what is not XML.
|
|
22
|
+
export function parseXml(text) {
|
|
23
|
+
const s = String(text ?? '').trim();
|
|
24
|
+
if (/<!DOCTYPE/i.test(s)) throw new Error('XML with a DOCTYPE is not accepted');
|
|
25
|
+
const root = {name: '#document', attrs: {}, children: [], text: ''};
|
|
26
|
+
const stack = [root];
|
|
27
|
+
TOKEN.lastIndex = 0;
|
|
28
|
+
while (TOKEN.lastIndex < s.length) {
|
|
29
|
+
const at = TOKEN.lastIndex;
|
|
30
|
+
const m = TOKEN.exec(s);
|
|
31
|
+
if (!m || m.index !== at) throw new Error(`not XML at character ${at}`);
|
|
32
|
+
const top = stack[stack.length - 1];
|
|
33
|
+
const [whole, cdata, closing, opening, attrText, selfClosing, chars] = m;
|
|
34
|
+
if (whole.startsWith('<!--') || whole.startsWith('<?')) continue;
|
|
35
|
+
if (cdata !== undefined) top.text += cdata;
|
|
36
|
+
else if (chars !== undefined) top.text += decode(chars);
|
|
37
|
+
else if (closing !== undefined) {
|
|
38
|
+
if (stack.length < 2 || top.name !== local(closing)) throw new Error(`unexpected </${closing}>`);
|
|
39
|
+
stack.pop();
|
|
40
|
+
} else if (opening !== undefined) {
|
|
41
|
+
const attrs = {};
|
|
42
|
+
for (const a of (attrText ?? '').matchAll(ATTR)) attrs[a[1]] = decode(a[2] ?? a[3] ?? '');
|
|
43
|
+
const el = {name: local(opening), attrs, children: [], text: ''};
|
|
44
|
+
top.children.push(el);
|
|
45
|
+
if (!selfClosing) stack.push(el);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (stack.length !== 1) throw new Error(`<${stack[stack.length - 1].name}> is not closed`);
|
|
49
|
+
const [el] = root.children;
|
|
50
|
+
if (!el || root.children.length > 1) throw new Error('an XML document has one root element');
|
|
51
|
+
return el;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const child = (el, name) => el?.children.find((c) => c.name === name);
|
|
55
|
+
export const children = (el, name) => el?.children.filter((c) => c.name === name) ?? [];
|
|
56
|
+
export const text = (el) => (el ? el.text.trim() : undefined);
|
package/src/services.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// The addresses of the Wildix services. The token is sent only to these: https/wss hosts under wildix.com, the
|
|
2
|
+
// x-bees API, and the user's own PBX named in the claims of the login token. Overrides (tests against a local
|
|
3
|
+
// server) are honoured only with WILDIX_ALLOW_CUSTOM_URL=1, except WILDIX_STREAM_URL and WILDIX_WDA_URL, which may
|
|
4
|
+
// name another https host under wildix.com.
|
|
5
|
+
import {jwtClaims} from './auth.mjs';
|
|
6
|
+
import {UsageError} from './errors.mjs';
|
|
7
|
+
|
|
8
|
+
export const XBEES_API = 'https://api.x-bees.com';
|
|
9
|
+
|
|
10
|
+
const UNDER_WILDIX = (scheme) => new RegExp(`^${scheme}://[a-z0-9-]+(\\.[a-z0-9-]+)*\\.wildix\\.com(:[0-9]+)?$`);
|
|
11
|
+
const HTTPS_WILDIX = UNDER_WILDIX('https');
|
|
12
|
+
const HOST = /^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)+$/;
|
|
13
|
+
|
|
14
|
+
const custom = (env) => env.WILDIX_ALLOW_CUSTOM_URL === '1';
|
|
15
|
+
const trim = (url) => url.replace(/\/+$/, '');
|
|
16
|
+
|
|
17
|
+
// base(variable, default, env, {wildixOverride}): the default, or the override in `variable` when allowed.
|
|
18
|
+
function base(variable, fallback, env, {wildixOverride = false} = {}) {
|
|
19
|
+
const given = env[variable];
|
|
20
|
+
if (!given) return fallback;
|
|
21
|
+
const url = trim(given);
|
|
22
|
+
if (custom(env)) return url;
|
|
23
|
+
if (wildixOverride && HTTPS_WILDIX.test(url)) return url;
|
|
24
|
+
throw new UsageError(
|
|
25
|
+
wildixOverride
|
|
26
|
+
? `${variable} must be https://<host>.wildix.com (another address only with WILDIX_ALLOW_CUSTOM_URL=1), got: ${url}`
|
|
27
|
+
: `${variable} is honoured only with WILDIX_ALLOW_CUSTOM_URL=1 (tests), got: ${url}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function region(env) {
|
|
32
|
+
const r = env.WILDIX_STREAM_REGION || 'eu-1';
|
|
33
|
+
if (!/^[a-z0-9-]{1,32}$/.test(r)) throw new UsageError(`WILDIX_STREAM_REGION is not a region: ${r}`);
|
|
34
|
+
return r;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Chats (the stream service): https://<region>.stream.wildix.com, the live feed wss://<region>-ws.stream.wildix.com.
|
|
38
|
+
export const streamUrl = (env = process.env) => base('WILDIX_STREAM_URL', `https://${region(env)}.stream.wildix.com`, env, {wildixOverride: true});
|
|
39
|
+
export const streamWsUrl = (env = process.env) => base('WILDIX_STREAM_WS_URL', `wss://${region(env)}-ws.stream.wildix.com`, env);
|
|
40
|
+
|
|
41
|
+
// Wildix Data Analytics: history, insights, reports (https://wda.wildix.com), the live stream (wss://wda-ws...).
|
|
42
|
+
export const wdaUrl = (env = process.env) => base('WILDIX_WDA_URL', 'https://wda.wildix.com', env, {wildixOverride: true});
|
|
43
|
+
export const wdaWsUrl = (env = process.env) => base('WILDIX_WDA_WS_URL', 'wss://wda-ws.wildix.com', env);
|
|
44
|
+
|
|
45
|
+
// The x-bees API (users, conversations of the older service, token check).
|
|
46
|
+
export const xbeesUrl = (env = process.env) => base('WILDIX_XBEES_URL', XBEES_API, env);
|
|
47
|
+
|
|
48
|
+
// The user's PBX: the login token names it (custom:pbx_domain, custom:pbx_port) and the PBX accepts that token.
|
|
49
|
+
export function pbxUrl(token, env = process.env) {
|
|
50
|
+
if (env.WILDIX_PBX_URL) return base('WILDIX_PBX_URL', undefined, env);
|
|
51
|
+
const claims = jwtClaims(token);
|
|
52
|
+
const host = claims['custom:pbx_domain'];
|
|
53
|
+
const port = claims['custom:pbx_port'];
|
|
54
|
+
if (!host) throw new UsageError('This login is not linked to a PBX (the token has no custom:pbx_domain): this needs a PBX user');
|
|
55
|
+
if (!HOST.test(host)) throw new UsageError(`The PBX address in the login is not a host name: ${host}`);
|
|
56
|
+
if (port !== undefined && port !== '' && !/^[0-9]{1,5}$/.test(String(port))) throw new UsageError(`The PBX port in the login is not a number: ${port}`);
|
|
57
|
+
return `https://${host}${port && String(port) !== '443' ? `:${port}` : ''}`;
|
|
58
|
+
}
|
package/src/stream.mjs
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
// The Wildix stream service (chats): https://<region>.stream.wildix.com/api/v1, region eu-1 by default.
|
|
2
2
|
import {UsageError} from './errors.mjs';
|
|
3
3
|
import {request} from './http.mjs';
|
|
4
|
+
import {streamUrl} from './services.mjs';
|
|
4
5
|
|
|
5
|
-
const WILDIX_HOST = /^https:\/\/[a-z0-9-]+(\.[a-z0-9-]+)*\.wildix\.com(:[0-9]+)?$/;
|
|
6
6
|
const ID = /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/;
|
|
7
7
|
|
|
8
|
-
// The base address
|
|
9
|
-
|
|
10
|
-
export function streamBase(env = process.env) {
|
|
11
|
-
const base = (env.WILDIX_STREAM_URL || `https://${env.WILDIX_STREAM_REGION || 'eu-1'}.stream.wildix.com`).replace(/\/+$/, '');
|
|
12
|
-
if (env.WILDIX_ALLOW_CUSTOM_URL !== '1' && !WILDIX_HOST.test(base)) {
|
|
13
|
-
throw new UsageError(`The stream service address must be https://<host>.wildix.com, got: ${base}`);
|
|
14
|
-
}
|
|
15
|
-
return base;
|
|
16
|
-
}
|
|
8
|
+
// The base address (see services.mjs for the overrides).
|
|
9
|
+
export const streamBase = (env = process.env) => streamUrl(env);
|
|
17
10
|
|
|
18
11
|
const valid = (v) => ID.test(v) && !v.includes('..');
|
|
19
12
|
|
|
@@ -38,6 +31,6 @@ export function messageId(input) {
|
|
|
38
31
|
export function streamClient({token, env = process.env, fetchImpl}) {
|
|
39
32
|
const api = `${streamBase(env)}/api/v1`;
|
|
40
33
|
return {
|
|
41
|
-
call: (method, path, body) => request(api + path, {method, token, body, fetchImpl, service: 'the stream service'}),
|
|
34
|
+
call: (method, path, body, options = {}) => request(api + path, {method, token, body, fetchImpl, service: 'the stream service', ...options}),
|
|
42
35
|
};
|
|
43
36
|
}
|
package/src/commands/auth.mjs
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// wx auth: the saved Wildix sessions (shared with the wildix-auth agent skill). Never prints a token.
|
|
2
|
-
import {AuthError} from '../errors.mjs';
|
|
3
|
-
import {pickAccount, sessions} from '../auth.mjs';
|
|
4
|
-
|
|
5
|
-
export default {
|
|
6
|
-
summary: 'the Wildix accounts you are logged in with',
|
|
7
|
-
commands: {
|
|
8
|
-
status: {
|
|
9
|
-
summary: 'saved sessions, which one is used and until when its token is valid (no network)',
|
|
10
|
-
usage: 'wx auth status',
|
|
11
|
-
options: {},
|
|
12
|
-
args: [0, 0],
|
|
13
|
-
async run({values, env, now}) {
|
|
14
|
-
let account = null;
|
|
15
|
-
try {
|
|
16
|
-
account = pickAccount(env, values.account);
|
|
17
|
-
} catch (e) {
|
|
18
|
-
if (!(e instanceof AuthError)) throw e;
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
account,
|
|
22
|
-
tokenFromEnvironment: Boolean(env.WILDIX_ID_TOKEN),
|
|
23
|
-
sessions: sessions(env).map((s) => ({
|
|
24
|
-
email: s.email,
|
|
25
|
-
tokenValidUntil: s.expiresAt ? new Date(s.expiresAt).toISOString() : null,
|
|
26
|
-
expired: s.expiresAt <= now,
|
|
27
|
-
})),
|
|
28
|
-
};
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
};
|