diff --git a/openvidu-browser/package.json b/openvidu-browser/package.json
index adc65aa9..e281dce1 100644
--- a/openvidu-browser/package.json
+++ b/openvidu-browser/package.json
@@ -5,6 +5,7 @@
"hark": "1.2.3",
"jsnlog": "2.30.0",
"platform": "1.3.6",
+ "semver": "7.3.5",
"uuid": "8.3.2",
"wolfy87-eventemitter": "5.2.9"
},
diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts
index bcd55d1a..ad243837 100644
--- a/openvidu-browser/src/OpenVidu/Session.ts
+++ b/openvidu-browser/src/OpenVidu/Session.ts
@@ -44,6 +44,8 @@ import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/Open
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
+import semverMajor = require('semver/functions/major');
+import semverMinor = require('semver/functions/minor');
/**
* @hidden
@@ -1250,6 +1252,7 @@ export class Session extends EventDispatcher {
token: (!!token) ? token : '',
session: this.sessionId,
platform: !!platform.getDescription() ? platform.getDescription() : 'unknown',
+ sdkVersion: this.openvidu.libraryVersion,
metadata: !!this.options.metadata ? this.options.metadata : '',
secret: this.openvidu.getSecret(),
recorder: this.openvidu.getRecorder()
@@ -1547,10 +1550,14 @@ export class Session extends EventDispatcher {
if (opts.life != null) {
this.openvidu.life = opts.life;
}
- if (opts.version !== this.openvidu.libraryVersion) {
- logger.warn('OpenVidu Server (' + opts.version +
- ') and OpenVidu Browser (' + this.openvidu.libraryVersion +
- ') versions do NOT match. There may be incompatibilities')
+ const minorDifference: number = semverMinor(opts.version) - semverMinor(this.openvidu.libraryVersion);
+ if ((semverMajor(opts.version) !== semverMajor(this.openvidu.libraryVersion)) || !(minorDifference == 0 || minorDifference == 1)) {
+ logger.error(`openvidu-browser (${this.openvidu.libraryVersion}) and openvidu-server (${opts.version}) versions are incompatible. `
+ + 'Errors are likely to occur. openvidu-browser SDK is only compatible with the same version or the immediately following minor version of an OpenVidu deployment');
+ } else if (minorDifference == 1) {
+ logger.warn(`openvidu-browser version ${this.openvidu.libraryVersion} does not match openvidu-server version ${opts.version}. `
+ + `These versions are still compatible with each other, but openvidu-browser version must be updated as soon as possible to ${semverMajor(opts.version)}.${semverMinor(opts.version)}.x. `
+ + `This client using openvidu-browser ${this.openvidu.libraryVersion} will become incompatible with the next release of openvidu-server`);
}
}
diff --git a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java
index a7648fc4..4e917016 100644
--- a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java
+++ b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java
@@ -38,6 +38,7 @@ public class ProtocolElements {
public static final String JOINROOM_METADATA_PARAM = "metadata";
public static final String JOINROOM_SECRET_PARAM = "secret";
public static final String JOINROOM_PLATFORM_PARAM = "platform";
+ public static final String JOINROOM_SDKVERSION_PARAM = "sdkVersion";
public static final String JOINROOM_RECORDER_PARAM = "recorder";
public static final String JOINROOM_PEERID_PARAM = "id";
diff --git a/openvidu-server/pom.xml b/openvidu-server/pom.xml
index 80eb6a09..ae27db39 100644
--- a/openvidu-server/pom.xml
+++ b/openvidu-server/pom.xml
@@ -332,6 +332,11 @@
openvidu-java-client
${version.openvidu.java.client}
+
+ org.apache.maven
+ maven-artifact
+ ${version.maven.artifact}
+
diff --git a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java
index 11feaaf4..7c6e3b1a 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java
@@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.kurento.jsonrpc.DefaultJsonRpcHandler;
import org.kurento.jsonrpc.Session;
import org.kurento.jsonrpc.Transaction;
@@ -46,6 +47,7 @@ import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.java.client.ConnectionProperties;
+import io.openvidu.server.config.OpenviduBuildInfo;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.EndReason;
import io.openvidu.server.core.IdentifierPrefixes;
@@ -55,6 +57,8 @@ import io.openvidu.server.core.SessionManager;
import io.openvidu.server.core.Token;
import io.openvidu.server.utils.GeoLocation;
import io.openvidu.server.utils.GeoLocationByIp;
+import io.openvidu.server.utils.VersionComparator;
+import io.openvidu.server.utils.VersionComparator.VersionMismatchException;
public class RpcHandler extends DefaultJsonRpcHandler {
@@ -63,6 +67,9 @@ public class RpcHandler extends DefaultJsonRpcHandler {
@Autowired
OpenviduConfig openviduConfig;
+ @Autowired
+ OpenviduBuildInfo openviduBuildConfig;
+
@Autowired
GeoLocationByIp geoLocationByIp;
@@ -292,6 +299,8 @@ public class RpcHandler extends DefaultJsonRpcHandler {
participant.getPlatform());
}
+ checkSdkVersionCompliancy(request, participant);
+
rpcConnection.setSessionId(sessionId);
sessionManager.joinRoom(participant, sessionId, request.getId());
@@ -795,36 +804,36 @@ public class RpcHandler extends DefaultJsonRpcHandler {
return Arrays.asList("*");
}
- public static String getStringParam(Request request, String key) {
+ public static String getStringParam(Request request, String key) throws RuntimeException {
if (request.getParams() == null || request.getParams().get(key) == null) {
throw new RuntimeException("Request element '" + key + "' is missing in method '" + request != null
? request.getMethod()
: "[NO REQUEST OBJECT]"
- + "'. CHECK THAT 'openvidu-server' AND 'openvidu-browser' SHARE THE SAME VERSION NUMBER");
+ + "'. Check that 'openvidu-server' AND 'openvidu-browser' versions are compatible with each other");
}
return request.getParams().get(key).getAsString();
}
- public static int getIntParam(Request request, String key) {
+ public static int getIntParam(Request request, String key) throws RuntimeException {
if (request.getParams() == null || request.getParams().get(key) == null) {
throw new RuntimeException("Request element '" + key + "' is missing in method '" + request.getMethod()
- + "'. CHECK THAT 'openvidu-server' AND 'openvidu-browser' SHARE THE SAME VERSION NUMBER");
+ + "'. Check that 'openvidu-server' AND 'openvidu-browser' versions are compatible with each other");
}
return request.getParams().get(key).getAsInt();
}
- public static boolean getBooleanParam(Request request, String key) {
+ public static boolean getBooleanParam(Request request, String key) throws RuntimeException {
if (request.getParams() == null || request.getParams().get(key) == null) {
throw new RuntimeException("Request element '" + key + "' is missing in method '" + request.getMethod()
- + "'. CHECK THAT 'openvidu-server' AND 'openvidu-browser' SHARE THE SAME VERSION NUMBER");
+ + "'. Check that 'openvidu-server' AND 'openvidu-browser' versions are compatible with each other");
}
return request.getParams().get(key).getAsBoolean();
}
- public static JsonElement getParam(Request request, String key) {
+ public static JsonElement getParam(Request request, String key) throws RuntimeException {
if (request.getParams() == null || request.getParams().get(key) == null) {
throw new RuntimeException("Request element '" + key + "' is missing in method '" + request.getMethod()
- + "'. CHECK THAT 'openvidu-server' AND 'openvidu-browser' SHARE THE SAME VERSION NUMBER");
+ + "'. Check that 'openvidu-server' AND 'openvidu-browser' versions are compatible with each other");
}
return request.getParams().get(key);
}
@@ -879,4 +888,39 @@ public class RpcHandler extends DefaultJsonRpcHandler {
return senderPublicId;
}
+ private void checkSdkVersionCompliancy(Request request, Participant participant) {
+ // TODO: remove try-catch after release 2.21.0
+ String clientVersion = null;
+ try {
+ clientVersion = getStringParam(request, ProtocolElements.JOINROOM_SDKVERSION_PARAM);
+ } catch (RuntimeException e) {
+ // This empty catch is here to support client SDK 2.20.0 with server 2.21.0
+ // TODO: remove try-catch after release 2.21.0
+ return;
+ }
+ final String serverVersion = openviduBuildConfig.getOpenViduServerVersion();
+ try {
+ new VersionComparator().checkVersionCompatibility(clientVersion, serverVersion);
+ } catch (VersionMismatchException e) {
+ if (e.isIncompatible()) {
+ log.error(
+ "Participant {} with IP {} and platform {} has an incompatible version of openvidu-browser SDK ({}) for this OpenVidu deployment ({}). This may cause the system to malfunction",
+ participant.getParticipantPublicId(), participant.getLocation().getIp(),
+ participant.getPlatform(), clientVersion, serverVersion);
+ log.error(e.getMessage());
+ log.error(
+ "openvidu-browser SDK is only compatible with the same version or the immediately following minor version of an OpenVidu deployment");
+ } else {
+ DefaultArtifactVersion v = new DefaultArtifactVersion(serverVersion);
+ log.warn(
+ "Participant {} with IP {} and platform {} has an older version of openvidu-browser SDK ({}) for this OpenVidu deployment ({}). "
+ + "These versions are still compatible with each other, but client SDK must be updated as soon as possible to {}.x. This client using "
+ + "openvidu-browser {} will become incompatible with the next release of openvidu-server",
+ participant.getParticipantPublicId(), participant.getLocation().getIp(),
+ participant.getPlatform(), clientVersion, serverVersion,
+ (v.getMajorVersion() + "." + v.getMinorVersion()), clientVersion);
+ }
+ }
+ }
+
}
diff --git a/openvidu-server/src/main/java/io/openvidu/server/utils/VersionComparator.java b/openvidu-server/src/main/java/io/openvidu/server/utils/VersionComparator.java
new file mode 100644
index 00000000..e5fabb52
--- /dev/null
+++ b/openvidu-server/src/main/java/io/openvidu/server/utils/VersionComparator.java
@@ -0,0 +1,70 @@
+package io.openvidu.server.utils;
+
+import java.util.regex.Pattern;
+
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+
+public class VersionComparator {
+
+ // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
+ private final String OFFICIAL_SEMVER_REGEX = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$";
+
+ /**
+ * Checks if client version and server version are compatible with each other
+ *
+ * @param clientVersion
+ * @param serverVersion
+ * @throws VersionMismatchException if versions do not comply with semver
+ * format, or if client version and server
+ * version do not match
+ */
+ public void checkVersionCompatibility(String clientVersion, String serverVersion) throws VersionMismatchException {
+ checkSemver(clientVersion, "openvidu-browser");
+ checkSemver(serverVersion, "openvidu-server");
+ DefaultArtifactVersion comparableClientVersion = new DefaultArtifactVersion(clientVersion);
+ DefaultArtifactVersion comparableServerVersion = new DefaultArtifactVersion(serverVersion);
+ if (comparableClientVersion.getMajorVersion() != comparableServerVersion.getMajorVersion()) {
+ throw new VersionMismatchException(true, "Incompatible major versions of openvidu-browser (\""
+ + clientVersion + "\") and openvidu-server (\"" + serverVersion + "\")");
+ }
+ checkMinorVersions(comparableClientVersion, comparableServerVersion);
+ }
+
+ private void checkSemver(String version, String artifactName) {
+ if (!Pattern.compile(OFFICIAL_SEMVER_REGEX).matcher(version).matches()) {
+ throw new RuntimeException(
+ "Version \"" + version + "\" of " + artifactName + " does not comply with semver format");
+ }
+ }
+
+ private void checkMinorVersions(DefaultArtifactVersion clientVersion, DefaultArtifactVersion serverVersion)
+ throws VersionMismatchException {
+ int difference = serverVersion.getMinorVersion() - clientVersion.getMinorVersion();
+ if (difference == 1) {
+ throw new VersionMismatchException(false, "openvidu-browser \"" + clientVersion + "\" requires update to \""
+ + serverVersion.getMajorVersion() + "." + serverVersion.getMinorVersion() + ".x\"");
+ }
+ if (difference != 0) {
+ throw new VersionMismatchException(true, "Incompatible minor versions of openvidu-browser (\""
+ + clientVersion + "\") and openvidu-server (\"" + serverVersion + "\")");
+ }
+ }
+
+ public class VersionMismatchException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private boolean incompatible;
+
+ public VersionMismatchException(boolean incompatible, String errorMessage) {
+ super(errorMessage);
+ this.incompatible = incompatible;
+ }
+
+ public boolean isIncompatible() {
+ return this.incompatible;
+ }
+
+ }
+
+}
diff --git a/openvidu-server/src/test/java/io/openvidu/server/test/unit/VersionComparatorTest.java b/openvidu-server/src/test/java/io/openvidu/server/test/unit/VersionComparatorTest.java
new file mode 100644
index 00000000..66a7f8e3
--- /dev/null
+++ b/openvidu-server/src/test/java/io/openvidu/server/test/unit/VersionComparatorTest.java
@@ -0,0 +1,94 @@
+package io.openvidu.server.test.unit;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import io.openvidu.server.utils.VersionComparator;
+import io.openvidu.server.utils.VersionComparator.VersionMismatchException;
+
+public class VersionComparatorTest {
+
+ @Test
+ void versionSyntaxWrongTest() throws VersionMismatchException {
+ VersionComparator comparator = new VersionComparator();
+ assertThrows(RuntimeException.class, () -> {
+ comparator.checkVersionCompatibility("not_a_valid_semver", "2.21.0");
+ });
+ assertThrows(RuntimeException.class, () -> {
+ comparator.checkVersionCompatibility("2.21.0", "not.valid.semver");
+ });
+ assertThrows(RuntimeException.class, () -> {
+ comparator.checkVersionCompatibility("2.21", "2.21");
+ });
+ assertThrows(RuntimeException.class, () -> {
+ comparator.checkVersionCompatibility("2.21.0", "2.21");
+ });
+ assertThrows(RuntimeException.class, () -> {
+ comparator.checkVersionCompatibility("2", "2.21.0");
+ });
+ assertThrows(RuntimeException.class, () -> {
+ comparator.checkVersionCompatibility("", "");
+ });
+ }
+
+ @Test
+ void versionsDoMatchTest() throws VersionMismatchException {
+ VersionComparator comparator = new VersionComparator();
+ comparator.checkVersionCompatibility("2.21.0", "2.21.0");
+ comparator.checkVersionCompatibility("2.21.1", "2.21.0");
+ comparator.checkVersionCompatibility("2.19.20", "2.19.0");
+ comparator.checkVersionCompatibility("2.21.1-beta", "2.21.0");
+ comparator.checkVersionCompatibility("3.0.0", "3.0.0-beta4");
+ }
+
+ @Test
+ void versionsDoNotMatchButAreCompatibleTest() throws VersionMismatchException {
+ VersionComparator comparator = new VersionComparator();
+ try {
+ comparator.checkVersionCompatibility("2.20.0", "2.21.0");
+ } catch (VersionMismatchException e) {
+ assertFalse(e.isIncompatible());
+ }
+ try {
+ comparator.checkVersionCompatibility("2.20.5", "2.21.0");
+ } catch (VersionMismatchException e) {
+ assertFalse(e.isIncompatible());
+ }
+ try {
+ comparator.checkVersionCompatibility("2.20.5-dev", "2.21.0");
+ } catch (VersionMismatchException e) {
+ assertFalse(e.isIncompatible());
+ }
+ try {
+ comparator.checkVersionCompatibility("2.0.0", "2.0.0-dev1");
+ } catch (VersionMismatchException e) {
+ assertFalse(e.isIncompatible());
+ }
+ }
+
+ @Test
+ void versionsIncompatibleTest() {
+ VersionComparator comparator = new VersionComparator();
+ assertThrows(VersionMismatchException.class, () -> {
+ comparator.checkVersionCompatibility("2.0.0", "3.0.0");
+ });
+ assertThrows(VersionMismatchException.class, () -> {
+ comparator.checkVersionCompatibility("3.0.0", "2.0.0");
+ });
+ assertThrows(VersionMismatchException.class, () -> {
+ comparator.checkVersionCompatibility("2.21.0", "2.23.0");
+ });
+ assertThrows(VersionMismatchException.class, () -> {
+ comparator.checkVersionCompatibility("2.21.0", "2.30.0");
+ });
+ assertThrows(VersionMismatchException.class, () -> {
+ comparator.checkVersionCompatibility("2.22.0-dev1", "2.21.0");
+ });
+ assertThrows(VersionMismatchException.class, () -> {
+ comparator.checkVersionCompatibility("2.22.0", "2.21.0-dev1");
+ });
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index d75f21db..503461a1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,6 +75,7 @@
1.6.8
3.0.0
3.2.0
+ 3.8.3
11
11