@myvoip/sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +56 -0
- package/dist/client.js +65 -0
- package/dist/http.d.ts +20 -0
- package/dist/http.js +84 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/netsapiens.d.ts +25 -0
- package/dist/netsapiens.js +58 -0
- package/package.json +20 -4
- package/README.md +0 -3
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type FetchLike } from "./http.js";
|
|
2
|
+
export type ClientOptions = {
|
|
3
|
+
apiToken: string;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
fetch?: FetchLike;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare class UponAI {
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(options: ClientOptions);
|
|
11
|
+
private call;
|
|
12
|
+
me(): Promise<unknown>;
|
|
13
|
+
readonly credentials: {
|
|
14
|
+
list: () => Promise<unknown>;
|
|
15
|
+
};
|
|
16
|
+
readonly mappings: {
|
|
17
|
+
list: () => Promise<unknown>;
|
|
18
|
+
get: (mappingId: string) => Promise<unknown>;
|
|
19
|
+
};
|
|
20
|
+
readonly sourceDomains: {
|
|
21
|
+
list: (credentialId: string) => Promise<unknown>;
|
|
22
|
+
};
|
|
23
|
+
readonly preflight: {
|
|
24
|
+
start: (mappingId: string) => Promise<unknown>;
|
|
25
|
+
list: (mappingId?: string) => Promise<unknown>;
|
|
26
|
+
get: (runId: string) => Promise<unknown>;
|
|
27
|
+
};
|
|
28
|
+
readonly migrations: {
|
|
29
|
+
start: (preflightRunId: string, dryRun?: boolean) => Promise<unknown>;
|
|
30
|
+
list: (mappingId?: string) => Promise<unknown>;
|
|
31
|
+
get: (runId: string) => Promise<unknown>;
|
|
32
|
+
kill: (runId: string) => Promise<unknown>;
|
|
33
|
+
validate: (runId: string) => Promise<unknown>;
|
|
34
|
+
};
|
|
35
|
+
readonly probe: {
|
|
36
|
+
run: (mappingId: string) => Promise<unknown>;
|
|
37
|
+
latest: (mappingId: string) => Promise<unknown>;
|
|
38
|
+
};
|
|
39
|
+
readonly phones: {
|
|
40
|
+
dmStatus: (mappingId: string) => Promise<unknown>;
|
|
41
|
+
rpsAssign: (mappingId: string) => Promise<unknown>;
|
|
42
|
+
dmEnroll: (mappingId: string, dryRun?: boolean) => Promise<unknown>;
|
|
43
|
+
control: (mappingId: string, mac: string, action: "reboot" | "factory_reset", force?: boolean) => Promise<unknown>;
|
|
44
|
+
oneTimePass: (mappingId: string) => Promise<unknown>;
|
|
45
|
+
};
|
|
46
|
+
readonly autopilot: {
|
|
47
|
+
batches: () => Promise<unknown>;
|
|
48
|
+
batch: (batchId: string) => Promise<unknown>;
|
|
49
|
+
batchAction: (batchId: string, action: "pause" | "resume" | "cancel") => Promise<unknown>;
|
|
50
|
+
itemAction: (itemId: string, action: "continue" | "retry" | "skip") => Promise<unknown>;
|
|
51
|
+
setNote: (itemId: string, note: string) => Promise<unknown>;
|
|
52
|
+
};
|
|
53
|
+
readonly audit: {
|
|
54
|
+
list: (limit?: number) => Promise<unknown>;
|
|
55
|
+
};
|
|
56
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { apiRequest } from "./http.js";
|
|
2
|
+
const DEFAULT_BASE = "https://netsapiens.uponai.com";
|
|
3
|
+
export class UponAI {
|
|
4
|
+
options;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = {
|
|
7
|
+
baseUrl: options.baseUrl ?? DEFAULT_BASE,
|
|
8
|
+
token: options.apiToken,
|
|
9
|
+
fetch: options.fetch,
|
|
10
|
+
timeoutMs: options.timeoutMs,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
call(method, path, query, body) {
|
|
14
|
+
return apiRequest(this.options, method, path, { query, body });
|
|
15
|
+
}
|
|
16
|
+
me() {
|
|
17
|
+
return this.call("GET", "/api/me");
|
|
18
|
+
}
|
|
19
|
+
credentials = {
|
|
20
|
+
list: () => this.call("GET", "/api/credentials"),
|
|
21
|
+
};
|
|
22
|
+
mappings = {
|
|
23
|
+
list: () => this.call("GET", "/api/mappings"),
|
|
24
|
+
get: (mappingId) => this.call("GET", `/api/mappings/${mappingId}`),
|
|
25
|
+
};
|
|
26
|
+
sourceDomains = {
|
|
27
|
+
list: (credentialId) => this.call("GET", "/api/source-domains", { credential_id: credentialId }),
|
|
28
|
+
};
|
|
29
|
+
preflight = {
|
|
30
|
+
start: (mappingId) => this.call("POST", "/api/preflight-runs", undefined, { mapping_id: mappingId }),
|
|
31
|
+
list: (mappingId) => this.call("GET", "/api/preflight-runs", { mapping_id: mappingId }),
|
|
32
|
+
get: (runId) => this.call("GET", `/api/preflight-runs/${runId}`),
|
|
33
|
+
};
|
|
34
|
+
migrations = {
|
|
35
|
+
start: (preflightRunId, dryRun = false) => this.call("POST", "/api/migration-runs", undefined, {
|
|
36
|
+
preflight_run_id: preflightRunId,
|
|
37
|
+
dry_run: dryRun,
|
|
38
|
+
}),
|
|
39
|
+
list: (mappingId) => this.call("GET", "/api/migration-runs", { mapping_id: mappingId }),
|
|
40
|
+
get: (runId) => this.call("GET", `/api/migration-runs/${runId}`),
|
|
41
|
+
kill: (runId) => this.call("POST", `/api/migration-runs/${runId}/kill`),
|
|
42
|
+
validate: (runId) => this.call("POST", `/api/migration-runs/${runId}/validate`),
|
|
43
|
+
};
|
|
44
|
+
probe = {
|
|
45
|
+
run: (mappingId) => this.call("POST", `/api/mappings/${mappingId}/probe`),
|
|
46
|
+
latest: (mappingId) => this.call("GET", `/api/mappings/${mappingId}/probe/latest`),
|
|
47
|
+
};
|
|
48
|
+
phones = {
|
|
49
|
+
dmStatus: (mappingId) => this.call("GET", `/api/mappings/${mappingId}/phones/dm-status`),
|
|
50
|
+
rpsAssign: (mappingId) => this.call("POST", `/api/mappings/${mappingId}/phones/rps-assign`),
|
|
51
|
+
dmEnroll: (mappingId, dryRun = true) => this.call("POST", `/api/mappings/${mappingId}/phones/dm-enroll`, { dry_run: dryRun }),
|
|
52
|
+
control: (mappingId, mac, action, force = false) => this.call("POST", `/api/mappings/${mappingId}/phones/dm-control`, undefined, { mac, action, force }),
|
|
53
|
+
oneTimePass: (mappingId) => this.call("POST", `/api/mappings/${mappingId}/phones/global-one-time-pass`),
|
|
54
|
+
};
|
|
55
|
+
autopilot = {
|
|
56
|
+
batches: () => this.call("GET", "/api/autopilot/batches"),
|
|
57
|
+
batch: (batchId) => this.call("GET", `/api/autopilot/batches/${batchId}`),
|
|
58
|
+
batchAction: (batchId, action) => this.call("POST", `/api/autopilot/batches/${batchId}/${action}`),
|
|
59
|
+
itemAction: (itemId, action) => this.call("POST", `/api/autopilot/items/${itemId}/${action}`),
|
|
60
|
+
setNote: (itemId, note) => this.call("PUT", `/api/autopilot/items/${itemId}/note`, undefined, { note }),
|
|
61
|
+
};
|
|
62
|
+
audit = {
|
|
63
|
+
list: (limit = 50) => this.call("GET", "/api/platform-audit-events", { limit }),
|
|
64
|
+
};
|
|
65
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare class ApiError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly body: string;
|
|
4
|
+
constructor(status: number, message: string, body: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class AuthenticationError extends ApiError {
|
|
7
|
+
constructor(message: string, body: string);
|
|
8
|
+
}
|
|
9
|
+
export declare function scrub(value: unknown): unknown;
|
|
10
|
+
export type FetchLike = typeof fetch;
|
|
11
|
+
export type RequestOptions = {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
token: string;
|
|
14
|
+
fetch?: FetchLike;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function apiRequest(options: RequestOptions, method: string, path: string, init?: {
|
|
18
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
19
|
+
body?: unknown;
|
|
20
|
+
}): Promise<unknown>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export class ApiError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
body;
|
|
4
|
+
constructor(status, message, body) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ApiError";
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.body = body;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class AuthenticationError extends ApiError {
|
|
12
|
+
constructor(message, body) {
|
|
13
|
+
super(401, message, body);
|
|
14
|
+
this.name = "AuthenticationError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const SECRET_PARTS = [
|
|
18
|
+
"password",
|
|
19
|
+
"passwd",
|
|
20
|
+
"secret",
|
|
21
|
+
"token",
|
|
22
|
+
"api_key",
|
|
23
|
+
"api-key",
|
|
24
|
+
"auth_tag",
|
|
25
|
+
"auth-tag",
|
|
26
|
+
"device_key",
|
|
27
|
+
"device-key",
|
|
28
|
+
];
|
|
29
|
+
export function scrub(value) {
|
|
30
|
+
if (Array.isArray(value))
|
|
31
|
+
return value.map(scrub);
|
|
32
|
+
if (value && typeof value === "object") {
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const [key, item] of Object.entries(value)) {
|
|
35
|
+
const lowered = key.toLowerCase();
|
|
36
|
+
out[key] = SECRET_PARTS.some((part) => lowered.includes(part)) ? "***" : scrub(item);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
export async function apiRequest(options, method, path, init = {}) {
|
|
43
|
+
if (!options.token) {
|
|
44
|
+
throw new AuthenticationError("missing API token", "");
|
|
45
|
+
}
|
|
46
|
+
const url = new URL(path.replace(/^\//, ""), options.baseUrl.replace(/\/$/, "") + "/");
|
|
47
|
+
for (const [key, value] of Object.entries(init.query ?? {})) {
|
|
48
|
+
if (value !== undefined)
|
|
49
|
+
url.searchParams.set(key, String(value));
|
|
50
|
+
}
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 60_000);
|
|
53
|
+
let response;
|
|
54
|
+
try {
|
|
55
|
+
response = await (options.fetch ?? fetch)(url, {
|
|
56
|
+
method,
|
|
57
|
+
signal: controller.signal,
|
|
58
|
+
headers: {
|
|
59
|
+
Accept: "application/json",
|
|
60
|
+
Authorization: `Bearer ${options.token}`,
|
|
61
|
+
...(init.body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
62
|
+
},
|
|
63
|
+
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
}
|
|
69
|
+
const text = await response.text();
|
|
70
|
+
if (response.status === 401) {
|
|
71
|
+
throw new AuthenticationError(`${method} ${path} returned 401`, text.slice(0, 2000));
|
|
72
|
+
}
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new ApiError(response.status, `${method} ${path} returned ${response.status}`, text.slice(0, 2000));
|
|
75
|
+
}
|
|
76
|
+
if (response.status === 204 || text.length === 0)
|
|
77
|
+
return { ok: true };
|
|
78
|
+
try {
|
|
79
|
+
return scrub(JSON.parse(text));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { text: text.slice(0, 4000) };
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type FetchLike } from "./http.js";
|
|
2
|
+
export type NetsapiensOptions = {
|
|
3
|
+
apiToken: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
fetch?: FetchLike;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare class Netsapiens {
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(options: NetsapiensOptions);
|
|
11
|
+
private call;
|
|
12
|
+
private domainPath;
|
|
13
|
+
domain(domain: string): Promise<unknown>;
|
|
14
|
+
connections(domain: string): Promise<unknown>;
|
|
15
|
+
/** Header Manipulation on Edit SIP Trunk. Request and To are the trunk name, not the .myvoip domain. */
|
|
16
|
+
setTrunkHeaders(domain: string, origMatch: string, headers: {
|
|
17
|
+
requestHost?: string;
|
|
18
|
+
toHost?: string;
|
|
19
|
+
fromHost?: string;
|
|
20
|
+
}): Promise<unknown>;
|
|
21
|
+
phoneNumbers(domain: string): Promise<unknown>;
|
|
22
|
+
dialRules(domain: string, dialPlan?: string): Promise<unknown>;
|
|
23
|
+
cdrs(domain: string, start: string, end: string, limit?: number): Promise<unknown>;
|
|
24
|
+
users(domain: string): Promise<unknown>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { apiRequest } from "./http.js";
|
|
2
|
+
export class Netsapiens {
|
|
3
|
+
options;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.options = {
|
|
6
|
+
baseUrl: options.baseUrl,
|
|
7
|
+
token: options.apiToken,
|
|
8
|
+
fetch: options.fetch,
|
|
9
|
+
timeoutMs: options.timeoutMs,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
call(method, path, query, body) {
|
|
13
|
+
return apiRequest(this.options, method, path, { query, body });
|
|
14
|
+
}
|
|
15
|
+
domainPath(domain, suffix = "") {
|
|
16
|
+
return `/ns-api/v2/domains/${encodeURIComponent(domain)}${suffix}`;
|
|
17
|
+
}
|
|
18
|
+
domain(domain) {
|
|
19
|
+
return this.call("GET", this.domainPath(domain));
|
|
20
|
+
}
|
|
21
|
+
connections(domain) {
|
|
22
|
+
return this.call("GET", this.domainPath(domain, "/connections"));
|
|
23
|
+
}
|
|
24
|
+
/** Header Manipulation on Edit SIP Trunk. Request and To are the trunk name, not the .myvoip domain. */
|
|
25
|
+
setTrunkHeaders(domain, origMatch, headers) {
|
|
26
|
+
const body = {};
|
|
27
|
+
if (headers.requestHost !== undefined)
|
|
28
|
+
body["connection-translation-request-host"] = headers.requestHost;
|
|
29
|
+
if (headers.toHost !== undefined)
|
|
30
|
+
body["connection-translation-destination-host"] = headers.toHost;
|
|
31
|
+
if (headers.fromHost !== undefined)
|
|
32
|
+
body["connection-translation-source-host"] = headers.fromHost;
|
|
33
|
+
return this.call("PUT", this.domainPath(domain, `/connections/${encodeURIComponent(origMatch)}`), undefined, body);
|
|
34
|
+
}
|
|
35
|
+
phoneNumbers(domain) {
|
|
36
|
+
return this.call("GET", this.domainPath(domain, "/phonenumbers"));
|
|
37
|
+
}
|
|
38
|
+
async dialRules(domain, dialPlan) {
|
|
39
|
+
let plan = dialPlan;
|
|
40
|
+
if (!plan) {
|
|
41
|
+
const row = await this.domain(domain);
|
|
42
|
+
plan = typeof row === "object" && row && "dial-plan" in row ? String(row["dial-plan"] ?? "") : "";
|
|
43
|
+
if (!plan)
|
|
44
|
+
throw new Error(`domain ${domain} has no dial-plan; pass one`);
|
|
45
|
+
}
|
|
46
|
+
return this.call("GET", this.domainPath(domain, `/dialplans/${encodeURIComponent(plan)}/dialrules`));
|
|
47
|
+
}
|
|
48
|
+
cdrs(domain, start, end, limit = 25) {
|
|
49
|
+
return this.call("GET", this.domainPath(domain, "/cdrs"), {
|
|
50
|
+
"datetime-start": start,
|
|
51
|
+
"datetime-end": end,
|
|
52
|
+
limit: Math.max(1, Math.min(limit, 100)),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
users(domain) {
|
|
56
|
+
return this.call("GET", this.domainPath(domain, "/users"));
|
|
57
|
+
}
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myvoip/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for the UponAI migration tool and the NetSapiens portal",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": { "node": ">=22" },
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"test": "node --experimental-strip-types --test src/http.test.ts"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "^5.6.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/README.md
DELETED