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)
-
registriruemsia v google firebase (https://firebase.google.com/)
-
potrebuetsia zaregistrirovat' proekt i prilozhenie
potrebuetsia pridumat' imia prilozheniia
i otkliuchit' (a mozhno i ostavit') analitiku
proekt gotov, nado dobavit' prilozhenie
-
nado skachat' google-services.json
-
teper' nado sozdat' servisnyi akkaunt
i sokhranit' ego kliuch v fail serviceAccountKey.json
Android
-
skachivaem linphone
git clone https://github.com/BelledonneCommunications/linphone-android -
fail google-services.json (sm vyshe) nado polozhit' v linphone-android/app (tam uzhe est' ot razrabotchikov linphone, prosto perezapisat')
-
v faile google-services.json v sektsii "client" dubliruem soderzhimoe i vo vtorom ekzempliare k package_name dobavliaem .debug
-
v linphone-android/app/build.gradle meniaem "org.linphone" na imia kotoroe pridumali pri sozdanii prilozheniia v firebase
-
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 -
podstavliaem lp1234 v linphone-android/keystore.properties
-
kachaem i stavim Android Studio IDE
https://developer.android.com/studio#downloads(esli eshche net) -
otkryvaem proekt linphone-android
-
kompiliruem, zapuskaem na ustroistve ili emuliatore
- v nastroikakh prilozheniia vkliuchaem pushi, OTKLIuChAEM avtozapusk i fonovuiu sluzhbu
s androidom vse (reliz, rebrending i t.p. - uzhe sami :))
iOS
vsio analogichno Android:
- skachivaem https://github.com/BelledonneCommunications/linphone-iphone
- zameniaem nazvanie prilozheniia, versiiu i dannye razrabotchikov
- 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
- zameniaem GoogleService-Info.plist na svoi, vziatyi iz nashego akkaunta Firebase
- sobiraem proekt, arkhiviruem, otpravliaem v AppStore na proverku, publikuem.
Asterisk
- dialplan na lua (nado podozhdat' poka ne poiavitsia extension)
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
-
nado kak-to poluchat' tokeny pushei i otpravliat' ikh, dlia etogo potrebuetsia nebol'shoi skript na node.js (prilagaetsia)
-
riadom s lp.js nado polozhit' serviceAccountKey.json
-
skriptu potrebuetsia AMI
-
i nekotoroe kolichestvo zavisimostei
npm i firebase-admin asterisk-manager express redis -
kliuchi budem khranit' v redis
apt-get install redis -
u extension zhelatel'no postavit' transport tcp, togda pri otkliuchenii telefona asterisk srazu poimet chto kontakt otvalilsia
Iskhodnyi kod
- lp.js (registratsiia tokenov, otpravka pushei)
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);
}
});
- extension.lua (dialplan)
apt-get install lua-socket
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,
},
}
- 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>
- manager.conf
[general]
enabled = yes
port = 5038
bindaddr = 127.0.0.1
[asterisk]
secret = 881d6256664648e0ebe1ed0e9b1340f2
read = all
write = all