Skip to content

Commit

Permalink
Implementing Apps API
Browse files Browse the repository at this point in the history
  • Loading branch information
psanders committed Feb 6, 2022
1 parent 3cc425d commit 6b6218a
Show file tree
Hide file tree
Showing 29 changed files with 312 additions and 145 deletions.
2 changes: 1 addition & 1 deletion .helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ mediaserver:
# externPort is the public facing port
externPort: 6060
# proxyUsername username at SIPProxy
proxyUsername: ast
proxyUsername: ms
# replicproxySecret secret at SIPProxy
proxySecret: changeit
# replicaCount is the number of MediaServer nodes
Expand Down
3 changes: 2 additions & 1 deletion env_example
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ MS_ARI_INTERNAL_URL=http://localhost:8088
MS_ARI_USERNAME=ari
MS_ARI_SECRET=changeit
MS_TRUNK=routr
MS_ENDPOINT=sip:ast@node1
VOICE_URL=http://voice.fonoster:3000
MS_ENDPOINT=sip:voice@default
MS_CONTEXT=local-ctx
MS_EXTENSION=s
DS_HOST=yourapihost
Expand Down
11 changes: 5 additions & 6 deletions etc/bootstrap.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# Media Controller
# Voice Media Server

- apiVersion: v1beta1
kind: Peer
metadata:
name: Media Controller @ Node 1
name: Voice Media Server @ Default Region
spec:
device: node1
device: default
credentials:
username: ast
secret: 'changeit'

username: voice
secret: '6d1dea885027e3489322711f9708a7b7'
2 changes: 2 additions & 0 deletions etc/service_envs.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
{
"module": "numbers",
"env": [
"VOICE_URL",
"MS_ENDPOINT",
"SIPPROXY_HOST",
"SIPPROXY_API_PORT",
"SIPPROXY_API_USERNAME",
Expand Down
1 change: 1 addition & 0 deletions mods/callmanager/src/client/callmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default class CallManager
r.setFrom(request.from);
r.setTo(request.to);
r.setWebhook(request.webhook);
r.setAppRef(request.appRef);
r.setIgnoreE164Validation(request.ignoreE164Validation);
r.setMetadata(metadata);

Expand Down
1 change: 1 addition & 0 deletions mods/callmanager/src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface CallRequest {
from: string;
to: string;
webhook?: string;
appRef?: string;
metadata?: Record<string, unknown>;
ignoreE164Validation?: boolean;
}
Expand Down
2 changes: 2 additions & 0 deletions mods/callmanager/src/protos/callmanager.proto
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ message CallRequest {
bool ignore_e164_validation = 4;
// Optional metadata for voice applications
string metadata = 5;
// Optional app reference
string app_ref = 6;
}

message CallResponse {
Expand Down
32 changes: 32 additions & 0 deletions mods/callmanager/src/service/assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FonosterInvalidArgument } from "@fonoster/errors";
import phone from "phone";
import { CallManagerPB } from "../client/callmanager";

export const assertCompatibleParameters = (
request: CallManagerPB.CallRequest
) => {
if (request.getWebhook() && request.getAppRef()) {
throw new FonosterInvalidArgument(
"webhook and appRef cannot be used together"
);
}
};

const isValidURL= (value: string) =>
/^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/.test(value);

export const assertIsE164 = (number: string, field: string) => {
if (phone(number).length === 0) {
throw new FonosterInvalidArgument(
`field "${field}" must be a valid e164 value.`
);
}
};

export const assertWebhookIsURL = (webhook: string) => {
if (webhook && !isValidURL(webhook)) {
throw new FonosterInvalidArgument(
"webhook field must be a valid URL."
);
}
};
55 changes: 20 additions & 35 deletions mods/callmanager/src/service/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {CallRequest, CallResponse} from "./protos/callmanager_pb";
import {nanoid} from "nanoid";
import {FonosterError} from "@fonoster/errors";
import phone from "phone";
import {EndpointInfo} from "../client/types";
import { CallRequest, CallResponse } from "./protos/callmanager_pb";
import { nanoid } from "nanoid";
import { EndpointInfo } from "../client/types";
import { assertCompatibleParameters, assertIsE164, assertWebhookIsURL } from "./assertions";

export default async function (
request: CallRequest,
channel: any,
endpointInfo: EndpointInfo
): Promise<CallResponse> {
if (
!request.getIgnoreE164Validation() &&
phone(request.getFrom()).length === 0
) {
throw new FonosterError("invalid e164 number");
}

if (
!request.getIgnoreE164Validation() &&
phone(request.getTo()).length === 0
) {
throw new FonosterError("invalid e164 number");
}
assertCompatibleParameters(request);
if (!request.getIgnoreE164Validation())
assertIsE164(request.getFrom(), 'from')
if (!request.getIgnoreE164Validation())
assertIsE164(request.getFrom(), 'to')
if (request.getWebhook())
assertWebhookIsURL(request.getWebhook())

const response = new CallResponse();
response.setRef(nanoid());

const variables = !request.getWebhook()
? {
DID_INFO: request.getFrom(),
REF: response.getRef(),
METADATA: request.getMetadata()
}
: {
DID_INFO: request.getFrom(),
WEBHOOK: request.getWebhook(),
REF: response.getRef(),
METADATA: request.getMetadata()
};

await channel.originate({
context: endpointInfo.context,
extension: endpointInfo.extension,
endpoint: `PJSIP/${endpointInfo.trunk}/sip:${request.getTo()}@${
endpointInfo.domain
}`,
variables
endpoint: `PJSIP/${endpointInfo.trunk}/sip:${request.getTo()}@${endpointInfo.domain
}`,
variables: {
DID_INFO: request.getFrom(),
REF: response.getRef(),
METADATA: request.getMetadata(),
WEBHOOK: request.getWebhook(),
APP_REF: request.getAppRef()
}
});

return response;
Expand Down
3 changes: 3 additions & 0 deletions mods/callmanager/src/service/protos/callmanager_pb.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class CallRequest extends jspb.Message {
setIgnoreE164Validation(value: boolean): CallRequest;
getMetadata(): string;
setMetadata(value: string): CallRequest;
getAppRef(): string;
setAppRef(value: string): CallRequest;

serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): CallRequest.AsObject;
Expand All @@ -36,6 +38,7 @@ export namespace CallRequest {
webhook: string,
ignoreE164Validation: boolean,
metadata: string,
appRef: string,
}
}

Expand Down
32 changes: 31 additions & 1 deletion mods/callmanager/src/service/protos/callmanager_pb.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ proto.fonoster.callmanager.v1beta1.CallRequest.toObject = function(includeInstan
to: jspb.Message.getFieldWithDefault(msg, 2, ""),
webhook: jspb.Message.getFieldWithDefault(msg, 3, ""),
ignoreE164Validation: jspb.Message.getBooleanFieldWithDefault(msg, 4, false),
metadata: jspb.Message.getFieldWithDefault(msg, 5, "")
metadata: jspb.Message.getFieldWithDefault(msg, 5, ""),
appRef: jspb.Message.getFieldWithDefault(msg, 6, "")
};

if (includeInstance) {
Expand Down Expand Up @@ -156,6 +157,10 @@ proto.fonoster.callmanager.v1beta1.CallRequest.deserializeBinaryFromReader = fun
var value = /** @type {string} */ (reader.readString());
msg.setMetadata(value);
break;
case 6:
var value = /** @type {string} */ (reader.readString());
msg.setAppRef(value);
break;
default:
reader.skipField();
break;
Expand Down Expand Up @@ -220,6 +225,13 @@ proto.fonoster.callmanager.v1beta1.CallRequest.serializeBinaryToWriter = functio
f
);
}
f = message.getAppRef();
if (f.length > 0) {
writer.writeString(
6,
f
);
}
};


Expand Down Expand Up @@ -313,6 +325,24 @@ proto.fonoster.callmanager.v1beta1.CallRequest.prototype.setMetadata = function(
};


/**
* optional string app_ref = 6;
* @return {string}
*/
proto.fonoster.callmanager.v1beta1.CallRequest.prototype.getAppRef = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 6, ""));
};


/**
* @param {string} value
* @return {!proto.fonoster.callmanager.v1beta1.CallRequest} returns this
*/
proto.fonoster.callmanager.v1beta1.CallRequest.prototype.setAppRef = function(value) {
return jspb.Message.setProto3StringField(this, 6, value);
};





Expand Down
2 changes: 2 additions & 0 deletions mods/dispatcher/src/events_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default function (err: any, ari: any) {

logger.verbose(`ingressInfo: ${JSON.stringify(ingressInfo, null, " ")}`);

const appRef = await getChannelVar(channel, "APP_REF") || ingressInfo.appRef
const webhook =
(await getChannelVar(channel, "WEBHOOK")) || ingressInfo.webhook;
const metadata = await getChannelVarAsJson(channel, "METADATA");
Expand Down Expand Up @@ -95,6 +96,7 @@ export default function (err: any, ari: any) {
callerId: event.channel.caller.name,
callerNumber: event.channel.caller.number,
selfEndpoint: webhook,
appRef,
metadata: metadata || {}
};

Expand Down
1 change: 1 addition & 0 deletions mods/dispatcher/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface CallRequest {
callerNumber: string;
selfEndpoint: string;
metadata?: Record<string, unknown>;
appRef?: string;
}

export interface AttachToEventsRequest {
Expand Down
1 change: 1 addition & 0 deletions mods/numbers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@fonoster/certs": "^0.2.42",
"@fonoster/common": "^0.2.42",
"@fonoster/core": "^0.2.43",
"@fonoster/apps": "^0.2.43",
"@fonoster/errors": "^0.2.42",
"@fonoster/logger": "^0.2.42",
"@grpc/grpc-js": "^1.3.6",
Expand Down
49 changes: 18 additions & 31 deletions mods/numbers/src/client/numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ import {
GetIngressInfoResponse,
INumbersClient
} from "./types";
import {APIClient, ClientOptions} from "@fonoster/common";
import {NumbersClient} from "../service/protos/numbers_grpc_pb";
import NumbersPB, {IngressInfo} from "../service/protos/numbers_pb";
import { APIClient, ClientOptions } from "@fonoster/common";
import { NumbersClient } from "../service/protos/numbers_grpc_pb";
import NumbersPB, { IngressInfo } from "../service/protos/numbers_pb";
import CommonPB from "../service/protos/common_pb";
import {promisifyAll} from "grpc-promise";
import { promisifyAll } from "grpc-promise";

/**
* @classdesc Use Fonoster Numbers, a capability of Fonoster SIP Proxy subsystem,
Expand All @@ -50,7 +50,7 @@ import {promisifyAll} from "grpc-promise";
* providerRef: "516f1577bcf86cd797439012",
* e164Number: "+17853177343",
* ingressInfo: {
* webhook: "https://webhooks.acme.com/hooks"
* webhook: "https://webhooks.acme.com/hooks"
* }
* };
*
Expand All @@ -68,7 +68,7 @@ export default class Numbers extends APIClient implements INumbersClient {
constructor(options?: ClientOptions) {
super(NumbersClient, options);
super.init();
promisifyAll(super.getService(), {metadata: super.getMeta()});
promisifyAll(super.getService(), { metadata: super.getMeta() });
}

/**
Expand Down Expand Up @@ -100,9 +100,8 @@ export default class Numbers extends APIClient implements INumbersClient {
request: CreateNumberRequest
): Promise<CreateNumberResponse> {
const ingressInfo = new NumbersPB.IngressInfo();
ingressInfo.setWebhook(
request.ingressInfo ? request.ingressInfo.webhook : null
);
ingressInfo.setWebhook(request?.ingressInfo?.webhook);
ingressInfo.setAppRef(request?.ingressInfo?.appRef);
const req = new NumbersPB.CreateNumberRequest();
req.setProviderRef(request.providerRef);
req.setE164Number(request.e164Number);
Expand Down Expand Up @@ -137,7 +136,8 @@ export default class Numbers extends APIClient implements INumbersClient {
aorLink: res.getAorLink(),
e164Number: res.getE164Number(),
ingressInfo: {
webhook: res.getIngressInfo() ? res.getIngressInfo().getWebhook : null
webhook: res.getIngressInfo()?.getWebhook(),
appRef: res.getIngressInfo()?.getAppRef()
},
providerRef: res.getProviderRef(),
ref: res.getRef(),
Expand Down Expand Up @@ -170,30 +170,16 @@ export default class Numbers extends APIClient implements INumbersClient {
async updateNumber(
request: UpdateNumberRequest
): Promise<UpdateNumberResponse> {
if (request.aorLink && request.ingressInfo) {
throw new Error(
"'ingressApp' and 'aorLink' are not compatible parameters"
);
} else if (!request.aorLink && !request.ingressInfo) {
throw new Error(
"You must provider either an 'ingressApp' or and 'aorLink'"
);
}
const ingressInfo = new IngressInfo();
ingressInfo.setWebhook(request?.ingressInfo?.webhook);
ingressInfo.setAppRef(request?.ingressInfo?.appRef);

const req = new NumbersPB.UpdateNumberRequest();
req.setRef(request.ref);
req.setAorLink(request.aorLink);

if (request.aorLink) {
req.setAorLink(request.aorLink);
req.setIngressInfo(undefined);
} else {
req.setAorLink(undefined);
const ingressInfo = new IngressInfo();
ingressInfo.setWebhook(
request.ingressInfo ? request.ingressInfo.webhook : null
);
if (ingressInfo.getAppRef() || ingressInfo.getWebhook())
req.setIngressInfo(ingressInfo);
}

const result = await super.getService().updateNumber().sendMessage(req);

Expand Down Expand Up @@ -301,12 +287,13 @@ export default class Numbers extends APIClient implements INumbersClient {

return {
webhook: result.getWebhook(),
accessKeyId: result.getAccessKeyId()
accessKeyId: result.getAccessKeyId(),
appRef: result.getAppRef()
};
}
}

export {NumbersPB, CommonPB, INumbersClient};
export { NumbersPB, CommonPB, INumbersClient };

// WARNING: Workaround for support to commonjs clients
module.exports = Numbers;
Expand Down
Loading

0 comments on commit 6b6218a

Please sign in to comment.