Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {

const normalReconnectReasons = ['idle', 'done (forced)'];
const DEFAULT_MOBIUS_WEBSOCKET_SESSION = 'mobius-websocket-session';
const TOKEN_REFRESH_INTERVAL_MS = 1 * 60 * 60 * 1000; // 1 hour
const TEST_MOBIUS_WEBSOCKET_URL = 'wss://mobius.aload-calling1.ciscospark.com/v1/calling/web';

function normalizeMobiusAuthToken(token) {
if (typeof token !== 'string') {
Expand Down Expand Up @@ -75,6 +77,9 @@ const MobiusSocket = WebexPlugin.extend({
},

initialize() {
this._tokenRefreshTimer = undefined;
this._tokenRefreshInFlight = undefined;

/*
When one of these legacy feature gets updated, this event would be triggered
* group-message-notifications
Expand Down Expand Up @@ -437,6 +442,12 @@ const MobiusSocket = WebexPlugin.extend({

// Update overall connected status
this.connected = this.hasConnectedSockets();
const hasConnectedSocket = Array.from(this.sockets.values()).some(
(socket) => socket?.connected
);
if (!hasConnectedSocket) {
this._stopTokenRefreshTimer();
}
});
},

Expand All @@ -456,6 +467,7 @@ const MobiusSocket = WebexPlugin.extend({
this.connected = false;
this.sockets.clear();
this.backoffCalls.clear();
this._stopTokenRefreshTimer();
// Clear connection promises to prevent stale promises
if (this._connectPromises) {
this._connectPromises.clear();
Expand Down Expand Up @@ -506,7 +518,11 @@ const MobiusSocket = WebexPlugin.extend({

_prepareUrl(webSocketUrl) {
if (!webSocketUrl) {
webSocketUrl = this.webex.internal.device.webSocketUrl;
// Original catalog-based URL resolution (kept for later restore):
// webSocketUrl = this.webex.internal.device.webSocketUrl;

// Temporary testing override until catalog URI is available.
webSocketUrl = TEST_MOBIUS_WEBSOCKET_URL;
}

// TODO: Validate the host against the service catalog
Expand Down Expand Up @@ -727,6 +743,7 @@ const MobiusSocket = WebexPlugin.extend({
this.connecting = this.hasConnectingSockets();
this.connected = this.hasConnectedSockets();
this.hasEverConnected = true;
this._startTokenRefreshTimer();
this._emit(sid, 'online');
}

Expand Down Expand Up @@ -865,6 +882,77 @@ const MobiusSocket = WebexPlugin.extend({
return handlers;
},

_startTokenRefreshTimer() {
const hasConnectedSocket = Array.from(this.sockets.values()).some(
(socket) => socket?.connected
);
if (this._tokenRefreshTimer || !hasConnectedSocket) {
return;
}

this._tokenRefreshTimer = setInterval(() => {
this._refreshAndReauthSockets().catch((error) => {
this.logger.error(`${this.namespace}: periodic token refresh failed`, error);
});
}, TOKEN_REFRESH_INTERVAL_MS);
},

_stopTokenRefreshTimer() {
if (!this._tokenRefreshTimer) {
return;
}

clearInterval(this._tokenRefreshTimer);
this._tokenRefreshTimer = undefined;
},

_refreshAndReauthSockets() {
if (this._tokenRefreshInFlight) {
return this._tokenRefreshInFlight;
}

const hasConnectedSocket = Array.from(this.sockets.values()).some(
(socket) => socket?.connected
);
if (!hasConnectedSocket) {
this._stopTokenRefreshTimer();

return Promise.resolve();
}

const tokenPromise = this.webex.credentials.canRefresh
? this.webex.credentials
.refresh({force: true})
.then(() => this.webex.credentials.getUserToken())
: this.webex.credentials.getUserToken();

this._tokenRefreshInFlight = tokenPromise
.then((token) => {
if (!token) {
throw new Error('Mobius token refresh did not return a token');
}
const refreshedToken = normalizeMobiusAuthToken(token.toString());
const authPayloadPromises = [];

for (const socket of this.sockets.values()) {
if (socket?.connected) {
authPayloadPromises.push(socket.refresh(refreshedToken));
}
}

return Promise.all(authPayloadPromises);
})
.catch((error) => {
this.logger.error(`${this.namespace}: failed to refresh/re-auth Mobius sockets`, error);
throw error;
})
.finally(() => {
this._tokenRefreshInFlight = undefined;
});

return this._tokenRefreshInFlight;
},

_onclose(sessionId, event, sourceSocket) {
// I don't see any way to avoid the complexity or statement count in here.
/* eslint complexity: [0] */
Expand All @@ -891,6 +979,12 @@ const MobiusSocket = WebexPlugin.extend({
// Update overall connected status
this.connecting = this.hasConnectingSockets();
this.connected = this.hasConnectedSockets();
const hasConnectedSocketAfterClose = Array.from(this.sockets.values()).some(
(socket) => socket?.connected
);
if (!hasConnectedSocketAfterClose) {
this._stopTokenRefreshTimer();
}
} else {
// Old socket closed; do not flip connection state
this.logger.info(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ export default class Socket extends EventEmitter {

socket.onopen = () => {
this.logger.info(`socket,${this._domain}: connected`);
this._authorize()
this._authorize(this.token)
.then(() => {
this.logger.info(`socket,${this._domain}: authorized`);
socket.onclose = this.onclose;
Expand Down Expand Up @@ -459,19 +459,29 @@ export default class Socket extends EventEmitter {
});
}

refresh(token) {
if (!token) {
return Promise.reject(new Error('`token` is required for Socket#refresh()'));
}

const refreshedToken = token && typeof token.toString === 'function' ? token.toString() : token;

return this._authorize(refreshedToken);
}

/**
* Sends an auth message up the socket
* @private
* Sends an auth message up the socket with a refreshed token.
* @param {string} token
* @returns {Promise}
*/
_authorize() {
_authorize(token) {
this.logger.info(`socket,${this._domain}: authorizing`);

return this.sendRequest(
{
type: MESSAGE_TYPES.AUTH,
data: {
token: this.token,
token,
},
},
{
Expand Down
Loading