Dark Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

rosteleset/ApplicationAPI

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

9 Commits

Repository files navigation

ApplicationAPI

Dannyi API ispol'zuiut nashi mobil'nye prilozheniia dlia iOS i Android

Dlia prosmotra API vy mozhete pereiti po dannoi ssylke libo klonirovat' etot repozitorii sebe.

API sgenerirovano avtomaticheski pri pomoshchi apidoc

Primer, kotoryi pomozhet vam nachat' sozdavat' sobstvennyi dvizhok dlia domofonii

Nizhe nabroski togo, kak sdelat' mobil'noe prilozhenie na baze iskhodnikov Linphone, kotoroe budet prinimat' zvonki s vashego Asterisk cherez push-notifications. Dannyi primer illiustriruet nash podkhod, pri pomoshchi kotorogo my delaem integratsiiu mezhdu SIP-domofonami <-> Asterisk <-> Mobil'nymi prilozheniiami

Firebase (google)

  1. registriruemsia v google firebase (https://firebase.google.com/)

  2. potrebuetsia zaregistrirovat' proekt i prilozhenie

potrebuetsia pridumat' imia prilozheniia

i otkliuchit' (a mozhno i ostavit') analitiku

proekt gotov, nado dobavit' prilozhenie

  1. nado skachat' google-services.json

  2. teper' nado sozdat' servisnyi akkaunt

i sokhranit' ego kliuch v fail serviceAccountKey.json

Android

  1. skachivaem linphone

    git clone https://github.com/BelledonneCommunications/linphone-android
  2. fail google-services.json (sm vyshe) nado polozhit' v linphone-android/app (tam uzhe est' ot razrabotchikov linphone, prosto perezapisat')

  3. v faile google-services.json v sektsii "client" dubliruem soderzhimoe i vo vtorom ekzempliare k package_name dobavliaem .debug

  1. v linphone-android/app/build.gradle meniaem "org.linphone" na imia kotoroe pridumali pri sozdanii prilozheniia v firebase

  2. generiruem kliuchi dlia podpisi prilozheniia

    keytool -genkey -keystore linphone-android/app/bc-android.keystore -storepass lp1234 -alias lp1234 -keypass lp1234 -dname "CN=Mikhail Ivanov O=StartAndroid C=RU" -validity 10000 -keyalg RSA -keysize 2048
  3. podstavliaem lp1234 v linphone-android/keystore.properties

  1. kachaem i stavim Android Studio IDE https://developer.android.com/studio#downloads (esli eshche net)

  2. otkryvaem proekt linphone-android

  3. kompiliruem, zapuskaem na ustroistve ili emuliatore

  1. v nastroikakh prilozheniia vkliuchaem pushi, OTKLIuChAEM avtozapusk i fonovuiu sluzhbu

s androidom vse (reliz, rebrending i t.p. - uzhe sami :))

iOS

vsio analogichno Android:

  1. skachivaem https://github.com/BelledonneCommunications/linphone-iphone
  2. zameniaem nazvanie prilozheniia, versiiu i dannye razrabotchikov
  3. Neobkhodimye kliuchi, provizhining profaily i sertifikaty dlia podpisi prilozheniia XCode dolzhen sozdat' avtomaticheski, libo ikh mozhno dobavit' ikh vruchnuiu iz paneli razrabotchika na developer.apple.com
  4. zameniaem GoogleService-Info.plist na svoi, vziatyi iz nashego akkaunta Firebase
  5. sobiraem proekt, arkhiviruem, otpravliaem v AppStore na proverku, publikuem.

Asterisk

  1. dialplan na lua (nado podozhdat' poka ne poiavitsia extension)
local timeout = os.time() + 60

local pjsip_extension = ''

push(extension, channel.CALLERID("name"):get(), channel.CALLERID("num"):get())

log_debug("starting loop for: "..extension)
while os.time() < timeout do
pjsip_extension = channel.PJSIP_DIAL_CONTACTS(extension):get()
if pjsip_extension ~= "" then
log_debug("has registration: "..extension.." ["..pjsip_extension.."]")
app.Dial(pjsip_extension, 60, 'mtT')
else
app.Wait(0.5)
end
end
  1. nado kak-to poluchat' tokeny pushei i otpravliat' ikh, dlia etogo potrebuetsia nebol'shoi skript na node.js (prilagaetsia)

  2. riadom s lp.js nado polozhit' serviceAccountKey.json

  3. skriptu potrebuetsia AMI

  4. i nekotoroe kolichestvo zavisimostei npm i firebase-admin asterisk-manager express redis

  5. kliuchi budem khranit' v redis apt-get install redis

  6. u extension zhelatel'no postavit' transport tcp, togda pri otkliuchenii telefona asterisk srazu poimet chto kontakt otvalilsia

Iskhodnyi kod

  1. lp.js (registratsiia tokenov, otpravka pushei)
{ pushOk(token, r); }).catch(e => { pushFail(token, e); }); } else { admin.messaging().sendToDevice(token, message).then(r => { pushOk(token, r); }).catch(e => { pushFail(token, e); }); } } else { let http2_server = (parseInt(type) === 2)?'https://api.sandbox.push.apple.com':'https://api.push.ap ple.com'; let curl = new Curl(); curl.setOpt(Curl.option.HTTP_VERSION, 3); curl.setOpt(Curl.option.URL, `${http2_server}/3/device/${token}`); curl.setOpt(Curl.option.PORT, 443); curl.setOpt(Curl.option.HTTPHEADER, [ `apns-topic: ${app_bundle_id}.voip`, `apns-push-type: voip`, `User-Agent: ${app_bundle_id}`, ]); curl.setOpt(Curl.option.POST, true); curl.setOpt(Curl.option.POSTFIELDS, JSON.stringify({ data: data, })); curl.setOpt(Curl.option.TIMEOUT, 30); curl.setOpt(Curl.option.SSL_VERIFYPEER, false); curl.setOpt(Curl.option.SSLCERT, cert); curl.setOpt(Curl.option.HEADER, true); curl.setOpt(Curl.option.VERBOSE, false); curl.on('end', code => { if (parseInt(code) === 200) { pushOk(token, { successCount: 1 }); } else { pushFail(token, { errorCode: code }); } curl.close(); }); curl.on('error', () => { curl.close(); }); curl.perform(); } } var contacts = {}; ami.on('contactstatus', e => { if (e.aor) { let uri = e.uri.split(';'); let token; let type; for (let i = 0; i < uri.length; i++) { let p = uri[i].split('='); switch (p[0]) { case 'pn-tok': token = p[1]; break; case 'pn-type': type = p[1]; break; } } if (token && type == 'firebase') { contacts[e.aor] = token; console.log(e.aor, token); redis.set('contacts', JSON.stringify(contacts)); } } }); app.get('/wakeup', function (req, res) { console.log(req.query); if (req.query.ext && contacts[req.query.ext]) { realPush({ // empty message }, { type: 'voip', realm: req.query.realm?req.query.realm:'Unknown', user: req.query.from?req.query.from:'Unknown', }, { priority: 'high', mutableContent: true, }, contacts[req.query.ext], 0 ); } res.status(204).send(); }); redis.get('contacts', (e, v) => { if (!e && v) { contacts = JSON.parse(v); console.log(contacts); } });">#!/usr/bin/nodejs

const database = 'https://presentation-voxlink.firebaseio.com';

const fs = require('fs');
const path = require('path');
const admin = require('firebase-admin');
const ami = new require('asterisk-manager')('5038', '127.0.0.1', 'asterisk', '881d6256664648e0ebe1ed0e9b1340f2', true);
const app = require('express')();
const redis = require("redis").createClient();

admin.initializeApp({
credential: admin.credential.cert(require(path.join(__dirname, 'serviceAccountKey.json'))),
databaseURL: database,
});

ami.keepConnected();

app.use(require('body-parser').urlencoded({ extended: true }));
app.listen(8082, '127.0.0.1');

function pushOk(token, result) {
if (result && result.successCount && parseInt(result.successCount)) {
console.log((new Date()).toLocaleString() + " ok: " + token);
} else {
pushFail(token, result);
}
}

function pushFail(token, error) {
console.log((new Date()).toLocaleString() + " err: " + token);

let broken = false;
if (error && error.results && error.results.length && error.results[0] && error.results[0].error && error.results[0].error.code) {
if (error.results[0].error.code == 'messaging/registration-token-not-registered') {
for (let i in contacts) {
if (contacts[i] == token) {
delete contacts[i];
redis.set('contacts', JSON.stringify(contacts));
}
}
broken = true;
}
}

if (!broken) {
fs.appendFileSync('/tmp/pushFail.log', (new Date()).toLocaleString() + " err: " + token + "\n" + JSON.stringify(error) + "\n\n");
}
}

function realPush(msg, data, options, token, type) {

console.log(token, type, msg, data, options);

if (parseInt(type) === 0) {

let message = {
notification: msg,
data: data,
};

if (options) {
admin.messaging().sendToDevice(token, message, options).then(r => {
pushOk(token, r);
}).catch(e => {
pushFail(token, e);
});
} else {
admin.messaging().sendToDevice(token, message).then(r => {
pushOk(token, r);
}).catch(e => {
pushFail(token, e);
});
}
} else {

let http2_server = (parseInt(type) === 2)?'https://api.sandbox.push.apple.com':'https://api.push.apple.com';

let curl = new Curl();

curl.setOpt(Curl.option.HTTP_VERSION, 3);
curl.setOpt(Curl.option.URL, `${http2_server}/3/device/${token}`);
curl.setOpt(Curl.option.PORT, 443);
curl.setOpt(Curl.option.HTTPHEADER, [
`apns-topic: ${app_bundle_id}.voip`,
`apns-push-type: voip`,
`User-Agent: ${app_bundle_id}`,
]);
curl.setOpt(Curl.option.POST, true);
curl.setOpt(Curl.option.POSTFIELDS, JSON.stringify({
data: data,
}));
curl.setOpt(Curl.option.TIMEOUT, 30);
curl.setOpt(Curl.option.SSL_VERIFYPEER, false);
curl.setOpt(Curl.option.SSLCERT, cert);
curl.setOpt(Curl.option.HEADER, true);
curl.setOpt(Curl.option.VERBOSE, false);

curl.on('end', code => {
if (parseInt(code) === 200) {
pushOk(token, { successCount: 1 });
} else {
pushFail(token, { errorCode: code });
}
curl.close();
});

curl.on('error', () => {
curl.close();
});

curl.perform();
}
}

var contacts = {};

ami.on('contactstatus', e => {
if (e.aor) {
let uri = e.uri.split(';');
let token;
let type;
for (let i = 0; i < uri.length; i++) {
let p = uri[i].split('=');
switch (p[0]) {
case 'pn-tok':
token = p[1];
break;
case 'pn-type':
type = p[1];
break;
}
}
if (token && type == 'firebase') {
contacts[e.aor] = token;
console.log(e.aor, token);
redis.set('contacts', JSON.stringify(contacts));
}
}
});

app.get('/wakeup', function (req, res) {
console.log(req.query);
if (req.query.ext && contacts[req.query.ext]) {
realPush({
// empty message
}, {
type: 'voip',
realm: req.query.realm?req.query.realm:'Unknown',
user: req.query.from?req.query.from:'Unknown',
}, {
priority: 'high',
mutableContent: true,
},
contacts[req.query.ext],
0
);
}
res.status(204).send();
});

redis.get('contacts', (e, v) => {
if (!e && v) {
contacts = JSON.parse(v);
console.log(contacts);
}
});
  1. extension.lua (dialplan) apt-get install lua-socket
http = require 'socket.http'

function char_to_pchar(c)
return string.format("%%%02X", c:byte(1, 1))
end

function encodeURI(str)
return (str:gsub("[^%;%,%/%?%:%@%&%=%+%$%w%-%_%.%!%~%*%'%(%)%#]", char_to_pchar))
end

function encodeURIComponent(str)
return (str:gsub("[^%w%-_%.%!%~%*%'%(%)]", char_to_pchar))
end

function push(ext, realm, from)
http.request("http://127.0.0.1:8082/wakeup?ext="..encodeURIComponent(tostring(ext)).."&realm="..encodeURIComponent(tostring(realm)).."&from="..encodeURIComponent(tostring(from)))
end

extensions = {
[ "default" ] = {

[ "_." ] = function ()
app.Answer()
app.MusicOnHold()
end,

[ "_XXXX" ] = function (context, extension)
local timeout = os.time() + 60

local pjsip_extension = ''

push(extension, channel.CALLERID("name"):get(), channel.CALLERID("num"):get())

while os.time() < timeout do
pjsip_extension = channel.PJSIP_DIAL_CONTACTS(extension):get()
if pjsip_extension ~= "" then
app.Dial(pjsip_extension, 60, 'mtT')
else
app.Wait(0.5)
end
end
end,

},
}
  1. pjsip.conf
[transport-tcp]
type = transport
protocol = tcp
bind = 0.0.0.0

[static-aor-template](!)
type = aor
max_contacts = 1
remove_existing = yes

[mobile-endpoint-template](!)
type = endpoint
context = default
disallow = all
allow = opus
rtp_symmetric = yes
force_rport = yes
rewrite_contact = yes
timers = no
direct_media = no
inband_progress = no
allow_subscribe = yes
dtmf_mode = rfc4733
ice_support = yes
transport = transport-tcp

[1001](static-aor-template)

[1001]
type = auth
username = 1001
password = Giethoh4

[1001](mobile-endpoint-template)
auth = 1001
outbound_auth = 1001
aors = 1001
callerid = "M. Ivanov" <1001>
  1. manager.conf
[general]
enabled = yes
port = 5038
bindaddr = 127.0.0.1

[asterisk]
secret = 881d6256664648e0ebe1ed0e9b1340f2
read = all
write = all

About

API, ispol'zuemoe mobil'nymi prilozheniiami dlia iOS i Android / API for customers' mobile applications

Resources

Readme

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors