diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index bea04fa84..9a15f1df5 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -8,7 +8,6 @@ import { EncodingDecodingContext, CipherOptions, populateFieldsFromParent } from import Message, { WireMessage, getMessagesSize, encodeArray as encodeMessagesArray } from '../types/message'; import ChannelStateChange from './channelstatechange'; import ErrorInfo, { PartialErrorInfo } from '../types/errorinfo'; -import ConnectionErrors from '../transport/connectionerrors'; import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; import ConnectionStateChange from './connectionstatechange'; @@ -662,13 +661,13 @@ class RealtimeChannel extends EventEmitter { } default: + // RSF1, should handle unrecognized message actions gracefully and don't abort the realtime connection to ensure forward compatibility Logger.logAction( this.logger, - Logger.LOG_ERROR, + Logger.LOG_MAJOR, 'RealtimeChannel.processMessage()', - 'Fatal protocol error: unrecognised action (' + message.action + ')', + 'Protocol error: unrecognised message action (' + message.action + ')', ); - this.connectionManager.abort(ConnectionErrors.unknownChannelErr()); } } diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index e7cd78c6e..fd34c5878 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -1487,5 +1487,45 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async helper.closeAndFinish(done, realtime, err); } }); + + /** @spec RSF1 */ + it('unrecognized message action', async function () { + const helper = this.test.helper; + const realtime = helper.AblyRealtime(); + + let caughtError; + try { + await helper.monitorConnectionAsync(async () => { + const channel = realtime.channels.get('unknown_action'); + await channel.attach(); + + helper.recordPrivateApi('call.connectionManager.activeProtocol.getTransport'); + helper.recordPrivateApi('call.transport.onProtocolMessage'); + realtime.connection.connectionManager.activeProtocol + .getTransport() + .onProtocolMessage({ action: Number.MAX_SAFE_INTEGER, channel: channel.name }); + + // wait for the next event loop so connection can process any pending callbacks and go to a failed state + await new Promise((res, rej) => + setTimeout(() => { + try { + expect(realtime.connection.state).to.equal( + 'connected', + 'Check still connected after unrecognized action field', + ); + res(); + } catch (error) { + rej(error); + } + }, 0), + ); + }, realtime); + } catch (error) { + caughtError = error; + } + + expect(caughtError, 'Check connection was not closed after receiving unrecognized message action').to.not.exist; + await helper.closeAndFinishAsync(realtime); + }); }); });