Skip to content

Commit 3210b56

Browse files
committed
Merge branch 'call-forwarding' into 'release-25.7'
Call forwarding See merge request nwac/sdk-ts!120
2 parents c7c8564 + 3422dfb commit 3210b56

File tree

6 files changed

+305
-0
lines changed

6 files changed

+305
-0
lines changed

examples/call-forwarding-signal.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { NetworkAsCodeClient } from "network-as-code";
2+
3+
// We begin by creating a Network as Code client
4+
const client = new NetworkAsCodeClient("<your-application-key-here>");
5+
6+
// Then, create a device object for the phone number you want to check
7+
const myDevice = client.devices.get({
8+
// The phone number accepts the "+" sign, but not spaces or "()" marks
9+
phoneNumber: "+367123456"
10+
});
11+
12+
// Verify, if a device has an active, unconditional call forwarding
13+
const result = await myDevice.verifyUnconditionalForwarding()
14+
15+
// Show the result
16+
console.log(result)
17+
18+
// To get information about an active "call forwarding setup" for the given device,
19+
// use the following snippet:
20+
const services = await myDevice.getCallForwarding()
21+
22+
// Show active Call Forwarding Services
23+
console.log(services)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { beforeAll, describe, expect } from "@jest/globals";
2+
import "dotenv/config";
3+
import { NetworkAsCodeClient } from "../src";
4+
import { configureClient } from "./configClient";
5+
6+
let client: NetworkAsCodeClient;
7+
8+
beforeAll((): any => {
9+
client = configureClient()
10+
});
11+
12+
describe("Call forwarding retrieval and verification of unconditional forwarding", () => {
13+
it("should check if device is unconditionally forwarding a call", async () => {
14+
let device = client.devices.get({
15+
phoneNumber: "+99999991111"
16+
});
17+
18+
let response = await device.verifyUnconditionalForwarding()
19+
expect(response).toBe(true)
20+
});
21+
22+
it("should check if device is not unconditionally forwarding a call", async () => {
23+
let device = client.devices.get({
24+
phoneNumber: "+999999991001"
25+
});
26+
27+
let response = await device.verifyUnconditionalForwarding()
28+
expect(response).toBe(false)
29+
})
30+
31+
it("gets list of call forwarding services", async () => {
32+
let device = client.devices.get({
33+
phoneNumber: "+999999991111"
34+
});
35+
36+
let response = await device.getCallForwarding()
37+
let types = ['inactive', 'unconditional', 'conditional_busy', 'conditional_not_reachable', 'conditional_no_answer']
38+
39+
expect(response instanceof Array).toBeTruthy();
40+
expect(response.every((val) => types.includes(val)));
41+
});
42+
43+
});

src/api/callForwardingApi.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Copyright 2025 Nokia
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { errorHandler } from "../errors";
18+
import { ProxyAgent } from "proxy-agent";
19+
20+
import fetch from "node-fetch";
21+
22+
export class CallForwardingApi {
23+
private baseUrl: string;
24+
private headers: HeadersInit;
25+
private agent: ProxyAgent;
26+
27+
constructor(
28+
baseUrl: string,
29+
rapidKey: string,
30+
rapidHost: string,
31+
agent: ProxyAgent
32+
) {
33+
this.baseUrl = baseUrl;
34+
this.headers = {
35+
"Content-Type": "application/json",
36+
"X-RapidAPI-Host": rapidHost,
37+
"X-RapidAPI-Key": rapidKey,
38+
};
39+
this.agent = agent;
40+
}
41+
42+
async retrieveCallForwarding(phoneNumber: string) {
43+
const response = await fetch(`${this.baseUrl}/call-forwardings`, {
44+
method: "POST",
45+
headers: this.headers,
46+
body: JSON.stringify({ phoneNumber }),
47+
agent: this.agent,
48+
});
49+
50+
errorHandler(response);
51+
52+
return await response.json();
53+
}
54+
55+
async verifyUnconditionalForwarding(phoneNumber: string) {
56+
const response = await fetch(`${this.baseUrl}/unconditional-call-forwardings`, {
57+
method: "POST",
58+
headers: this.headers,
59+
body: JSON.stringify({ phoneNumber }),
60+
agent: this.agent,
61+
});
62+
63+
errorHandler(response);
64+
65+
return await response.json();
66+
}
67+
68+
}

src/api/client.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { AuthorizationEndpointsAPI } from "./authorizatioEndpointsApi";
2727
import { CredentialsAPI } from "./credentialsApi";
2828
import { NumberVerificationAPI } from "./numberVerificationApi";
2929
import { AccessTokenAPI } from "./accessTokenApi";
30+
import { CallForwardingApi } from "./callForwardingApi";
3031

3132
const QOS_URL = "/qod/v0";
3233

@@ -52,6 +53,8 @@ const AUTHORIZATION_ENDPOINTS_URL = "/.well-known";
5253

5354
const NUMBER_VERIFICATION_URL = "/passthrough/camara/v1/number-verification/number-verification/v0";
5455

56+
const CALL_FORWARDING_URL = "/passthrough/camara/v1/call-forwarding-signal/call-forwarding-signal/v0.3"
57+
5558
const agent = new ProxyAgent();
5659

5760
/*
@@ -95,6 +98,7 @@ export class APIClient {
9598
credentials: CredentialsAPI;
9699
verification: NumberVerificationAPI;
97100
accesstoken: AccessTokenAPI;
101+
callForwarding: CallForwardingApi;
98102

99103
constructor(
100104
token: string,
@@ -111,6 +115,7 @@ export class APIClient {
111115
authorizationEndpointsBaseUrl: string | undefined = undefined,
112116
credentialsBaseUrl: string | undefined = undefined,
113117
verificationBaseUrl: string | undefined = undefined,
118+
callForwardingBaseUrl: string | undefined = undefined,
114119
) {
115120
const baseUrl = environmentBaseUrl(envMode);
116121
const hostname = environmentHostname(envMode);
@@ -248,5 +253,16 @@ export class APIClient {
248253
this.accesstoken = new AccessTokenAPI(
249254
agent
250255
);
256+
257+
if (!callForwardingBaseUrl) {
258+
callForwardingBaseUrl = `${baseUrl}${CALL_FORWARDING_URL}`
259+
}
260+
261+
this.callForwarding = new CallForwardingApi(
262+
callForwardingBaseUrl,
263+
token,
264+
hostname,
265+
agent
266+
);
251267
}
252268
}

src/models/device.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,36 @@ export class Device {
426426

427427
}
428428

429+
/**
430+
* Get the information about Call Forwarding Services active for the given device.
431+
* @returns string[]: Active Call Forwarding Service types for the given device.
432+
*/
433+
async getCallForwarding(): Promise<string[]> {
434+
if (!this.phoneNumber) {
435+
throw new InvalidParameterError("Device phone number is required.");
436+
}
437+
438+
const response: any = await this._api.callForwarding.retrieveCallForwarding(
439+
this.phoneNumber
440+
);
441+
442+
return response;
443+
}
444+
445+
/**
446+
* Verify if device has unconditional call forwarding active.
447+
* @returns true/false
448+
*/
449+
async verifyUnconditionalForwarding(): Promise<boolean> {
450+
if (!this.phoneNumber) {
451+
throw new InvalidParameterError("Device phone number is required.");
452+
}
453+
454+
const response: any = await this._api.callForwarding.verifyUnconditionalForwarding(
455+
this.phoneNumber
456+
);
457+
458+
return response['active'];
459+
}
460+
429461
}

tests/callForwarding.test.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import fetchMock from '@fetch-mock/jest';
2+
3+
import { NetworkAsCodeClient } from "../src";
4+
import { Device } from "../src/models/device";
5+
import { InvalidParameterError } from "../src/errors";
6+
7+
jest.mock("node-fetch", () => {
8+
const nodeFetch = jest.requireActual("node-fetch");
9+
// only needed if your application makes use of Response, Request
10+
// or Headers classes directly
11+
Object.assign(fetchMock.config, {
12+
fetch: nodeFetch,
13+
Response: nodeFetch.Response,
14+
Request: nodeFetch.Request,
15+
Headers: nodeFetch.Headers
16+
});
17+
return fetchMock.fetchHandler;
18+
});
19+
20+
let client: NetworkAsCodeClient;
21+
22+
let device: Device;
23+
24+
beforeAll((): any => {
25+
client = new NetworkAsCodeClient("TEST_TOKEN");
26+
device = client.devices.get({
27+
phoneNumber: "+999999991000"
28+
});
29+
return client;
30+
});
31+
32+
beforeEach(() => {
33+
fetchMock.mockReset();
34+
});
35+
36+
afterEach(() => {
37+
fetchMock.unmockGlobal();
38+
});
39+
40+
describe("Call Forwarding Signal", () => {
41+
it("should verify unconditional call forwarding", async () => {
42+
fetchMock.mockGlobal().post(
43+
"https://network-as-code.p-eu.rapidapi.com/passthrough/camara/v1/call-forwarding-signal/call-forwarding-signal/v0.3/unconditional-call-forwardings",
44+
(_: any, req: any): any => {
45+
expect(JSON.parse(req.body.toString())).toEqual({
46+
phoneNumber: "+999999991000"
47+
});
48+
},
49+
{ response: Promise.resolve({
50+
body: JSON.stringify({
51+
active: true
52+
})
53+
})
54+
}
55+
);
56+
57+
const result = await device.verifyUnconditionalForwarding();
58+
expect(result).toBe(true);
59+
});
60+
61+
it("should get list of call forwarding services", async () => {
62+
fetchMock.mockGlobal().post(
63+
"https://network-as-code.p-eu.rapidapi.com/passthrough/camara/v1/call-forwarding-signal/call-forwarding-signal/v0.3/call-forwardings",
64+
(_: any, req: any): any => {
65+
expect(JSON.parse(req.body.toString())).toEqual({
66+
phoneNumber: "+999999991000"
67+
});
68+
},
69+
{ response: Promise.resolve({
70+
body: JSON.stringify([
71+
"unconditional",
72+
"conditional_busy",
73+
"conditional_no_answer"
74+
])
75+
})
76+
}
77+
);
78+
79+
const result = await device.getCallForwarding();
80+
let types = ['inactive', 'unconditional', 'conditional_busy', 'conditional_not_reachable', 'conditional_no_answer']
81+
82+
expect(result instanceof Array).toBeTruthy();
83+
expect(result.every((val) => types.includes(val)));
84+
});
85+
86+
it("should raise exception if verifying forwarding without phone number", async () => {
87+
fetchMock.mockGlobal().post(
88+
"https://network-as-code.p-eu.rapidapi.com/passthrough/camara/v1/call-forwarding-signal/call-forwarding-signal/v0.3/unconditional-call-forwardings",
89+
(_: any, req: any): any => {
90+
expect(JSON.parse(req.body.toString())).toEqual({
91+
networkAccessIdentifier: "device@testcsp.net"
92+
});
93+
}
94+
);
95+
96+
const deviceWithoutNumber = client.devices.get({
97+
networkAccessIdentifier: "device@testcsp.net"
98+
});
99+
100+
expect(
101+
async () => await deviceWithoutNumber.verifyUnconditionalForwarding()
102+
).rejects.toThrow(InvalidParameterError);
103+
});
104+
105+
it("should raise exception if getting forwarding services without phone number", async () => {
106+
fetchMock.mockGlobal().post(
107+
"https://network-as-code.p-eu.rapidapi.com/passthrough/camara/v1/call-forwarding-signal/call-forwarding-signal/v0.3/call-forwardings",
108+
(_: any, req: any): any => {
109+
expect(JSON.parse(req.body.toString())).toEqual({
110+
networkAccessIdentifier: "device@testcsp.net"
111+
});
112+
}
113+
);
114+
115+
const deviceWithoutNumber = client.devices.get({
116+
networkAccessIdentifier: "device@testcsp.net"
117+
});
118+
119+
expect(
120+
async () => await deviceWithoutNumber.getCallForwarding()
121+
).rejects.toThrow(InvalidParameterError);
122+
});
123+
});

0 commit comments

Comments
 (0)