From 54c0aa0f71b4253d20da5eeffeda3de21a5b04da Mon Sep 17 00:00:00 2001 From: raxelsen Date: Fri, 1 Dec 2017 12:31:26 +0100 Subject: [PATCH] TT#20530 Implement playable voicemail What has been done: - TT#27761, Conversations: Create custom modules for conversation items - TT#27763, Conversations: Implement "play voicemail" button with download functionality - TT#27764, Conversations: Implement hashed id based on conversation item type, call_type and id Change-Id: If772b3ed7e7db1dd7b93e48aacf1ce0d93acf5a8 --- README.md | 4 + npm-shrinkwrap.json | 48 +++++--- package.json | 2 + src/api/conversations.js | 44 ++++++- src/components/CscConversation.vue | 151 +++++++++++++++++++++++++ src/components/CscConversations.vue | 40 +++++++ src/components/pages/Conversations.vue | 134 +--------------------- src/locales/en.json | 3 +- src/store/conversations.js | 26 ++--- t/Dockerfile | 2 +- t/api/conversations.js | 89 +++++++++------ t/store/conversations.js | 20 +--- 12 files changed, 348 insertions(+), 215 deletions(-) create mode 100644 src/components/CscConversation.vue create mode 100644 src/components/CscConversations.vue diff --git a/README.md b/README.md index 8f2e4032..63e3c9a1 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,10 @@ Now you can log in to csc with one of the normal subscriber you just created. UR ### How to add new npm package +1. Remove the package if you've already installed it + +`npm remove <--save-dev || --save>` + 1. Ensure that you have a clean node_modules folder ``` diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index db3d768c..8965f90b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -169,9 +169,9 @@ "dev": true }, "asn1.js": { - "version": "4.9.1", + "version": "4.9.2", "from": "asn1.js@>=4.0.0 <5.0.0", - "resolved": "https://npm-registry.sipwise.com/asn1.js/-/asn1.js-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", "dev": true }, "assert": { @@ -1185,9 +1185,9 @@ "dev": true }, "crypto-browserify": { - "version": "3.11.1", - "from": "crypto-browserify@>=3.11.0 <4.0.0", - "resolved": "https://npm-registry.sipwise.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz", + "version": "3.12.0", + "from": "crypto-browserify@latest", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", "dev": true }, "css": { @@ -2016,6 +2016,12 @@ "resolved": "https://npm-registry.sipwise.com/file-loader/-/file-loader-0.11.2.tgz", "dev": true }, + "file-saver": { + "version": "1.3.3", + "from": "file-saver@latest", + "resolved": "https://npm-registry.sipwise.com/file-saver/-/file-saver-1.3.3.tgz", + "dev": true + }, "filename-regex": { "version": "2.0.1", "from": "filename-regex@>=2.0.0 <3.0.0", @@ -2107,9 +2113,9 @@ "dev": true }, "fsevents": { - "version": "1.1.2", + "version": "1.1.3", "from": "fsevents@>=1.0.0 <2.0.0", - "resolved": "https://npm-registry.sipwise.com/fsevents/-/fsevents-1.1.2.tgz", + "resolved": "https://npm-registry.sipwise.com/fsevents/-/fsevents-1.1.3.tgz", "dev": true, "optional": true, "dependencies": { @@ -2267,8 +2273,7 @@ "version": "2.0.5", "from": "cryptiles@2.0.5", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "dev": true, - "optional": true + "dev": true }, "dashdash": { "version": "1.14.1", @@ -2313,6 +2318,13 @@ "dev": true, "optional": true }, + "detect-libc": { + "version": "1.0.2", + "from": "detect-libc@^1.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.2.tgz", + "dev": true, + "optional": true + }, "ecc-jsbn": { "version": "0.1.1", "from": "ecc-jsbn@0.1.1", @@ -2426,8 +2438,7 @@ "version": "3.1.3", "from": "hawk@3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "dev": true, - "optional": true + "dev": true }, "hoek": { "version": "2.16.3", @@ -2583,9 +2594,9 @@ "optional": true }, "node-pre-gyp": { - "version": "0.6.36", - "from": "node-pre-gyp@^0.6.36", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz", + "version": "0.6.39", + "from": "node-pre-gyp@^0.6.39", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", "dev": true, "optional": true }, @@ -2749,8 +2760,7 @@ "version": "1.0.9", "from": "sntp@1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "dev": true, - "optional": true + "dev": true }, "sshpk": { "version": "1.13.0", @@ -5004,6 +5014,12 @@ "resolved": "https://npm-registry.sipwise.com/randombytes/-/randombytes-2.0.5.tgz", "dev": true }, + "randomfill": { + "version": "1.0.3", + "from": "randomfill@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.3.tgz", + "dev": true + }, "range-parser": { "version": "1.2.0", "from": "range-parser@>=1.2.0 <1.3.0", diff --git a/package.json b/package.json index d9949df4..fb6a45dc 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "chai": "^4.1.2", "colors": "^1.1.2", "connect-history-api-fallback": "^1.1.0", + "crypto-browserify": "3.12.0", "css-loader": "^0.28.7", "es6-promise": "^4.1.1", "eslint": "^4.8.0", @@ -48,6 +49,7 @@ "express": "^4.16.1", "extract-text-webpack-plugin": "^3.0.0", "file-loader": "^0.11.1", + "file-saver": "1.3.3", "friendly-errors-webpack-plugin": "^1.1.3", "glob": "^7.1.2", "google-libphonenumber": "3.0.7", diff --git a/src/api/conversations.js b/src/api/conversations.js index b7754eeb..d11d40eb 100644 --- a/src/api/conversations.js +++ b/src/api/conversations.js @@ -1,6 +1,8 @@ -import Vue from 'vue'; -import _ from 'lodash'; +import { saveAs } from 'file-saver' +import Vue from 'vue' +import _ from 'lodash' +import crypto from 'crypto-browserify' import { getJsonBody } from './utils' export function getConversations(id, page, rows) { @@ -11,7 +13,22 @@ export function getConversations(id, page, rows) { .then(result => { let jsonBody = getJsonBody(result.body); if (_.has(jsonBody, "_embedded.ngcp:conversations")) { - resolve(jsonBody._embedded['ngcp:conversations']); + let list = []; + _.forEach(jsonBody._embedded['ngcp:conversations'], function(item) { + let inputString = `${item.type}${item.call_type}${item.id}`; + let id = crypto.createHash('sha256').update(inputString).digest('base64'); + item._id = id; + if (item._links['ngcp:voicemailrecordings']) { + item.voicemail = item._links['ngcp:voicemailrecordings'].href; + }; + delete item._links; + if (item.type == 'call') { + item.type = item.call_type != 'call' ? 'callforward' + : item.type; + }; + list.push(item); + }); + resolve(list); } else { reject(new Error('No items returned for this page.')) }; @@ -20,3 +37,24 @@ export function getConversations(id, page, rows) { }); }); } + + +export function downloadVoiceMail(id) { + return new Promise((resolve, reject)=>{ + Vue.http.get('/api/voicemailrecordings/' + id, { responseType: 'blob' }) + .then(res => { + return res.blob(); + }).then(voicemail => { + saveAs(voicemail, "voicemail-" + id + '.wav'); + resolve(); + }).catch((err)=>{ + reject(err); + }); + }); +} + + + + + + diff --git a/src/components/CscConversation.vue b/src/components/CscConversation.vue new file mode 100644 index 00000000..3b1b83a5 --- /dev/null +++ b/src/components/CscConversation.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/src/components/CscConversations.vue b/src/components/CscConversations.vue new file mode 100644 index 00000000..abf63012 --- /dev/null +++ b/src/components/CscConversations.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/components/pages/Conversations.vue b/src/components/pages/Conversations.vue index 0efd3f49..d762127e 100644 --- a/src/components/pages/Conversations.vue +++ b/src/components/pages/Conversations.vue @@ -1,59 +1,12 @@ diff --git a/src/locales/en.json b/src/locales/en.json index 7415a983..6b986eaa 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -92,7 +92,8 @@ "buttons": { "call": "CALL", "audioCall": "Audio Call", - "videoCall": "Video Call" + "videoCall": "Video Call", + "play": "Play" }, "card": { "date": "Date", diff --git a/src/store/conversations.js b/src/store/conversations.js index d895f967..64048076 100644 --- a/src/store/conversations.js +++ b/src/store/conversations.js @@ -1,8 +1,8 @@ -'use strict'; +'use strict' -import _ from 'lodash'; -import { getConversations } from '../api/conversations'; +import _ from 'lodash' +import { getConversations, downloadVoiceMail } from '../api/conversations' export default { namespaced: true, @@ -14,16 +14,7 @@ export default { }, mutations: { loadConversations(state, options) { - let list = []; - _.forEach(options, function(item) { - delete item._links; - if (item.type == 'call') { - item.type = item.call_type != 'call' ? 'call forward' - : item.type; - }; - list.push(item); - }) - state.conversations = state.conversations.concat(list); + state.conversations = state.conversations.concat(options); state.page++; } }, @@ -39,6 +30,15 @@ export default { reject(err); }); }); + }, + downloadVoiceMail(context, id) { + return new Promise((resolve, reject)=>{ + downloadVoiceMail(id).then(()=>{ + resolve(); + }).catch((err)=>{ + reject(err); + }); + }); } } }; diff --git a/t/Dockerfile b/t/Dockerfile index 7757515d..cdecd083 100644 --- a/t/Dockerfile +++ b/t/Dockerfile @@ -5,7 +5,7 @@ FROM docker.mgm.sipwise.com/sipwise-stretch:latest # is updated with the current date. It will force refresh of all # of the base images and things like `apt-get update` won't be using # old cached versions when the Dockerfile is built. -ENV REFRESHED_AT 2017-10-06 +ENV REFRESHED_AT 2017-12-15 ENV DEBIAN_FRONTEND noninteractive ENV DISPLAY=:0 diff --git a/t/api/conversations.js b/t/api/conversations.js index 70c64643..a1ecc474 100644 --- a/t/api/conversations.js +++ b/t/api/conversations.js @@ -3,6 +3,7 @@ import Vue from 'vue'; import VueResource from 'vue-resource'; +import crypto from 'crypto-browserify' import { getConversations } from '../../src/api/conversations'; import { assert } from 'chai'; @@ -13,48 +14,72 @@ describe('Conversations', function(){ const subscriberId = 123; it('should get all data regarding conversations', function(done){ + let inputString = 'voicemailundefined1'; + let hashedId = crypto.createHash('sha256').update(inputString).digest('base64'); let innerData = [{ "_links" : { - "collection" : { - "href" : "/api/conversations/" - }, - "curies" : { - "href" : "http://purl.org/sipwise/ngcp-api/#rel-{rel}", - "name" : "ngcp", - "templated" : true - }, - "ngcp:calls" : { - "href" : "/api/calls/5" - }, - "ngcp:conversations" : { - "href" : "/api/conversations/5?type=call" - }, - "profile" : { - "href" : "http://purl.org/sipwise/ngcp-api/" - }, - "self" : { - "href" : "/api/conversations/5?type=call" - } + "collection": { + "href": "/api/conversations/" + }, + "curies": { + "href": "http://purl.org/sipwise/ngcp-api/#rel-{rel}", + "name": "ngcp", + "templated": true + }, + "ngcp:conversations": { + "href": "/api/conversations/1?type=voicemail" + }, + "ngcp:voicemailrecordings": { + "href": "/api/voicemailrecordings/1" + }, + "ngcp:voicemails": { + "href": "/api/voicemails/1" + }, + "profile": { + "href": "http://purl.org/sipwise/ngcp-api/" + }, + "self": { + "href": "/api/conversations/1?type=voicemail" + } }, - "call_id" : "cT1miqD5Nw", - "call_type" : "cfu", - "callee" : "vmu43993006@voicebox.local", - "caller" : "43993006", - "direction" : "out", - "duration" : "0:00:19.672", - "id" : 5, - "rating_status" : "ok", - "start_time" : "2017-11-10 08:51:10.452", - "status" : "ok", - "type" : "call" + "call_id": "kp55kEGtNp", + "callee": "43993006", + "caller": "43993006", + "context": "voicemailcaller_unavail", + "direction": "in", + "duration": "15", + "filename": "voicemail-0.wav", + "folder": "Old", + "id": 1, + "start_time": "2017-12-07 16:22:04", + "type": "voicemail", + "voicemail_subscriber_id": 235 }]; + let data = { "_embedded": { "ngcp:conversations": innerData } }; + let innerDataWithoutLinks = [{ + "call_id": "kp55kEGtNp", + "callee": "43993006", + "caller": "43993006", + "context": "voicemailcaller_unavail", + "direction": "in", + "duration": "15", + "filename": "voicemail-0.wav", + "folder": "Old", + "id": 1, + "start_time": "2017-12-07 16:22:04", + "type": "voicemail", + "voicemail_subscriber_id": 235, + "_id": hashedId, + "voicemail": "/api/voicemailrecordings/1" + }]; + Vue.http.interceptors = []; Vue.http.interceptors.unshift((request, next)=>{ next(request.respondWith(JSON.stringify(data), { @@ -62,7 +87,7 @@ describe('Conversations', function(){ })); }); getConversations(subscriberId).then((result)=>{ - assert.deepEqual(result, innerData); + assert.deepEqual(result, innerDataWithoutLinks); done(); }).catch((err)=>{ done(err); diff --git a/t/store/conversations.js b/t/store/conversations.js index ac738626..e2f28adb 100644 --- a/t/store/conversations.js +++ b/t/store/conversations.js @@ -12,33 +12,17 @@ describe('Conversations', function(){ ] }; let data = [ - { - "_links": { - }, - "call_type": "cfu", + { "caller": "43993010", "type": "call" }, { - "_links": { - }, "caller": "43993011", "type": "fax" } ]; ConversationsModule.mutations.loadConversations(state, data); - assert.deepEqual(state.conversations, [ - { - "call_type": "cfu", - "caller": "43993010", - "type": "call forward" - }, - { - "caller": "43993011", - "type": "fax" - } - ]); - + assert.deepEqual(state.conversations, data); }); });