@fonoster/sipnet 0.26.0 → 0.30.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.
Files changed (36) hide show
  1. package/dist/acls/updateAcl.d.ts +0 -19
  2. package/dist/acls/updateAcl.js +5 -4
  3. package/dist/agents/buildService.js +4 -2
  4. package/dist/agents/operations.d.ts +3 -2
  5. package/dist/agents/operations.js +5 -4
  6. package/dist/domains/buildService.js +4 -2
  7. package/dist/domains/operations.d.ts +3 -2
  8. package/dist/domains/operations.js +5 -4
  9. package/dist/numbers/buildService.d.ts +3 -1
  10. package/dist/numbers/buildService.js +6 -3
  11. package/dist/numbers/convertToRoutrNumber.d.ts +6 -2
  12. package/dist/numbers/convertToRoutrNumber.js +8 -4
  13. package/dist/numbers/createNumber.d.ts +3 -1
  14. package/dist/numbers/createNumber.js +13 -4
  15. package/dist/numbers/engineRouting.d.ts +29 -0
  16. package/dist/numbers/engineRouting.js +44 -0
  17. package/dist/numbers/getNumber.d.ts +0 -19
  18. package/dist/numbers/getNumber.js +6 -6
  19. package/dist/numbers/index.d.ts +2 -0
  20. package/dist/numbers/index.js +16 -0
  21. package/dist/numbers/repointNumbers.d.ts +49 -0
  22. package/dist/numbers/repointNumbers.js +83 -0
  23. package/dist/numbers/repointNumbersCli.d.ts +1 -0
  24. package/dist/numbers/repointNumbersCli.js +106 -0
  25. package/dist/numbers/updateNumber.d.ts +4 -2
  26. package/dist/numbers/updateNumber.js +20 -7
  27. package/dist/resources/checkRefs.d.ts +26 -0
  28. package/dist/resources/checkRefs.js +138 -0
  29. package/dist/resources/createResource.d.ts +2 -1
  30. package/dist/resources/createResource.js +5 -2
  31. package/dist/resources/updateResource.d.ts +2 -1
  32. package/dist/resources/updateResource.js +7 -2
  33. package/dist/trunks/buildService.js +4 -2
  34. package/dist/trunks/operations.d.ts +10 -3
  35. package/dist/trunks/operations.js +40 -4
  36. package/package.json +8 -8
@@ -1,22 +1,3 @@
1
- /**
2
- * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
3
- * http://github.com/fonoster/fonoster
4
- *
5
- * This file is part of Fonoster
6
- *
7
- * Licensed under the MIT License (the "License");
8
- * you may not use this file except in compliance with
9
- * the License. You may obtain a copy of the License at
10
- *
11
- * https://opensource.org/licenses/MIT
12
- *
13
- * Unless required by applicable law or agreed to in writing, software
14
- * distributed under the License is distributed on an "AS IS" BASIS,
15
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- * See the License for the specific language governing permissions and
17
- * limitations under the License.
18
- */
19
- import { GrpcErrorMessage } from "@fonoster/common";
20
1
  import { AclsApi } from "@fonoster/types";
21
2
  declare function updateAcl(api: AclsApi): (call: {
22
3
  request: unknown;
@@ -29,15 +29,16 @@ exports.updateAcl = updateAcl;
29
29
  * limitations under the License.
30
30
  */
31
31
  const common_1 = require("@fonoster/common");
32
+ const identity_1 = require("@fonoster/identity");
32
33
  const logger_1 = require("@fonoster/logger");
33
34
  const logger = (0, logger_1.getLogger)({ service: "sipnet", filePath: __filename });
34
35
  function updateAcl(api) {
35
- const fn = (call, callback) => __awaiter(this, void 0, void 0, function* () {
36
+ const fn = (call) => __awaiter(this, void 0, void 0, function* () {
36
37
  const { request } = call;
37
38
  const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
38
39
  logger.verbose("call to updateAcl", Object.assign(Object.assign({}, request), { accessKeyId }));
39
- const response = yield api.updateAcl(Object.assign(Object.assign({}, request), { deny: ["0.0.0.0/0"] }));
40
- callback(null, response);
40
+ return yield api.updateAcl(Object.assign(Object.assign({}, request), { deny: ["0.0.0.0/0"] }));
41
41
  });
42
- return (0, common_1.withErrorHandlingAndValidation)(fn, common_1.Validators.updateAclRequestSchema);
42
+ // Only the caller's own ACLs can be updated, like every other resource.
43
+ return (0, common_1.withErrorHandlingAndValidation)((0, identity_1.withAccess)(fn, ((ref) => api.getAcl(ref))), common_1.Validators.updateAclRequestSchema);
43
44
  }
@@ -23,9 +23,11 @@ exports.buildService = buildService;
23
23
  * limitations under the License.
24
24
  */
25
25
  const sdk_1 = __importDefault(require("@routr/sdk"));
26
+ const checkRefs_1 = require("../resources/checkRefs");
26
27
  const operations_1 = require("./operations");
27
28
  function buildService(clientOptions) {
28
29
  const client = new sdk_1.default.Agents(clientOptions);
30
+ const checkRefs = (0, checkRefs_1.createCheckRefs)((0, checkRefs_1.createRefLookups)(clientOptions));
29
31
  return {
30
32
  definition: {
31
33
  serviceName: "Agents",
@@ -34,8 +36,8 @@ function buildService(clientOptions) {
34
36
  proto: "agents.proto"
35
37
  },
36
38
  handlers: {
37
- createAgent: (0, operations_1.createAgent)(client),
38
- updateAgent: (0, operations_1.updateAgent)(client),
39
+ createAgent: (0, operations_1.createAgent)(client, checkRefs),
40
+ updateAgent: (0, operations_1.updateAgent)(client, checkRefs),
39
41
  getAgent: (0, operations_1.getAgent)(client),
40
42
  listAgents: (0, operations_1.listAgents)(client),
41
43
  deleteAgent: (0, operations_1.deleteAgent)(client)
@@ -1,8 +1,9 @@
1
1
  import { AgentsApi } from "@fonoster/types";
2
- declare function createAgent(agents: AgentsApi): (call: {
2
+ import { CheckRefs } from "../resources/checkRefs";
3
+ declare function createAgent(agents: AgentsApi, checkRefs?: CheckRefs): (call: {
3
4
  request: unknown;
4
5
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
5
- declare function updateAgent(agents: AgentsApi): (call: {
6
+ declare function updateAgent(agents: AgentsApi, checkRefs?: CheckRefs): (call: {
6
7
  request: unknown;
7
8
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
8
9
  declare function getAgent(agents: AgentsApi): (call: {
@@ -24,17 +24,18 @@ exports.updateAgent = updateAgent;
24
24
  * limitations under the License.
25
25
  */
26
26
  const common_1 = require("@fonoster/common");
27
+ const checkRefs_1 = require("../resources/checkRefs");
27
28
  const createResource_1 = require("../resources/createResource");
28
29
  const deleteResource_1 = require("../resources/deleteResource");
29
30
  const getResource_1 = require("../resources/getResource");
30
31
  const listResources_1 = require("../resources/listResources");
31
32
  const updateResource_1 = require("../resources/updateResource");
32
33
  const RESOURCE = "Agent";
33
- function createAgent(agents) {
34
- return (0, createResource_1.createResource)(agents, RESOURCE, common_1.Validators.createAgentRequestSchema);
34
+ function createAgent(agents, checkRefs) {
35
+ return (0, createResource_1.createResource)(agents, RESOURCE, common_1.Validators.createAgentRequestSchema, (0, checkRefs_1.refsChecker)(checkRefs, checkRefs_1.agentRefs));
35
36
  }
36
- function updateAgent(agents) {
37
- return (0, updateResource_1.updateResource)(agents, RESOURCE, common_1.Validators.updateAgentRequestSchema);
37
+ function updateAgent(agents, checkRefs) {
38
+ return (0, updateResource_1.updateResource)(agents, RESOURCE, common_1.Validators.updateAgentRequestSchema, (0, checkRefs_1.refsChecker)(checkRefs, checkRefs_1.agentRefs));
38
39
  }
39
40
  function getAgent(agents) {
40
41
  return (0, getResource_1.getResource)(agents, RESOURCE);
@@ -23,9 +23,11 @@ exports.buildService = buildService;
23
23
  * limitations under the License.
24
24
  */
25
25
  const sdk_1 = __importDefault(require("@routr/sdk"));
26
+ const checkRefs_1 = require("../resources/checkRefs");
26
27
  const operations_1 = require("./operations");
27
28
  function buildService(clientOptions) {
28
29
  const client = new sdk_1.default.Domains(clientOptions);
30
+ const checkRefs = (0, checkRefs_1.createCheckRefs)((0, checkRefs_1.createRefLookups)(clientOptions));
29
31
  return {
30
32
  definition: {
31
33
  serviceName: "Domains",
@@ -34,8 +36,8 @@ function buildService(clientOptions) {
34
36
  proto: "domains.proto"
35
37
  },
36
38
  handlers: {
37
- createDomain: (0, operations_1.createDomain)(client),
38
- updateDomain: (0, operations_1.updateDomain)(client),
39
+ createDomain: (0, operations_1.createDomain)(client, checkRefs),
40
+ updateDomain: (0, operations_1.updateDomain)(client, checkRefs),
39
41
  getDomain: (0, operations_1.getDomain)(client),
40
42
  listDomains: (0, operations_1.listDomains)(client),
41
43
  deleteDomain: (0, operations_1.deleteDomain)(client)
@@ -1,8 +1,9 @@
1
1
  import { DomainsApi } from "@fonoster/types";
2
- declare function createDomain(domains: DomainsApi): (call: {
2
+ import { CheckRefs } from "../resources/checkRefs";
3
+ declare function createDomain(domains: DomainsApi, checkRefs?: CheckRefs): (call: {
3
4
  request: unknown;
4
5
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
5
- declare function updateDomain(domains: DomainsApi): (call: {
6
+ declare function updateDomain(domains: DomainsApi, checkRefs?: CheckRefs): (call: {
6
7
  request: unknown;
7
8
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
8
9
  declare function getDomain(domains: DomainsApi): (call: {
@@ -24,17 +24,18 @@ exports.updateDomain = updateDomain;
24
24
  * limitations under the License.
25
25
  */
26
26
  const common_1 = require("@fonoster/common");
27
+ const checkRefs_1 = require("../resources/checkRefs");
27
28
  const createResource_1 = require("../resources/createResource");
28
29
  const deleteResource_1 = require("../resources/deleteResource");
29
30
  const getResource_1 = require("../resources/getResource");
30
31
  const listResources_1 = require("../resources/listResources");
31
32
  const updateResource_1 = require("../resources/updateResource");
32
33
  const RESOURCE = "Domain";
33
- function createDomain(domains) {
34
- return (0, createResource_1.createResource)(domains, RESOURCE, common_1.Validators.createDomainRequestSchema);
34
+ function createDomain(domains, checkRefs) {
35
+ return (0, createResource_1.createResource)(domains, RESOURCE, common_1.Validators.createDomainRequestSchema, (0, checkRefs_1.refsChecker)(checkRefs, checkRefs_1.domainRefs));
35
36
  }
36
- function updateDomain(domains) {
37
- return (0, updateResource_1.updateResource)(domains, RESOURCE, common_1.Validators.updateDomainRequestSchema);
37
+ function updateDomain(domains, checkRefs) {
38
+ return (0, updateResource_1.updateResource)(domains, RESOURCE, common_1.Validators.updateDomainRequestSchema, (0, checkRefs_1.refsChecker)(checkRefs, checkRefs_1.domainRefs));
38
39
  }
39
40
  function getDomain(domains) {
40
41
  return (0, getResource_1.getResource)(domains, RESOURCE);
@@ -18,7 +18,9 @@
18
18
  */
19
19
  import { NumberPreconditionsCheck } from "@fonoster/common";
20
20
  import { ClientOptions } from "../types";
21
- declare function buildService(clientOptions: ClientOptions, checkNumberPreconditions: NumberPreconditionsCheck): {
21
+ import { NumberRouting } from "./engineRouting";
22
+ /** `routing` picks the peer app-linked numbers point at (default: the media-node peer). */
23
+ declare function buildService(clientOptions: ClientOptions, checkNumberPreconditions: NumberPreconditionsCheck, routing?: NumberRouting): {
22
24
  definition: {
23
25
  serviceName: string;
24
26
  pckg: string;
@@ -5,13 +5,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.buildService = buildService;
7
7
  const sdk_1 = __importDefault(require("@routr/sdk"));
8
+ const checkRefs_1 = require("../resources/checkRefs");
8
9
  const createNumber_1 = require("./createNumber");
9
10
  const getNumber_1 = require("./getNumber");
10
11
  const listNumbers_1 = require("./listNumbers");
11
12
  const operations_1 = require("./operations");
12
13
  const updateNumber_1 = require("./updateNumber");
13
- function buildService(clientOptions, checkNumberPreconditions) {
14
+ /** `routing` picks the peer app-linked numbers point at (default: the media-node peer). */
15
+ function buildService(clientOptions, checkNumberPreconditions, routing) {
14
16
  const client = new sdk_1.default.Numbers(clientOptions);
17
+ const checkRefs = (0, checkRefs_1.createCheckRefs)((0, checkRefs_1.createRefLookups)(clientOptions));
15
18
  return {
16
19
  definition: {
17
20
  serviceName: "Numbers",
@@ -20,8 +23,8 @@ function buildService(clientOptions, checkNumberPreconditions) {
20
23
  proto: "numbers.proto"
21
24
  },
22
25
  handlers: {
23
- createNumber: (0, createNumber_1.createNumber)(client, checkNumberPreconditions),
24
- updateNumber: (0, updateNumber_1.updateNumber)(client, checkNumberPreconditions),
26
+ createNumber: (0, createNumber_1.createNumber)(client, checkNumberPreconditions, routing, checkRefs),
27
+ updateNumber: (0, updateNumber_1.updateNumber)(client, checkNumberPreconditions, routing, checkRefs),
25
28
  getNumber: (0, getNumber_1.getNumber)(client),
26
29
  listNumbers: (0, listNumbers_1.listNumbers)(client),
27
30
  deleteNumber: (0, operations_1.deleteNumber)(client)
@@ -1,5 +1,9 @@
1
1
  import { CreateNumberRequest, UpdateNumberRequest } from "@fonoster/types";
2
- declare function convertToRoutrNumber(number: CreateNumberRequest, accessKeyId: string): {
2
+ /**
3
+ * `appPeerAor` is the peer an app-linked number routes to: the media-node peer by default (see
4
+ * engineRouting.ts).
5
+ */
6
+ declare function convertToRoutrNumber(number: CreateNumberRequest, accessKeyId: string, appPeerAor?: string): {
3
7
  name: string;
4
8
  telUrl: string;
5
9
  aorLink: string;
@@ -13,7 +17,7 @@ declare function convertToRoutrNumber(number: CreateNumberRequest, accessKeyId:
13
17
  trunkRef: string;
14
18
  extended: Record<string, unknown>;
15
19
  };
16
- declare function convertToRoutrNumberUpdate(number: UpdateNumberRequest): {
20
+ declare function convertToRoutrNumberUpdate(number: UpdateNumberRequest, appPeerAor?: string): {
17
21
  ref: string;
18
22
  name: string;
19
23
  aorLink: string;
@@ -21,8 +21,12 @@ exports.convertToRoutrNumberUpdate = convertToRoutrNumberUpdate;
21
21
  * limitations under the License.
22
22
  */
23
23
  const common_1 = require("@fonoster/common");
24
- function convertToRoutrNumber(number, accessKeyId) {
25
- const aorLink = number.appRef ? common_1.ROUTR_DEFAULT_PEER_AOR : number.agentAor;
24
+ /**
25
+ * `appPeerAor` is the peer an app-linked number routes to: the media-node peer by default (see
26
+ * engineRouting.ts).
27
+ */
28
+ function convertToRoutrNumber(number, accessKeyId, appPeerAor = common_1.ROUTR_DEFAULT_PEER_AOR) {
29
+ const aorLink = number.appRef ? appPeerAor : number.agentAor;
26
30
  return {
27
31
  name: number.name,
28
32
  telUrl: number.telUrl,
@@ -42,11 +46,11 @@ function convertToRoutrNumber(number, accessKeyId) {
42
46
  extended: { accessKeyId }
43
47
  };
44
48
  }
45
- function convertToRoutrNumberUpdate(number) {
49
+ function convertToRoutrNumberUpdate(number, appPeerAor = common_1.ROUTR_DEFAULT_PEER_AOR) {
46
50
  let aorLink;
47
51
  let extraHeaders = [];
48
52
  if (number.appRef) {
49
- aorLink = common_1.ROUTR_DEFAULT_PEER_AOR;
53
+ aorLink = appPeerAor;
50
54
  extraHeaders.push({
51
55
  name: common_1.APP_REF_HEADER,
52
56
  value: number.appRef
@@ -18,7 +18,9 @@
18
18
  */
19
19
  import { GrpcErrorMessage, NumberPreconditionsCheck } from "@fonoster/common";
20
20
  import { NumbersApi } from "@fonoster/types";
21
- declare function createNumber(api: NumbersApi, checkNumberPreconditions: NumberPreconditionsCheck): (call: {
21
+ import { CheckRefs } from "../resources/checkRefs";
22
+ import { NumberRouting } from "./engineRouting";
23
+ declare function createNumber(api: NumbersApi, checkNumberPreconditions: NumberPreconditionsCheck, routing?: NumberRouting, checkRefs?: CheckRefs): (call: {
22
24
  request: unknown;
23
25
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
24
26
  export { createNumber };
@@ -30,16 +30,25 @@ exports.createNumber = createNumber;
30
30
  */
31
31
  const common_1 = require("@fonoster/common");
32
32
  const logger_1 = require("@fonoster/logger");
33
+ const checkRefs_1 = require("../resources/checkRefs");
33
34
  const convertToRoutrNumber_1 = require("./convertToRoutrNumber");
34
35
  const logger = (0, logger_1.getLogger)({ service: "sipnet", filePath: __filename });
35
- function createNumber(api, checkNumberPreconditions) {
36
+ function createNumber(api, checkNumberPreconditions, routing, checkRefs) {
36
37
  const fn = (call, callback) => __awaiter(this, void 0, void 0, function* () {
37
38
  const { request } = call;
38
- // Validates that the appRef or agentAor exists in the system
39
- yield checkNumberPreconditions(request);
40
39
  const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
40
+ // Validates that the appRef exists in the caller's workspace (RV-B4)
41
+ yield checkNumberPreconditions(Object.assign(Object.assign({}, request), { accessKeyId }));
42
+ // ...and the trunk too (H7)
43
+ yield (checkRefs === null || checkRefs === void 0 ? void 0 : checkRefs(accessKeyId, (0, checkRefs_1.numberRefs)(request)));
41
44
  logger.verbose("call to createNumber", Object.assign(Object.assign({}, request), { accessKeyId }));
42
- const response = yield api.createNumber((0, convertToRoutrNumber_1.convertToRoutrNumber)(request, accessKeyId));
45
+ const appPeerAor = routing && request.appRef
46
+ ? yield routing.aorFor({
47
+ appRef: request.appRef,
48
+ telUrl: request.telUrl
49
+ })
50
+ : undefined;
51
+ const response = yield api.createNumber((0, convertToRoutrNumber_1.convertToRoutrNumber)(request, accessKeyId, appPeerAor));
43
52
  callback(null, response);
44
53
  });
45
54
  return (0, common_1.withErrorHandlingAndValidation)(fn, common_1.Validators.createNumberRequestSchema);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Where app-linked numbers point (add-media-node 4.1). Asterisk is retired (cleanbreak CB-B3):
3
+ * every app-linked number goes to the media-node peer that Go media nodes register under with
4
+ * least-sessions. `rebuild` is the only engine left; the type stays so repoint reports keep
5
+ * their shape.
6
+ */
7
+ export type VoiceEngine = "rebuild";
8
+ export type EngineRoutingConfig = {
9
+ /** The media-node peer AOR (default sip:medianode@default). */
10
+ mediaNodeAor?: string;
11
+ };
12
+ export declare const MEDIA_NODE_PEER_AOR = "sip:medianode@default";
13
+ /** The AOR of the retired Asterisk peer, for moving numbers created before the rebuild. */
14
+ export declare const LEGACY_ASTERISK_PEER_AOR = "sip:voice@default";
15
+ export type NumberRouting = {
16
+ /** The engine for an app-linked number. */
17
+ engineFor(target: {
18
+ appRef: string;
19
+ telUrl?: string;
20
+ }): Promise<VoiceEngine>;
21
+ /** The peer AOR (Number.aorLink) for an app-linked number. */
22
+ aorFor(target: {
23
+ appRef: string;
24
+ telUrl?: string;
25
+ }): Promise<string>;
26
+ /** The peer AOR of an engine. */
27
+ aorOf(engine: VoiceEngine): string;
28
+ };
29
+ export declare function createNumberRouting(config?: EngineRoutingConfig): NumberRouting;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.LEGACY_ASTERISK_PEER_AOR = exports.MEDIA_NODE_PEER_AOR = void 0;
13
+ exports.createNumberRouting = createNumberRouting;
14
+ /**
15
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
16
+ * http://github.com/fonoster/fonoster
17
+ *
18
+ * This file is part of Fonoster
19
+ *
20
+ * Licensed under the MIT License (the "License");
21
+ * you may not use this file except in compliance with
22
+ * the License. You may obtain a copy of the License at
23
+ *
24
+ * https://opensource.org/licenses/MIT
25
+ *
26
+ * Unless required by applicable law or agreed to in writing, software
27
+ * distributed under the License is distributed on an "AS IS" BASIS,
28
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29
+ * See the License for the specific language governing permissions and
30
+ * limitations under the License.
31
+ */
32
+ const common_1 = require("@fonoster/common");
33
+ exports.MEDIA_NODE_PEER_AOR = common_1.ROUTR_DEFAULT_PEER_AOR;
34
+ /** The AOR of the retired Asterisk peer, for moving numbers created before the rebuild. */
35
+ exports.LEGACY_ASTERISK_PEER_AOR = "sip:voice@default";
36
+ function createNumberRouting(config = {}) {
37
+ var _a;
38
+ const aor = (_a = config.mediaNodeAor) !== null && _a !== void 0 ? _a : exports.MEDIA_NODE_PEER_AOR;
39
+ return {
40
+ engineFor: () => __awaiter(this, void 0, void 0, function* () { return "rebuild"; }),
41
+ aorFor: () => __awaiter(this, void 0, void 0, function* () { return aor; }),
42
+ aorOf: () => aor
43
+ };
44
+ }
@@ -1,22 +1,3 @@
1
- /**
2
- * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
3
- * http://github.com/fonoster/fonoster
4
- *
5
- * This file is part of Fonoster
6
- *
7
- * Licensed under the MIT License (the "License");
8
- * you may not use this file except in compliance with
9
- * the License. You may obtain a copy of the License at
10
- *
11
- * https://opensource.org/licenses/MIT
12
- *
13
- * Unless required by applicable law or agreed to in writing, software
14
- * distributed under the License is distributed on an "AS IS" BASIS,
15
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- * See the License for the specific language governing permissions and
17
- * limitations under the License.
18
- */
19
- import { GrpcErrorMessage } from "@fonoster/common";
20
1
  import { NumbersApi } from "@fonoster/types";
21
2
  declare function getNumber(api: NumbersApi): (call: {
22
3
  request: unknown;
@@ -29,16 +29,16 @@ exports.getNumber = getNumber;
29
29
  * limitations under the License.
30
30
  */
31
31
  const common_1 = require("@fonoster/common");
32
+ const identity_1 = require("@fonoster/identity");
32
33
  const logger_1 = require("@fonoster/logger");
33
34
  const convertToFonosterNumber_1 = require("./convertToFonosterNumber");
34
35
  const logger = (0, logger_1.getLogger)({ service: "sipnet", filePath: __filename });
35
36
  function getNumber(api) {
36
- const fn = (call, callback) => __awaiter(this, void 0, void 0, function* () {
37
- const { request } = call;
38
- const { ref } = request;
37
+ const fn = (call) => __awaiter(this, void 0, void 0, function* () {
38
+ const { ref } = call.request;
39
39
  logger.verbose("call to getNumber", { ref });
40
- const response = yield api.getNumber(ref);
41
- callback(null, (0, convertToFonosterNumber_1.convertToFonosterNumber)(response));
40
+ return (0, convertToFonosterNumber_1.convertToFonosterNumber)(yield api.getNumber(ref));
42
41
  });
43
- return (0, common_1.withErrorHandlingAndValidation)(fn, common_1.Validators.emptySchema);
42
+ // Only the caller's own numbers can be read, like every other resource.
43
+ return (0, common_1.withErrorHandlingAndValidation)((0, identity_1.withAccess)(fn, ((ref) => api.getNumber(ref))), common_1.Validators.emptySchema);
44
44
  }
@@ -17,3 +17,5 @@
17
17
  * limitations under the License.
18
18
  */
19
19
  export { buildService as buildNumbersService } from "./buildService";
20
+ export * from "./engineRouting";
21
+ export * from "./repointNumbers";
@@ -1,4 +1,18 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
17
  exports.buildNumbersService = void 0;
4
18
  /**
@@ -21,3 +35,5 @@ exports.buildNumbersService = void 0;
21
35
  */
22
36
  var buildService_1 = require("./buildService");
23
37
  Object.defineProperty(exports, "buildNumbersService", { enumerable: true, get: function () { return buildService_1.buildService; } });
38
+ __exportStar(require("./engineRouting"), exports);
39
+ __exportStar(require("./repointNumbers"), exports);
@@ -0,0 +1,49 @@
1
+ import { NumberRouting, VoiceEngine } from "./engineRouting";
2
+ /** The slice of Routr's Numbers API that repointing needs (`new SDK.Numbers(...)` fits). */
3
+ export type RoutrNumbersClient = {
4
+ listNumbers(request: {
5
+ pageSize: number;
6
+ pageToken?: string;
7
+ }): Promise<{
8
+ items: RoutrNumber[];
9
+ nextPageToken?: string;
10
+ }>;
11
+ updateNumber(request: {
12
+ ref: string;
13
+ name: string;
14
+ aorLink: string;
15
+ extraHeaders?: {
16
+ name: string;
17
+ value: string;
18
+ }[];
19
+ }): Promise<unknown>;
20
+ };
21
+ export type RoutrNumber = {
22
+ ref: string;
23
+ name: string;
24
+ telUrl: string;
25
+ aorLink?: string;
26
+ extraHeaders?: {
27
+ name: string;
28
+ value: string;
29
+ }[];
30
+ };
31
+ export type RepointChange = {
32
+ ref: string;
33
+ telUrl: string;
34
+ appRef: string;
35
+ engine: VoiceEngine;
36
+ from?: string;
37
+ to: string;
38
+ };
39
+ /**
40
+ * Moves app-linked Numbers to the peer routing says (the media nodes). Routr stores `aorLink` on
41
+ * each Number, so numbers created for Asterisk keep pointing at it until they are rewritten;
42
+ * agent-linked Numbers are never touched. Returns what changed (or would,
43
+ * with `dryRun`).
44
+ */
45
+ export declare function repointNumbers(client: RoutrNumbersClient, routing: NumberRouting, options?: {
46
+ appRefs?: string[];
47
+ dryRun?: boolean;
48
+ pageSize?: number;
49
+ }): Promise<RepointChange[]>;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.repointNumbers = repointNumbers;
13
+ /**
14
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
15
+ * http://github.com/fonoster/fonoster
16
+ *
17
+ * This file is part of Fonoster
18
+ *
19
+ * Licensed under the MIT License (the "License");
20
+ * you may not use this file except in compliance with
21
+ * the License. You may obtain a copy of the License at
22
+ *
23
+ * https://opensource.org/licenses/MIT
24
+ *
25
+ * Unless required by applicable law or agreed to in writing, software
26
+ * distributed under the License is distributed on an "AS IS" BASIS,
27
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28
+ * See the License for the specific language governing permissions and
29
+ * limitations under the License.
30
+ */
31
+ const common_1 = require("@fonoster/common");
32
+ /**
33
+ * Moves app-linked Numbers to the peer routing says (the media nodes). Routr stores `aorLink` on
34
+ * each Number, so numbers created for Asterisk keep pointing at it until they are rewritten;
35
+ * agent-linked Numbers are never touched. Returns what changed (or would,
36
+ * with `dryRun`).
37
+ */
38
+ function repointNumbers(client_1, routing_1) {
39
+ return __awaiter(this, arguments, void 0, function* (client, routing, options = {}) {
40
+ var _a, _b, _c;
41
+ const changes = [];
42
+ let pageToken;
43
+ const numbers = [];
44
+ do {
45
+ const page = yield client.listNumbers({
46
+ pageSize: (_a = options.pageSize) !== null && _a !== void 0 ? _a : 100,
47
+ pageToken
48
+ });
49
+ numbers.push(...page.items);
50
+ pageToken = page.nextPageToken || undefined;
51
+ } while (pageToken);
52
+ for (const n of numbers) {
53
+ // Header names are case-insensitive (sipnet writes x-app-ref; operators may write X-App-Ref).
54
+ const appRef = (_c = (_b = n.extraHeaders) === null || _b === void 0 ? void 0 : _b.find((h) => h.name.toLowerCase() === common_1.APP_REF_HEADER.toLowerCase())) === null || _c === void 0 ? void 0 : _c.value;
55
+ if (!appRef)
56
+ continue;
57
+ if (options.appRefs && !options.appRefs.includes(appRef))
58
+ continue;
59
+ const engine = yield routing.engineFor({ appRef, telUrl: n.telUrl });
60
+ const to = routing.aorOf(engine);
61
+ if (n.aorLink === to)
62
+ continue;
63
+ changes.push({
64
+ ref: n.ref,
65
+ telUrl: n.telUrl,
66
+ appRef,
67
+ engine,
68
+ from: n.aorLink,
69
+ to
70
+ });
71
+ if (!options.dryRun) {
72
+ // Routr's update needs the name; extra headers are kept as they are.
73
+ yield client.updateNumber({
74
+ ref: n.ref,
75
+ name: n.name,
76
+ aorLink: to,
77
+ extraHeaders: n.extraHeaders
78
+ });
79
+ }
80
+ }
81
+ return changes;
82
+ });
83
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ /**
49
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
50
+ * http://github.com/fonoster/fonoster
51
+ *
52
+ * This file is part of Fonoster
53
+ *
54
+ * Licensed under the MIT License (the "License");
55
+ * you may not use this file except in compliance with
56
+ * the License. You may obtain a copy of the License at
57
+ *
58
+ * https://opensource.org/licenses/MIT
59
+ *
60
+ * Unless required by applicable law or agreed to in writing, software
61
+ * distributed under the License is distributed on an "AS IS" BASIS,
62
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
63
+ * See the License for the specific language governing permissions and
64
+ * limitations under the License.
65
+ */
66
+ const fs = __importStar(require("fs"));
67
+ const sdk_1 = __importDefault(require("@routr/sdk"));
68
+ const engineRouting_1 = require("./engineRouting");
69
+ const repointNumbers_1 = require("./repointNumbers");
70
+ /**
71
+ * Moves app-linked Numbers created before the rebuild (pointing at Asterisk's sip:voice@default)
72
+ * to the media-node peer:
73
+ *
74
+ * node dist/numbers/repointNumbersCli.js <routing.json> [--apply] [--app <ref>]...
75
+ *
76
+ * routing.json holds an EngineRoutingConfig ({ mediaNodeAor }); `{}` uses sip:medianode@default.
77
+ * Without --apply it only prints what would change. ROUTR_API_ENDPOINT (default routr:51907)
78
+ * points at Routr's API.
79
+ */
80
+ function main(argv) {
81
+ return __awaiter(this, void 0, void 0, function* () {
82
+ var _a;
83
+ const file = argv[0];
84
+ if (!file) {
85
+ console.error("usage: repointNumbersCli <routing.json> [--apply] [--app <ref>]...");
86
+ process.exit(2);
87
+ }
88
+ const config = JSON.parse(fs.readFileSync(file, "utf8"));
89
+ const appRefs = argv
90
+ .map((a, i) => (a === "--app" ? argv[i + 1] : undefined))
91
+ .filter((a) => !!a);
92
+ const client = new sdk_1.default.Numbers({
93
+ endpoint: (_a = process.env.ROUTR_API_ENDPOINT) !== null && _a !== void 0 ? _a : "routr:51907",
94
+ insecure: true
95
+ });
96
+ const changes = yield (0, repointNumbers_1.repointNumbers)(client, (0, engineRouting_1.createNumberRouting)(config), {
97
+ dryRun: !argv.includes("--apply"),
98
+ appRefs: appRefs.length ? appRefs : undefined
99
+ });
100
+ console.log(JSON.stringify({ applied: argv.includes("--apply"), changes }, null, 2));
101
+ });
102
+ }
103
+ main(process.argv.slice(2)).catch((e) => {
104
+ console.error(e.message);
105
+ process.exit(1);
106
+ });
@@ -16,9 +16,11 @@
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */
19
- import { GrpcErrorMessage, NumberPreconditionsCheck } from "@fonoster/common";
19
+ import { NumberPreconditionsCheck } from "@fonoster/common";
20
20
  import { NumbersApi } from "@fonoster/types";
21
- declare function updateNumber(api: NumbersApi, checkNumberPreconditions: NumberPreconditionsCheck): (call: {
21
+ import { CheckRefs } from "../resources/checkRefs";
22
+ import { NumberRouting } from "./engineRouting";
23
+ declare function updateNumber(api: NumbersApi, checkNumberPreconditions: NumberPreconditionsCheck, routing?: NumberRouting, checkRefs?: CheckRefs): (call: {
22
24
  request: unknown;
23
25
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
24
26
  export { updateNumber };
@@ -29,17 +29,30 @@ exports.updateNumber = updateNumber;
29
29
  * limitations under the License.
30
30
  */
31
31
  const common_1 = require("@fonoster/common");
32
+ const identity_1 = require("@fonoster/identity");
32
33
  const logger_1 = require("@fonoster/logger");
34
+ const checkRefs_1 = require("../resources/checkRefs");
33
35
  const convertToRoutrNumber_1 = require("./convertToRoutrNumber");
34
36
  const logger = (0, logger_1.getLogger)({ service: "sipnet", filePath: __filename });
35
- function updateNumber(api, checkNumberPreconditions) {
36
- const fn = (call, callback) => __awaiter(this, void 0, void 0, function* () {
37
+ function updateNumber(api, checkNumberPreconditions, routing, checkRefs) {
38
+ const fn = (call) => __awaiter(this, void 0, void 0, function* () {
39
+ var _a;
37
40
  const { request } = call;
38
- // Validates that the appRef or agentAor exists in the system
39
- yield checkNumberPreconditions(request);
41
+ // Validates that the appRef exists in the caller's workspace (RV-B4)
42
+ const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
43
+ yield checkNumberPreconditions(Object.assign(Object.assign({}, request), { accessKeyId }));
44
+ // ...and the trunk too (H7)
45
+ yield (checkRefs === null || checkRefs === void 0 ? void 0 : checkRefs(accessKeyId, (0, checkRefs_1.numberRefs)(request)));
40
46
  logger.verbose("call to updateNumber", Object.assign({}, request));
41
- const response = yield api.updateNumber((0, convertToRoutrNumber_1.convertToRoutrNumberUpdate)(request));
42
- callback(null, response);
47
+ // The number's telUrl matters only for per-number pins, so it is read only with routing.
48
+ const appPeerAor = routing && request.appRef
49
+ ? yield routing.aorFor({
50
+ appRef: request.appRef,
51
+ telUrl: (_a = (yield api.getNumber(request.ref))) === null || _a === void 0 ? void 0 : _a.telUrl
52
+ })
53
+ : undefined;
54
+ return yield api.updateNumber((0, convertToRoutrNumber_1.convertToRoutrNumberUpdate)(request, appPeerAor));
43
55
  });
44
- return (0, common_1.withErrorHandlingAndValidation)(fn, common_1.Validators.updateNumberRequestSchema);
56
+ // Only the caller's own numbers can be updated, like every other resource.
57
+ return (0, common_1.withErrorHandlingAndValidation)((0, identity_1.withAccess)(fn, ((ref) => api.getNumber(ref))), common_1.Validators.updateNumberRequestSchema);
45
58
  }
@@ -0,0 +1,26 @@
1
+ import { ClientOptions } from "../types";
2
+ type RefKind = "Acl" | "Credentials" | "Domain" | "Number" | "Trunk";
3
+ type OwnedResource = {
4
+ extended?: Record<string, unknown> | null;
5
+ } | null | undefined;
6
+ type RefLookups = Record<RefKind, (ref: string) => Promise<OwnedResource>>;
7
+ /** One reference in a request: the request field, what it names and its value. */
8
+ type RefField = {
9
+ field: string;
10
+ kind: RefKind;
11
+ ref?: string | null;
12
+ };
13
+ type CheckRefs = (accessKeyId: string | undefined, refs: RefField[]) => Promise<void>;
14
+ /** A request's references, checked before it reaches Routr. */
15
+ type CheckRequestRefs<R> = (request: R, accessKeyId: string | undefined) => Promise<void>;
16
+ declare function createCheckRefs(lookups: Partial<RefLookups>): CheckRefs;
17
+ /** The production lookups: Routr's API, one client per kind. */
18
+ declare function createRefLookups(clientOptions: ClientOptions): RefLookups;
19
+ type Refs = Record<string, unknown>;
20
+ declare const agentRefs: (r: Refs) => RefField[];
21
+ declare const trunkRefs: (r: Refs) => RefField[];
22
+ declare const domainRefs: (r: Refs) => RefField[];
23
+ declare const numberRefs: (r: Refs) => RefField[];
24
+ /** The reference check for one resource type's requests. */
25
+ declare function refsChecker<R>(checkRefs: CheckRefs | undefined, refsOf: (request: Refs) => RefField[]): CheckRequestRefs<R> | undefined;
26
+ export { agentRefs, CheckRefs, CheckRequestRefs, createCheckRefs, createRefLookups, domainRefs, numberRefs, RefField, RefKind, RefLookups, refsChecker, trunkRefs };
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.trunkRefs = exports.numberRefs = exports.domainRefs = exports.agentRefs = void 0;
16
+ exports.createCheckRefs = createCheckRefs;
17
+ exports.createRefLookups = createRefLookups;
18
+ exports.refsChecker = refsChecker;
19
+ /**
20
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
21
+ * http://github.com/fonoster/fonoster
22
+ *
23
+ * This file is part of Fonoster
24
+ *
25
+ * Licensed under the MIT License (the "License");
26
+ * you may not use this file except in compliance with
27
+ * the License. You may obtain a copy of the License at
28
+ *
29
+ * https://opensource.org/licenses/MIT
30
+ *
31
+ * Unless required by applicable law or agreed to in writing, software
32
+ * distributed under the License is distributed on an "AS IS" BASIS,
33
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34
+ * See the License for the specific language governing permissions and
35
+ * limitations under the License.
36
+ */
37
+ const common_1 = require("@fonoster/common");
38
+ const grpc_js_1 = require("@grpc/grpc-js");
39
+ const sdk_1 = __importDefault(require("@routr/sdk"));
40
+ const isNotFound = (e) => (e === null || e === void 0 ? void 0 : e.code) === grpc_js_1.status.NOT_FOUND;
41
+ function createCheckRefs(lookups) {
42
+ return (accessKeyId, refs) => __awaiter(this, void 0, void 0, function* () {
43
+ var _a;
44
+ const given = refs.filter((r) => typeof r.ref === "string" && r.ref !== "");
45
+ if (given.length === 0)
46
+ return;
47
+ if (!accessKeyId) {
48
+ throw new common_1.GrpcError(grpc_js_1.status.PERMISSION_DENIED, "the caller's workspace is required to reference other resources");
49
+ }
50
+ for (const { field, kind, ref } of given) {
51
+ const lookup = lookups[kind];
52
+ if (!lookup)
53
+ throw new Error(`no lookup for ${kind} references`);
54
+ let resource;
55
+ try {
56
+ resource = yield lookup(ref);
57
+ }
58
+ catch (e) {
59
+ if (!isNotFound(e))
60
+ throw e;
61
+ resource = undefined;
62
+ }
63
+ // Another workspace's resource is reported like a missing one, so refs of other
64
+ // workspaces are not confirmed.
65
+ if (((_a = resource === null || resource === void 0 ? void 0 : resource.extended) === null || _a === void 0 ? void 0 : _a.accessKeyId) !== accessKeyId) {
66
+ throw new common_1.GrpcError(grpc_js_1.status.INVALID_ARGUMENT, `${field} ${ref} not found`);
67
+ }
68
+ }
69
+ });
70
+ }
71
+ /** The production lookups: Routr's API, one client per kind. */
72
+ function createRefLookups(clientOptions) {
73
+ const acls = new sdk_1.default.Acls(clientOptions);
74
+ const credentials = new sdk_1.default.Credentials(clientOptions);
75
+ const domains = new sdk_1.default.Domains(clientOptions);
76
+ const numbers = new sdk_1.default.Numbers(clientOptions);
77
+ const trunks = new sdk_1.default.Trunks(clientOptions);
78
+ return {
79
+ Acl: (ref) => acls.getAcl(ref),
80
+ Credentials: (ref) => credentials.getCredentials(ref),
81
+ Domain: (ref) => domains.getDomain(ref),
82
+ Number: (ref) => numbers.getNumber(ref),
83
+ Trunk: (ref) => trunks.getTrunk(ref)
84
+ };
85
+ }
86
+ const agentRefs = (r) => [
87
+ { field: "domainRef", kind: "Domain", ref: r.domainRef },
88
+ {
89
+ field: "credentialsRef",
90
+ kind: "Credentials",
91
+ ref: r.credentialsRef
92
+ }
93
+ ];
94
+ exports.agentRefs = agentRefs;
95
+ const trunkRefs = (r) => [
96
+ {
97
+ field: "accessControlListRef",
98
+ kind: "Acl",
99
+ ref: r.accessControlListRef
100
+ },
101
+ {
102
+ field: "inboundCredentialsRef",
103
+ kind: "Credentials",
104
+ ref: r.inboundCredentialsRef
105
+ },
106
+ {
107
+ field: "outboundCredentialsRef",
108
+ kind: "Credentials",
109
+ ref: r.outboundCredentialsRef
110
+ }
111
+ ];
112
+ exports.trunkRefs = trunkRefs;
113
+ const domainRefs = (r) => {
114
+ var _a;
115
+ return [
116
+ {
117
+ field: "accessControlListRef",
118
+ kind: "Acl",
119
+ ref: r.accessControlListRef
120
+ },
121
+ ...((_a = r.egressPolicies) !== null && _a !== void 0 ? _a : []).map((p, i) => ({
122
+ field: `egressPolicies[${i}].numberRef`,
123
+ kind: "Number",
124
+ ref: p === null || p === void 0 ? void 0 : p.numberRef
125
+ }))
126
+ ];
127
+ };
128
+ exports.domainRefs = domainRefs;
129
+ const numberRefs = (r) => [
130
+ { field: "trunkRef", kind: "Trunk", ref: r.trunkRef }
131
+ ];
132
+ exports.numberRefs = numberRefs;
133
+ /** The reference check for one resource type's requests. */
134
+ function refsChecker(checkRefs, refsOf) {
135
+ return checkRefs
136
+ ? (request, accessKeyId) => checkRefs(accessKeyId, refsOf(request))
137
+ : undefined;
138
+ }
@@ -18,7 +18,8 @@
18
18
  */
19
19
  import { GrpcErrorMessage } from "@fonoster/common";
20
20
  import { z } from "zod";
21
- declare function createResource<T, R, U>(api: U, resource: string, schema: z.ZodSchema): (call: {
21
+ import { CheckRequestRefs } from "./checkRefs";
22
+ declare function createResource<T, R, U>(api: U, resource: string, schema: z.ZodSchema, checkRefs?: CheckRequestRefs<R>): (call: {
22
23
  request: unknown;
23
24
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
24
25
  export { createResource };
@@ -31,11 +31,14 @@ exports.createResource = createResource;
31
31
  const common_1 = require("@fonoster/common");
32
32
  const logger_1 = require("@fonoster/logger");
33
33
  const logger = (0, logger_1.getLogger)({ service: "sipnet", filePath: __filename });
34
- function createResource(api, resource, schema) {
34
+ function createResource(api, resource, schema, checkRefs) {
35
35
  const fn = (call, callback) => __awaiter(this, void 0, void 0, function* () {
36
36
  const { request } = call;
37
37
  const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
38
- logger.verbose(`call to create${resource}`, Object.assign(Object.assign({}, request), { accessKeyId }));
38
+ // Never the request itself: credentials and trunks carry passwords (M11).
39
+ logger.verbose(`call to create${resource}`, { accessKeyId });
40
+ // Referenced credentials, ACLs, domains, numbers and trunks must be the caller's (H7).
41
+ yield (checkRefs === null || checkRefs === void 0 ? void 0 : checkRefs(request, accessKeyId));
39
42
  const response = yield api[`create${resource}`](Object.assign(Object.assign({}, request), { extended: {
40
43
  accessKeyId
41
44
  } }));
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
- declare function updateResource<T, R, U>(api: U, resource: string, schema: z.ZodSchema): (call: {
2
+ import { CheckRequestRefs } from "./checkRefs";
3
+ declare function updateResource<T, R, U>(api: U, resource: string, schema: z.ZodSchema, checkRefs?: CheckRequestRefs<R>): (call: {
3
4
  request: unknown;
4
5
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
5
6
  export { updateResource };
@@ -32,10 +32,15 @@ const common_1 = require("@fonoster/common");
32
32
  const identity_1 = require("@fonoster/identity");
33
33
  const logger_1 = require("@fonoster/logger");
34
34
  const logger = (0, logger_1.getLogger)({ service: "sipnet", filePath: __filename });
35
- function updateResource(api, resource, schema) {
35
+ function updateResource(api, resource, schema, checkRefs) {
36
36
  const fn = (call) => __awaiter(this, void 0, void 0, function* () {
37
37
  const { request } = call;
38
- logger.verbose(`call to update${resource}`, Object.assign({}, request));
38
+ // Never the request itself: credentials and trunks carry passwords (M11).
39
+ logger.verbose(`call to update${resource}`, {
40
+ ref: request.ref
41
+ });
42
+ // Referenced credentials, ACLs, domains, numbers and trunks must be the caller's (H7).
43
+ yield (checkRefs === null || checkRefs === void 0 ? void 0 : checkRefs(request, (0, common_1.getAccessKeyIdFromCall)(call)));
39
44
  return yield api[`update${resource}`](request);
40
45
  });
41
46
  return (0, common_1.withErrorHandlingAndValidation)((0, identity_1.withAccess)(fn, (ref) => api[`get${resource}`](ref)), schema);
@@ -23,9 +23,11 @@ exports.buildService = buildService;
23
23
  * limitations under the License.
24
24
  */
25
25
  const sdk_1 = __importDefault(require("@routr/sdk"));
26
+ const checkRefs_1 = require("../resources/checkRefs");
26
27
  const operations_1 = require("./operations");
27
28
  function buildService(clientOptions) {
28
29
  const client = new sdk_1.default.Trunks(clientOptions);
30
+ const checkRefs = (0, checkRefs_1.createCheckRefs)((0, checkRefs_1.createRefLookups)(clientOptions));
29
31
  return {
30
32
  definition: {
31
33
  serviceName: "Trunks",
@@ -34,8 +36,8 @@ function buildService(clientOptions) {
34
36
  proto: "trunks.proto"
35
37
  },
36
38
  handlers: {
37
- createTrunk: (0, operations_1.createTrunk)(client),
38
- updateTrunk: (0, operations_1.updateTrunk)(client),
39
+ createTrunk: (0, operations_1.createTrunk)(client, checkRefs),
40
+ updateTrunk: (0, operations_1.updateTrunk)(client, checkRefs),
39
41
  getTrunk: (0, operations_1.getTrunk)(client),
40
42
  listTrunks: (0, operations_1.listTrunks)(client),
41
43
  deleteTrunk: (0, operations_1.deleteTrunk)(client)
@@ -1,8 +1,15 @@
1
- import { TrunkApi } from "@fonoster/types";
2
- declare function createTrunk(trunks: TrunkApi): (call: {
1
+ import { TrunkApi, TrunkExtended, UpdateTrunkRequest } from "@fonoster/types";
2
+ import { CheckRefs } from "../resources/checkRefs";
3
+ declare function createTrunk(trunks: TrunkApi, checkRefs?: CheckRefs): (call: {
3
4
  request: unknown;
4
5
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
5
- declare function updateTrunk(trunks: TrunkApi): (call: {
6
+ /**
7
+ * A partial update: fields the request leaves blank keep their current value (proto3 sends ""
8
+ * and [] for fields not set, and Routr rejects a trunk without a name or inbound URI). Clearing
9
+ * the URIs or a reference through an update is therefore not possible; recreate the trunk.
10
+ */
11
+ export declare function mergeTrunkUpdate(request: UpdateTrunkRequest, current: Partial<TrunkExtended>): UpdateTrunkRequest;
12
+ declare function updateTrunk(trunks: TrunkApi, checkRefs?: CheckRefs): (call: {
6
13
  request: unknown;
7
14
  }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
8
15
  declare function getTrunk(trunks: TrunkApi): (call: {
@@ -1,5 +1,15 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.mergeTrunkUpdate = mergeTrunkUpdate;
3
13
  exports.createTrunk = createTrunk;
4
14
  exports.deleteTrunk = deleteTrunk;
5
15
  exports.getTrunk = getTrunk;
@@ -24,17 +34,43 @@ exports.updateTrunk = updateTrunk;
24
34
  * limitations under the License.
25
35
  */
26
36
  const common_1 = require("@fonoster/common");
37
+ const checkRefs_1 = require("../resources/checkRefs");
27
38
  const createResource_1 = require("../resources/createResource");
28
39
  const deleteResource_1 = require("../resources/deleteResource");
29
40
  const getResource_1 = require("../resources/getResource");
30
41
  const listResources_1 = require("../resources/listResources");
31
42
  const updateResource_1 = require("../resources/updateResource");
32
43
  const RESOURCE = "Trunk";
33
- function createTrunk(trunks) {
34
- return (0, createResource_1.createResource)(trunks, RESOURCE, common_1.Validators.createTrunkRequestSchema);
44
+ function createTrunk(trunks, checkRefs) {
45
+ return (0, createResource_1.createResource)(trunks, RESOURCE, common_1.Validators.createTrunkRequestSchema, (0, checkRefs_1.refsChecker)(checkRefs, checkRefs_1.trunkRefs));
35
46
  }
36
- function updateTrunk(trunks) {
37
- return (0, updateResource_1.updateResource)(trunks, RESOURCE, common_1.Validators.updateTrunkRequestSchema);
47
+ const blank = (v) => v === undefined || v === null || v === "" || (Array.isArray(v) && !v.length);
48
+ /**
49
+ * A partial update: fields the request leaves blank keep their current value (proto3 sends ""
50
+ * and [] for fields not set, and Routr rejects a trunk without a name or inbound URI). Clearing
51
+ * the URIs or a reference through an update is therefore not possible; recreate the trunk.
52
+ */
53
+ function mergeTrunkUpdate(request, current) {
54
+ const merged = Object.assign({}, request);
55
+ for (const key of [
56
+ "name",
57
+ "inboundUri",
58
+ "accessControlListRef",
59
+ "inboundCredentialsRef",
60
+ "outboundCredentialsRef",
61
+ "uris"
62
+ ]) {
63
+ const value = current[key];
64
+ if (blank(merged[key]) && !blank(value))
65
+ merged[key] = value;
66
+ }
67
+ return merged;
68
+ }
69
+ function updateTrunk(trunks, checkRefs) {
70
+ const partial = Object.assign(Object.assign({}, trunks), { getTrunk: (ref) => trunks.getTrunk(ref), updateTrunk: (request) => __awaiter(this, void 0, void 0, function* () {
71
+ return trunks.updateTrunk(mergeTrunkUpdate(request, yield trunks.getTrunk(request.ref)));
72
+ }) });
73
+ return (0, updateResource_1.updateResource)(partial, RESOURCE, common_1.Validators.updateTrunkRequestSchema, (0, checkRefs_1.refsChecker)(checkRefs, checkRefs_1.trunkRefs));
38
74
  }
39
75
  function getTrunk(trunks) {
40
76
  return (0, getResource_1.getResource)(trunks, RESOURCE);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonoster/sipnet",
3
- "version": "0.26.0",
3
+ "version": "0.30.0",
4
4
  "description": "Routr-based SIP stack",
5
5
  "author": "Pedro Sanders <psanders@fonoster.com>",
6
6
  "homepage": "https://github.com/fonoster/fonoster#readme",
@@ -20,12 +20,12 @@
20
20
  "fonoster": "./dist/index.js"
21
21
  },
22
22
  "dependencies": {
23
- "@fonoster/common": "^0.26.0",
24
- "@fonoster/identity": "^0.26.0",
25
- "@fonoster/logger": "^0.26.0",
26
- "@fonoster/types": "^0.26.0",
27
- "@grpc/grpc-js": "~1.10.6",
28
- "@routr/sdk": "2.13.1",
23
+ "@fonoster/common": "^0.30.0",
24
+ "@fonoster/identity": "^0.30.0",
25
+ "@fonoster/logger": "^0.30.0",
26
+ "@fonoster/types": "^0.30.0",
27
+ "@grpc/grpc-js": "~1.14.6",
28
+ "@routr/sdk": "2.15.2",
29
29
  "zod": "^3.23.8"
30
30
  },
31
31
  "files": [
@@ -41,5 +41,5 @@
41
41
  "bugs": {
42
42
  "url": "https://github.com/fonoster/fonoster/issues"
43
43
  },
44
- "gitHead": "d14f414d791a33f92e8c4225f7e462abc440a142"
44
+ "gitHead": "b59b06a3ef325abe3f188c0fb87cc22d50ed5471"
45
45
  }