From c567a2678a8749a0dfd8bb8f93562ca675f5a4e0 Mon Sep 17 00:00:00 2001 From: or-else Date: Wed, 19 Sep 2018 17:38:30 +0300 Subject: [PATCH] push notifications seem to work in FF and Chrome --- audio/msg.mp3 | Bin 2189 -> 3983 bytes service-worker.js | 23 +++++ src/webapp.jsx | 229 ++++++++++++++++++++++++---------------------- 3 files changed, 145 insertions(+), 107 deletions(-) create mode 100644 service-worker.js diff --git a/audio/msg.mp3 b/audio/msg.mp3 index 41095dd45ccc157c231fef716cba62f91e595f85..bf53313a1255b83ed129e1c460a6983618ca1775 100644 GIT binary patch literal 3983 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 Dsz)^o literal 2189 zcmbu9e>_wBAICpz`MG6&|F|y0u7pt1Ojm7;%lv3)=yGeAAM>k*wCbCUeu$E}@~g-S zxBN;{sudfm%OqS`-+MKr!mT9T#iKF%p4}ebukT;qf4joS}RK(_(v0I1mj=QaG-h+BiTM#UO! zYdl$FdJXv+ibPQ67%21R|IQ0A%6P>&MUFSM@29`+uWcb88c-7eKv#?afJ6hJw2lKV z3E;l?V4z1-w3~)lGL`BI=PlHcp@hC-Yk!li3Byn+hf0lgFs-Y`yTCZjzxy0!U*KLZcE$q4^r)ay3#p!;B%SJ|_wJN1X%`-ksIw^cWykno+6 z=0_?+x-GgQsEJsvM6NpTtEolQA81` zt}vsdd5Y?Sis%Tgpl76uB;rti>XRbJOB0?n-SEHE(>)?dpRuELWj*Zl@^$0|o@8kv z1)J%HdpO{W?CElt7&&%vGQO~f^Naz1Ii8Gl6I-{1)wGIY@EXQUv?nn_88-7+%f5a7 zMJWkqxWxXLjc`5ii3|zR^a1P}m%XV`aJZJh=!O6RR3Ea^A1nZ;2(~4R4M5~ybEv4( zOnWVxf8-A@z2RT_xMJ#l5+O0%u`++ZN<8PT^R0B|T#_Hj>GBV%LN_v8KnRs|r|ITWE#_DYEl(HtquZvb27`K$uT*vaVFVuE_A$k-{ zAH3SH5K?C=TOdc~z8Am^@M>%>^v$r7_S}64z%F0~18C4#{3>tR?Ped6xluP@vQ}tD zyym|r+FFHFL}x^K_H%rjn8V3YLh<uI#U&{-1EZ!X;dc6%hxticvB^}gQdpdY*_Ef zGb6tl7(y#`@8hq0IrL%UC;o8bjnEJB7yPubwzexmembZ6^Ju$hw4EjGSvL}NM0;2v zq{9BBW->bCL`CSbjX0B#P%?us{k_j51^oSnBE6GK&%O+{3R1^i!}~l!p1!!JsL8Ev z=W6%mLe006F&k|)ri!yJa~tz-Gk1ykZk9SZgG;^RH_9d?jq=qx?N%+xtB!sDJb5Yu zcxRT-wPD64n%*yG@0MB{Sq4nic6Nu@yW*poeKjoXF^R_GO?ukG{~TJjs7j9v@mEo zkYI1bGz4Fl9C?!+ro);Y4Wb0llJi+4G7odJsK|(d;$I7Eq%*`(+wY=+nmm&B*$@$R z3)cejT0a<%{Ak$L8huLq=;EQ*d9tIje6Mj{@LLJae7>&1vraGYIM#)>?gTL>Sbul? zBt<37r$EmS!QnI(m>RiHlJT6L=ARC|Sx2794f^XaT3RZX95 zM1>4g!jrJIwK3G6*cJ*QH`80ZD*gbBjn^G0qUSVL{cO}V@PN0M`!e2i*sNqkE?<`2 zVu-9H;e+p^kL|dAU)?%i`*l(>g*2(_mz=;>_J>hzf;uXuVzVXD+33oik_zEfy6_zS zjodq|#qo?IfoIUYOHRq+o#&6$EMC51TAemD@m?BeXf9497p7E(h|IFz@yPagcb(Tp zf}vWPsLc3{Q}aH*Py}KIlYJndfy2 { - // Registration was successful - this.initDesktopAlerts(fbPush, this.state.desktopAlerts, reg); - }, (err) => { - // registration failed :( - console.log('Failed to register service worker:', err); - }); + try { + this.fbPush = firebase.initializeApp({ + apiKey: FIREBASE_PUBLIC_API_KEY, + messagingSenderId: FIREBASE_SENDER_ID, + }, APP_NAME).messaging(); + this.fbPush.usePublicVapidKey(FIREBASE_VAPID_KEY); + navigator.serviceWorker.register('/service-worker.js').then((reg) => { + this.fbPush.useServiceWorker(reg); + this.initDesktopAlerts(); + if (this.state.desktopAlerts) { + if (!this.state.firebaseToken) { + this.togglePushToken(true, null); + } else { + this.tinode.setDeviceToken(this.state.firebaseToken); + } + } + }).catch((err) => { + // registration failed :( + console.log("Failed to register service worker:", err); + }); + } catch (err) { + this.handleError("Failed to initialize push notifications", "err"); + console.log("Failed to initialize push notifications", err); + this.setState({desktopAlertsEnabled: false}); + } } - tinode.enableLogging(true, true); - tinode.onConnect = this.handleConnected; - tinode.onDisconnect = this.handleDisconnect; + this.tinode.enableLogging(true, true); + this.tinode.onConnect = this.handleConnected; + this.tinode.onDisconnect = this.handleDisconnect; let token, firebasePushToken; if (localStorage.getObject("keep-logged-in")) { token = localStorage.getObject("auth-token"); @@ -4439,8 +4460,8 @@ class TinodeWeb extends React.Component { if (token) { // When reading from storage, date is returned as string. token.expires = new Date(token.expires); - tinode.setAuthToken(token); - tinode.connect().catch((err) => { + this.tinode.setAuthToken(token); + this.tinode.connect().catch((err) => { // Socket error this.handleError(err.message, "err"); }); @@ -4554,10 +4575,10 @@ class TinodeWeb extends React.Component { this.setState({loginDisabled: true, login: login, password: password}); this.handleError("", null); - if (this.state.tinode.isConnected()) { + if (this.tinode.isConnected()) { this.doLogin(login, password, {meth: this.state.credMethod, resp: this.state.credCode}); } else { - this.state.tinode.connect().catch((err) => { + this.tinode.connect().catch((err) => { // Socket error this.setState({loginDisabled: false}); this.handleError(err.message, "err"); @@ -4567,7 +4588,7 @@ class TinodeWeb extends React.Component { // Connection succeeded. handleConnected() { - var params = this.state.tinode.getServerInfo(); + var params = this.tinode.getServerInfo(); this.setState({ serverVersion: params.ver + " " + (params.build ? params.build : "none") + "; " }); @@ -4575,7 +4596,7 @@ class TinodeWeb extends React.Component { } doLogin(login, password, cred) { - if (this.state.tinode.isAuthenticated()) { + if (this.tinode.isAuthenticated()) { // Already logged in. Go to default screen. navigateTo(""); return; @@ -4584,12 +4605,12 @@ class TinodeWeb extends React.Component { cred = Tinode.credential(cred); // Try to login with login/password. If they are not available, try token. If no token, ask for login/password. let promise = null; - let token = this.state.tinode.getAuthToken(); + let token = this.tinode.getAuthToken(); if (login && password) { this.setState({password: null}); - promise = this.state.tinode.loginBasic(login, password, cred); + promise = this.tinode.loginBasic(login, password, cred); } else if (token) { - promise = this.state.tinode.loginToken(token.token, cred); + promise = this.tinode.loginToken(token.token, cred); } if (promise) { @@ -4629,10 +4650,10 @@ class TinodeWeb extends React.Component { // Refresh authentication token. if (localStorage.getObject("keep-logged-in")) { - localStorage.setObject("auth-token", this.state.tinode.getAuthToken()); + localStorage.setObject("auth-token", this.tinode.getAuthToken()); } // Logged in fine, subscribe to 'me' attaching callbacks from the contacts view. - var me = this.state.tinode.getMeTopic(); + var me = this.tinode.getMeTopic(); me.onMetaDesc = this.tnMeMetaDesc; me.onContactUpdate = this.tnMeContactUpdate; me.onSubsUpdated = this.tnMeSubsUpdated; @@ -4640,7 +4661,7 @@ class TinodeWeb extends React.Component { connected: true, credMethod: undefined, credCode: undefined, - myUserId: this.state.tinode.getCurrentUserID() + myUserId: this.tinode.getCurrentUserID() }); // Subscribe, fetch topic desc, the list of subscriptions. Messages are not fetched. me.subscribe( @@ -4761,7 +4782,7 @@ class TinodeWeb extends React.Component { let newState = { chatList: [] }; - this.state.tinode.getMeTopic().contacts((c) => { + this.tinode.getMeTopic().contacts((c) => { newState.chatList.push(c); if (this.state.topicSelected == c.topic) { newState.topicSelectedOnline = c.online; @@ -4775,7 +4796,7 @@ class TinodeWeb extends React.Component { // Sending "received" notifications tnData(data) { - let topic = this.state.tinode.getTopic(data.topic); + let topic = this.tinode.getTopic(data.topic); if (topic.msgStatus(data) > Tinode.MESSAGE_STATUS_SENDING) { clearTimeout(this.receivedTimer); this.receivedTimer = setTimeout(() => { @@ -4787,7 +4808,7 @@ class TinodeWeb extends React.Component { /* Fnd topic: find contacts by tokens */ tnInitFind() { - let fnd = this.state.tinode.getFndTopic(); + let fnd = this.tinode.getFndTopic(); if (fnd.isSubscribed()) { this.setState({contactsSearchQuery: ''}); this.tnFndSubsUpdated(); @@ -4802,7 +4823,7 @@ class TinodeWeb extends React.Component { tnFndSubsUpdated() { let foundContacts = []; // Don't attempt to create P2P topics which already exist. Server will reject the duplicates. - this.state.tinode.getFndTopic().contacts((s) => { + this.tinode.getFndTopic().contacts((s) => { foundContacts.push(s); }); this.setState({ @@ -4815,7 +4836,7 @@ class TinodeWeb extends React.Component { @param query {Array} is an array of contacts to search for */ handleSearchContacts(query) { - var fnd = this.state.tinode.getFndTopic(); + var fnd = this.tinode.getFndTopic(); this.setState({contactsSearchQuery: query == Tinode.DEL_CHAR ? '' : query}); fnd.setMeta({desc: {public: query}}).then((ctrl) => { return fnd.getMeta(fnd.startMetaQuery().withSub().build()); @@ -4866,7 +4887,7 @@ class TinodeWeb extends React.Component { // User is sending a message, either plain text or a drafty object, possibly // with attachments. handleSendMessage(msg, promise, uploader) { - let topic = this.state.tinode.getTopic(this.state.topicSelected); + let topic = this.tinode.getTopic(this.state.topicSelected); msg = topic.createMessage(msg, false); // The uploader is used to show progress. @@ -4898,9 +4919,9 @@ class TinodeWeb extends React.Component { // Actual registration of a new account. handleNewAccountRequest(login_, password_, public_, cred_, tags_) { - this.state.tinode.connect(this.state.serverAddress) + this.tinode.connect(this.state.serverAddress) .then(() => { - return this.state.tinode.createAccountBasic(login_, password_, + return this.tinode.createAccountBasic(login_, password_, {public: public_, tags: tags_, cred: Tinode.credential(cred_)}); }).then((ctrl) => { if (ctrl.code >= 300 && ctrl.text === "validate credentials") { @@ -4915,19 +4936,19 @@ class TinodeWeb extends React.Component { handleUpdateAccountRequest(password, pub, priv) { if (pub || priv) { - this.state.tinode.getMeTopic().setMeta({desc: {public: pub, private: priv}}).catch((err) => { + this.tinode.getMeTopic().setMeta({desc: {public: pub, private: priv}}).catch((err) => { this.handleError(err.message, "err"); }); } if (password) { - this.state.tinode.updateAccountBasic(null, this.state.tinode.getCurrentLogin(), password).catch((err) => { + this.tinode.updateAccountBasic(null, this.tinode.getCurrentLogin(), password).catch((err) => { this.handleError(err.message, "err"); }); } } handleUpdateAccountTagsRequest(tags) { - this.state.tinode.getFndTopic().setMeta({tags: tags}) + this.tinode.getFndTopic().setMeta({tags: tags}) .catch((err) => { this.handleError(err.message, "err"); }); @@ -4940,90 +4961,84 @@ class TinodeWeb extends React.Component { // User updated global parameters. handleGlobalSettings(settings) { - console.log("handleGlobalSettings", settings); - let serverAddress = settings.serverAddress || this.state.serverAddress; let transport = settings.transport || this.state.transport; let messageSounds = (typeof settings.messageSounds == 'boolean') ? settings.messageSounds : this.state.messageSounds; - let desktopAlerts = settings.desktopAlerts; + + if (settings.desktopAlerts != this.state.desktopAlerts) { + this.togglePushToken(settings.desktopAlerts, this.state.firebaseToken); + } this.setState({ serverAddress: serverAddress, transport: transport, messageSounds: messageSounds, - desktopAlerts: desktopAlerts, + desktopAlerts: settings.desktopAlerts, tinode: TinodeWeb.tnSetup(serverAddress, transport), }); - localStorage.setObject({ + localStorage.setObject("settings", { serverAddress: serverAddress, messageSoundsOff: !messageSounds, - transport: this.state.transport, - desktopAlerts: desktopAlerts + transport: transport, + desktopAlerts: settings.desktopAlerts, }); + navigateTo(setUrlSidePanel(window.location.hash, '')); } // Initialize desktop alerts = push notifications. - initDesktopAlerts(fbPush, enabled, reg) { - let oldToken = localStorage.getObject("firebase-token"); + initDesktopAlerts() { + // Google could not be bothered to mention that + // onTokenRefresh is never called. + this.fbPush.onTokenRefresh(() => { + this.requestPushToken(this.state.firebaseToken, true); + }); + + this.fbPush.onMessage((payload) => { + // No need to do anything about it. + // All the magic happends in the service worker. + }); + } + + togglePushToken(enabled, oldToken) { if (enabled) { - fbPush.useServiceWorker(reg); if (!oldToken) { - fbPush.requestPermission().then(() => { - console.log("firebase: permission granted"); - // Token will be received in onTokenRefresh. - }).catch(function(err) { + this.fbPush.requestPermission().then(() => { + this.requestPushToken(oldToken); + }).catch((err) => { this.handleError(err.message, "err"); this.setState({desktopAlerts: false, firebaseToken: null}); console.log("Failed to get permission to notify.", err); }); - } else { - this.setState({"firebaseToken": oldToken}); } } else if (oldToken) { - FirebasePush.deleteToken(oldToken).then(function() { - console.log('Token deleted.'); + this.fbPush.deleteToken(oldToken).then(() => { + let settings = localStorage.getObject("settings"); + settings.desktopAlerts = false; + localStorage.setObject("settings", settings); localStorage.removeItem("firebase-token"); this.setState({desktopAlerts: false, firebaseToken: null}); - }).catch(function(err) { - console.log('Unable to delete token. ', err); + }).catch((err) => { + console.log("Unable to delete token.", err); }); + } else { + this.setState({desktopAlerts: false, firebaseToken: null}); } + } - fbPush.onTokenRefresh(() => { - fbPush.getToken().then((refreshedToken) => { - this.setState({"firebaseToken": refreshedToken}); - if (refreshedToken != oldToken) { - localStorage.save("firebase-token", refreshedToken); - console.log("send new token to server", refreshedToken); - } - }).catch(function(err) { - this.handleError(err.message, "err"); - console.log("Failed to retrieve firebase token", err); - }); - }); - - fbPush.onMessage(function(payload) { - console.log('Message received. ', payload); + requestPushToken(oldToken, sendToServer) { + this.fbPush.getToken().then((refreshedToken) => { + this.setState({"firebaseToken": refreshedToken}); + if (refreshedToken != oldToken) { + localStorage.setObject("firebase-token", refreshedToken); + this.tinode.setDeviceToken(refreshedToken, sendToServer); + } + }).catch((err) => { + this.handleError(err.message, "err"); + console.log("Failed to retrieve firebase token", err); }); } - requestPermission() { - console.log('Requesting permission...'); - // [START request_permission] - FirebasePush.requestPermission().then(function() { - console.log('Notification permission granted.'); - // TODO(developer): Retrieve an Instance ID token for use with FCM. - // [START_EXCLUDE] - // In many cases once an app has been granted notification permission, it - // should update its UI reflecting this. - resetUI(); - // [END_EXCLUDE] - }).catch(function(err) { - console.log('Unable to get permission to notify.', err); - }); - // [END request_permission] - } // User clicked Cancel button in Setting or Sign Up panel. handleSidepanelCancel() { var parsed = parseUrlHash(window.location.hash); @@ -5044,7 +5059,7 @@ class TinodeWeb extends React.Component { // Request to start a new topic. New P2P topic requires peer's name. handleNewTopicRequest(peerName, pub, priv, tags) { - var topicName = peerName || this.state.tinode.newGroupTopicName(); + var topicName = peerName || this.tinode.newGroupTopicName(); if (!peerName) { this.setState( {newGroupTopicParams: {desc: {public: pub, private: {comment: priv}}, tags: tags}}, @@ -5066,7 +5081,7 @@ class TinodeWeb extends React.Component { } handleTopicUpdateRequest(topicName, pub, priv, permissions) { - var topic = this.state.tinode.getTopic(topicName); + var topic = this.tinode.getTopic(topicName); if (topic) { var params = {}; if (pub) { @@ -5085,7 +5100,7 @@ class TinodeWeb extends React.Component { } handleChangePermissions(topicName, mode, uid) { - var topic = this.state.tinode.getTopic(topicName); + var topic = this.tinode.getTopic(topicName); if (topic) { var am = topic.getAccessMode(); if (uid) { @@ -5102,7 +5117,7 @@ class TinodeWeb extends React.Component { } handleTagsUpdated(topicName, tags) { - var topic = this.state.tinode.getTopic(topicName); + var topic = this.tinode.getTopic(topicName); if (topic) { var instance = this; topic.setMeta({tags: tags}).catch((err) => { @@ -5119,7 +5134,7 @@ class TinodeWeb extends React.Component { } handleLeaveUnsubRequest(topicName) { - var topic = this.state.tinode.getTopic(topicName); + var topic = this.tinode.getTopic(topicName); if (!topic) { return; } @@ -5148,7 +5163,7 @@ class TinodeWeb extends React.Component { defaultTopicContextMenu(topicName) { var muted = false, blocked = false, subscribed = false, deleter = false; - var topic = this.state.tinode.getTopic(topicName); + var topic = this.tinode.getTopic(topicName); if (topic) { if (topic.isSubscribed()) { subscribed = true; @@ -5193,7 +5208,7 @@ class TinodeWeb extends React.Component { return; } - var topic = this.state.tinode.getTopic(topicName); + var topic = this.tinode.getTopic(topicName); if (!topic) { return; } @@ -5225,7 +5240,7 @@ class TinodeWeb extends React.Component {
{this.state.contextMenuVisible ?