From 967cd56ee0f4b20d6db928465183489a39f8d1bf Mon Sep 17 00:00:00 2001 From: JAY01 Date: Thu, 7 Jan 2021 13:14:00 +0000 Subject: [PATCH 01/14] Add configuration parameter for automatically sending an initial provisional response --- src/api/invitation.ts | 26 +++++--------------------- src/api/user-agent-options.ts | 7 +++++++ src/api/user-agent.ts | 3 ++- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/api/invitation.ts b/src/api/invitation.ts index 43860ebfc..535c9c385 100644 --- a/src/api/invitation.ts +++ b/src/api/invitation.ts @@ -187,29 +187,13 @@ export class Invitation extends Session { /** * If true, a first provisional response after the 100 Trying * will be sent automatically. This is false it the UAC required - * reliable provisional responses (100rel in Require header), - * otherwise it is true. The provisional is sent by calling - * `progress()` without any options. - * - * FIXME: TODO: It seems reasonable that the ISC user should - * be able to optionally disable this behavior. As the provisional - * is sent prior to the "invite" event being emitted, it's a known - * issue that the ISC user cannot register listeners or do any other - * setup prior to the call to `progress()`. As an example why this is - * an issue, setting `ua.configuration.rel100` to REQUIRED will result - * in an attempt by `progress()` to send a 183 with SDP produced by - * calling `getDescription()` on a session description handler, but - * the ISC user cannot perform any potentially required session description - * handler initialization (thus preventing the utilization of setting - * `ua.configuration.rel100` to REQUIRED). That begs the question of - * why this behavior is disabled when the UAC requires 100rel but not - * when the UAS requires 100rel? But ignoring that, it's just one example - * of a class of cases where the ISC user needs to do something prior - * to the first call to `progress()` and is unable to do so. - * @internal + * reliable provisional responses (100rel in Require header) or + * the user agent configuration has specified to not send an + * initial response, otherwise it is true. The provisional is sent by + * calling `progress()` without any options. */ public get autoSendAnInitialProvisionalResponse(): boolean { - return this.rel100 === "required" ? false : true; + return this.rel100 !== "required" && this.userAgent.configuration.sendInitialProvisionalResponse; } /** diff --git a/src/api/user-agent-options.ts b/src/api/user-agent-options.ts index 7836ea0b2..ee7194145 100644 --- a/src/api/user-agent-options.ts +++ b/src/api/user-agent-options.ts @@ -272,6 +272,13 @@ export interface UserAgentOptions { * A random hostname in the .invalid domain. */ viaHost?: string; + + /** + * If true, a first provisional response after the 100 Trying will be sent automatically if UAC does not + * require reliable provisional responses. + * @defaultValue `true` + */ + sendInitialProvisionalResponse?: boolean; } /** diff --git a/src/api/user-agent.ts b/src/api/user-agent.ts index 0b33b0689..336d56ef9 100644 --- a/src/api/user-agent.ts +++ b/src/api/user-agent.ts @@ -284,7 +284,8 @@ export class UserAgent { transportOptions: {}, uri: new URI("sip", "anonymous", "anonymous.invalid"), userAgentString: "SIP.js/" + LIBRARY_VERSION, - viaHost: "" + viaHost: "", + sendInitialProvisionalResponse: true }; } From 5b5f9212bf4f4e918f36ad05573ee104dedc01cf Mon Sep 17 00:00:00 2001 From: John Riordan Date: Wed, 20 Jan 2021 13:22:00 -0500 Subject: [PATCH 02/14] URI comparison done per RFC 3261 Section 19.1.4 --- src/api/referral.ts | 6 +- src/api/registerer.ts | 37 +++++---- src/api/user-agent-options.ts | 8 +- src/grammar/uri.ts | 140 ++++++++++++++++++++++++++++++++-- 4 files changed, 166 insertions(+), 25 deletions(-) diff --git a/src/api/referral.ts b/src/api/referral.ts index 8c1edf267..b4b698001 100644 --- a/src/api/referral.ts +++ b/src/api/referral.ts @@ -26,7 +26,11 @@ export class Referral { } public get replaces(): string | undefined { - return this.referTo.uri.getHeader("replaces"); + const value = this.referTo.uri.getHeader("replaces"); + if (value instanceof Array) { + return value[0]; + } + return value; } /** Incoming REFER request message. */ diff --git a/src/api/registerer.ts b/src/api/registerer.ts index 206b7500b..70461b6f6 100644 --- a/src/api/registerer.ts +++ b/src/api/registerer.ts @@ -1,4 +1,13 @@ -import { C, Grammar, Logger, OutgoingRegisterRequest, OutgoingRequestMessage, URI, NameAddrHeader } from "../core"; +import { + C, + Grammar, + Logger, + OutgoingRegisterRequest, + OutgoingRequestMessage, + URI, + equivalentURI, + NameAddrHeader +} from "../core"; import { Emitter, EmitterImpl } from "./emitter"; import { RequestPendingError } from "./exceptions"; import { RegistererOptions } from "./registerer-options"; @@ -374,19 +383,21 @@ export class Registerer { throw new Error("Contact undefined"); } - /* Adding host and port checks may break people not using contactName, so only check those - * if the parameter is set. The server mucking with host and port is entirely legal, - * so in cases where that occurs usage of contactName is currently broken. - */ - if ( - contact.uri.user === this.userAgent.contact.uri.user && - (this.userAgent.configuration.contactName === "" || - (contact.uri.host === this.userAgent.contact.uri.host && - contact.uri.port === this.userAgent.contact.uri.port)) - ) { - expires = Number(contact.getParam("expires")); - break; + // If we are using a randomly generated user name (which is the default behavior) + if (this.userAgent.configuration.contactName === "") { + // compare the user portion of the URI under the assumption that it will be unique + if (contact.uri.user === this.userAgent.contact.uri.user) { + expires = Number(contact.getParam("expires")); + break; + } + } else { + // otherwise use comparision rules in Section 19.1.4 + if (equivalentURI(contact.uri, this.userAgent.contact.uri)) { + expires = Number(contact.getParam("expires")); + break; + } } + contact = undefined; } diff --git a/src/api/user-agent-options.ts b/src/api/user-agent-options.ts index 7836ea0b2..c0ef17dbd 100644 --- a/src/api/user-agent-options.ts +++ b/src/api/user-agent-options.ts @@ -74,17 +74,15 @@ export interface UserAgentOptions { autoStop?: boolean; /** - * The contact name associated with the user agent. + * The user portion of user agent's contact URI. * @remarks - * User specified contact name, if not specifed random string will be generated + * If not specifed a random string will be generated and utilized as the user portion of the contact URI. * @defaultValue `""` */ contactName?: string; /** - * The URI parameters associated with the user agent. - * @remarks - * User specified contact parameters + * The URI parameters of the user agent's contact URI. * @defaultValue `{ transport: "ws" }` */ contactParams?: { [name: string]: string }; diff --git a/src/grammar/uri.ts b/src/grammar/uri.ts index 207b6d039..57200f2df 100644 --- a/src/grammar/uri.ts +++ b/src/grammar/uri.ts @@ -13,7 +13,7 @@ interface URIObject { * @public */ export class URI extends Parameters { - private headers: {[name: string]: any} = {}; + public headers: {[name: string]: Array} = {}; private normal: URIObject; private raw: URIObject; @@ -32,7 +32,7 @@ export class URI extends Parameters { host: string, port?: number, parameters?: { [name: string]: string | number | null }, - headers?: any + headers?: {[name: string]: Array} ) { super(parameters || {}); // Checks @@ -88,11 +88,11 @@ export class URI extends Parameters { this.normal.port = this.raw.port = value === 0 ? value : value; } - public setHeader(name: string, value: any): void { + public setHeader(name: string, value: Array | string): void { this.headers[this.headerize(name)] = (value instanceof Array) ? value : [value]; } - public getHeader(name: string): string | undefined { + public getHeader(name: string): Array | undefined { if (name) { return this.headers[this.headerize(name)]; } @@ -103,12 +103,12 @@ export class URI extends Parameters { return !!name && !!this.headers.hasOwnProperty(this.headerize(name)); } - public deleteHeader(header: string): any { + public deleteHeader(header: string): Array | undefined { header = this.headerize(header); // eslint-disable-next-line no-prototype-builtins if (this.headers.hasOwnProperty(header)) { - const value: any = this.headers[header]; + const value = this.headers[header]; delete this.headers[header]; return value; } @@ -169,6 +169,7 @@ export class URI extends Parameters { for (const header in this.headers) { // eslint-disable-next-line no-prototype-builtins if (this.headers.hasOwnProperty(header)) { + // eslint-disable-next-line @typescript-eslint/no-for-in-array for (const idx in this.headers[header]) { // eslint-disable-next-line no-prototype-builtins if (this.headers[header].hasOwnProperty(idx)) { @@ -239,3 +240,130 @@ export class URI extends Parameters { return hname; } } + +/** + * Returns true if URIs are equivalent per RFC 3261 Section 19.1.4. + * @param a URI to compare + * @param b URI to compare + * + * @remarks + * 19.1.4 URI Comparison + * Some operations in this specification require determining whether two + * SIP or SIPS URIs are equivalent. + * + * https://tools.ietf.org/html/rfc3261#section-19.1.4 + */ +export function equivalentURI(a: URI, b: URI): boolean { + + // o A SIP and SIPS URI are never equivalent. + if (a.scheme !== b.scheme) { + return false; + } + + // o Comparison of the userinfo of SIP and SIPS URIs is case- + // sensitive. This includes userinfo containing passwords or + // formatted as telephone-subscribers. Comparison of all other + // components of the URI is case-insensitive unless explicitly + // defined otherwise. + // + // o The ordering of parameters and header fields is not significant + // in comparing SIP and SIPS URIs. + // + // o Characters other than those in the "reserved" set (see RFC 2396 + // [5]) are equivalent to their ""%" HEX HEX" encoding. + // + // o An IP address that is the result of a DNS lookup of a host name + // does not match that host name. + // + // o For two URIs to be equal, the user, password, host, and port + // components must match. + // + // A URI omitting the user component will not match a URI that + // includes one. A URI omitting the password component will not + // match a URI that includes one. + // + // A URI omitting any component with a default value will not + // match a URI explicitly containing that component with its + // default value. For instance, a URI omitting the optional port + // component will not match a URI explicitly declaring port 5060. + // The same is true for the transport-parameter, ttl-parameter, + // user-parameter, and method components. + // + // Defining sip:user@host to not be equivalent to + // sip:user@host:5060 is a change from RFC 2543. When deriving + // addresses from URIs, equivalent addresses are expected from + // equivalent URIs. The URI sip:user@host:5060 will always + // resolve to port 5060. The URI sip:user@host may resolve to + // other ports through the DNS SRV mechanisms detailed in [4]. + + // FIXME: TODO: + // - character compared to hex encoding is not handled + // - password does not exist on URI currently + if (a.user !== b.user || a.host !== b.host || a.port !== b.port) { + return false; + } + + // o URI uri-parameter components are compared as follows: + function compareParameters(a: URI, b: URI): boolean { + // - Any uri-parameter appearing in both URIs must match. + const parameterKeysA = Object.keys(a.parameters); + const parameterKeysB = Object.keys(b.parameters); + const intersection = parameterKeysA.filter(x => parameterKeysB.includes(x)); + if (!intersection.every(key => a.parameters[key] === b.parameters[key])) { + return false; + } + + // - A user, ttl, or method uri-parameter appearing in only one + // URI never matches, even if it contains the default value. + if (!["user", "ttl", "method", "transport"].every(key => a.hasParam(key) && b.hasParam(key) || !a.hasParam(key) && !b.hasParam(key))) { + return false; + } + + // - A URI that includes an maddr parameter will not match a URI + // that contains no maddr parameter. + if (!["maddr"].every(key => a.hasParam(key) && b.hasParam(key) || !a.hasParam(key) && !b.hasParam(key))) { + return false; + } + + // - All other uri-parameters appearing in only one URI are + // ignored when comparing the URIs. + return true; + } + + if (!compareParameters(a, b)) { + return false; + } + + // o URI header components are never ignored. Any present header + // component MUST be present in both URIs and match for the URIs + // to match. The matching rules are defined for each header field + // in Section 20. + const headerKeysA = Object.keys(a.headers); + const headerKeysB = Object.keys(b.headers); + + // No need to check if no headers + if (headerKeysA.length !== 0 || headerKeysB.length !== 0) { + + // Must have same number of headers + if (headerKeysA.length !== headerKeysB.length) { + return false; + } + + // Must have same headers + const intersection = headerKeysA.filter(x => headerKeysB.includes(x)); + if (intersection.length !== headerKeysB.length) { + return false; + } + + // FIXME: Not to spec. But perhaps not worth fixing? + // Must have same header values + // It seems too much to consider multiple headers with same name. + // It seems too much to compare two header params according to the rule of each header. + // We'll assume a single header and compare them string to string... + if (!intersection.every(key => a.headers[key].length && b.headers[key].length && a.headers[key][0] === b.headers[key][0])) { + return false; + } + } + + return true; +} \ No newline at end of file From 026079ac4c7b8bfbe91400d29b82243110666dbf Mon Sep 17 00:00:00 2001 From: John Riordan Date: Wed, 20 Jan 2021 13:22:06 -0500 Subject: [PATCH 03/14] Tests --- test/spec/core/uri.spec.ts | 94 +++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/test/spec/core/uri.spec.ts b/test/spec/core/uri.spec.ts index 2527c1f5e..a31bb46f0 100644 --- a/test/spec/core/uri.spec.ts +++ b/test/spec/core/uri.spec.ts @@ -1,4 +1,4 @@ -import { Grammar, URI } from "../../../src/core"; +import { Grammar, URI, equivalentURI } from "../../../src/core"; // TODO: // These tests were ported to typescript verbatim. @@ -399,4 +399,96 @@ describe("Core URI", () => { }); }); }); + + describe("equivalentURI", () => { + it("equivalent - 1", () => { + const a = Grammar.URIParse("sip:%61lice@atlanta.com;transport=TCP"); + const b = Grammar.URIParse("sip:alice@AtLanTa.CoM;Transport=tcp"); + expect(a instanceof URI).toBe(true); + expect(b instanceof URI).toBe(true); + if (a instanceof URI && b instanceof URI) { + expect(equivalentURI(a, b)).toEqual(true); + } + }); + + it("equivalent - 2", () => { + const a = Grammar.URIParse("sip:carol@chicago.com"); + const b = Grammar.URIParse("sip:carol@chicago.com;security=off"); + expect(a instanceof URI).toBe(true); + expect(b instanceof URI).toBe(true); + if (a instanceof URI && b instanceof URI) { + expect(equivalentURI(a, b)).toEqual(true); + } + }); + + it("equivalent - 3", () => { + const a = Grammar.URIParse("sip:biloxi.com;transport=tcp;method=REGISTER?to=sip:bob%40biloxi.com"); + const b = Grammar.URIParse("sip:biloxi.com;method=REGISTER;transport=tcp?to=sip:bob%40biloxi.com"); + expect(a instanceof URI).toBe(true); + expect(b instanceof URI).toBe(true); + if (a instanceof URI && b instanceof URI) { + expect(equivalentURI(a, b)).toEqual(true); + } + }); + + it("equivalent - 4", () => { + const a = Grammar.URIParse("sip:alice@atlanta.com?subject=project%20x&priority=urgent"); + const b = Grammar.URIParse("sip:alice@atlanta.com?priority=urgent&subject=project%20x"); + expect(a instanceof URI).toBe(true); + expect(b instanceof URI).toBe(true); + if (a instanceof URI && b instanceof URI) { + expect(equivalentURI(a, b)).toEqual(true); + } + }); + + it("not equivalent - different usernames", () => { + const a = Grammar.URIParse("SIP:ALICE@AtLanTa.CoM;Transport=udp"); + const b = Grammar.URIParse("sip:alice@AtLanTa.CoM;Transport=UDP"); + expect(a instanceof URI).toBe(true); + expect(b instanceof URI).toBe(true); + if (a instanceof URI && b instanceof URI) { + expect(equivalentURI(a, b)).toEqual(false); + } + }); + + it("not equivalent - can resolve to different ports", () => { + const a = Grammar.URIParse("sip:bob@biloxi.com"); + const b = Grammar.URIParse("sip:bob@biloxi.com:5060"); + expect(a instanceof URI).toBe(true); + expect(b instanceof URI).toBe(true); + if (a instanceof URI && b instanceof URI) { + expect(equivalentURI(a, b)).toEqual(false); + } + }); + + it("not equivalent - can resolve to different transports", () => { + const a = Grammar.URIParse("sip:bob@biloxi.com"); + const b = Grammar.URIParse("sip:bob@biloxi.com;transport=udp"); + expect(a instanceof URI).toBe(true); + expect(b instanceof URI).toBe(true); + if (a instanceof URI && b instanceof URI) { + expect(equivalentURI(a, b)).toEqual(false); + } + }); + + it("not equivalent - different header component", () => { + const a = Grammar.URIParse("sip:carol@chicago.com"); + const b = Grammar.URIParse("sip:carol@chicago.com?Subject=next%20meeting"); + expect(a instanceof URI).toBe(true); + expect(b instanceof URI).toBe(true); + if (a instanceof URI && b instanceof URI) { + expect(equivalentURI(a, b)).toEqual(false); + } + }); + + it("not equivalent - transitive", () => { + const a = Grammar.URIParse("sip:carol@chicago.com;security=on"); + const b = Grammar.URIParse("sip:carol@chicago.com;security=off"); + expect(a instanceof URI).toBe(true); + expect(b instanceof URI).toBe(true); + if (a instanceof URI && b instanceof URI) { + expect(equivalentURI(a, b)).toEqual(false); + } + }); + }); }); From eb18d1f70222a1cc6b8a81177dbc0a9cfa6fcda5 Mon Sep 17 00:00:00 2001 From: John Riordan Date: Wed, 20 Jan 2021 13:27:25 -0500 Subject: [PATCH 04/14] Docs --- .../sip.js.useragentoptions.contactname.md | 4 ++-- .../sip.js.useragentoptions.contactparams.md | 7 +------ docs/api/sip.js.useragentoptions.md | 4 ++-- docs/core/sip.js.uri._constructor_.md | 6 ++++-- docs/core/sip.js.uri.deleteheader.md | 4 ++-- docs/core/sip.js.uri.getheader.md | 4 ++-- docs/core/sip.js.uri.headers.md | 13 +++++++++++++ docs/core/sip.js.uri.md | 1 + docs/core/sip.js.uri.setheader.md | 4 ++-- etc/core/sip.js.api.md | 19 +++++++++++++++---- src/grammar/uri.ts | 1 + 11 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 docs/core/sip.js.uri.headers.md diff --git a/docs/api/sip.js.useragentoptions.contactname.md b/docs/api/sip.js.useragentoptions.contactname.md index c1dfd93a4..aeefd55e3 100644 --- a/docs/api/sip.js.useragentoptions.contactname.md +++ b/docs/api/sip.js.useragentoptions.contactname.md @@ -4,7 +4,7 @@ ## UserAgentOptions.contactName property -The contact name associated with the user agent. +The user portion of user agent's contact URI. Signature: @@ -14,5 +14,5 @@ contactName?: string; ## Remarks -User specified contact name, if not specifed random string will be generated +If not specifed a random string will be generated and utilized as the user portion of the contact URI. diff --git a/docs/api/sip.js.useragentoptions.contactparams.md b/docs/api/sip.js.useragentoptions.contactparams.md index 1b9e0d0b9..d3050d9e4 100644 --- a/docs/api/sip.js.useragentoptions.contactparams.md +++ b/docs/api/sip.js.useragentoptions.contactparams.md @@ -4,7 +4,7 @@ ## UserAgentOptions.contactParams property -The URI parameters associated with the user agent. +The URI parameters of the user agent's contact URI. Signature: @@ -13,8 +13,3 @@ contactParams?: { [name: string]: string; }; ``` - -## Remarks - -User specified contact parameters - diff --git a/docs/api/sip.js.useragentoptions.md b/docs/api/sip.js.useragentoptions.md index 732be34e3..e1d26d722 100644 --- a/docs/api/sip.js.useragentoptions.md +++ b/docs/api/sip.js.useragentoptions.md @@ -22,8 +22,8 @@ export interface UserAgentOptions | [authorizationUsername](./sip.js.useragentoptions.authorizationusername.md) | string | Authorization username. | | [autoStart](./sip.js.useragentoptions.autostart.md) | boolean | | | [autoStop](./sip.js.useragentoptions.autostop.md) | boolean | If true, the user agent calls the stop() method on unload (if running in browser window). | -| [contactName](./sip.js.useragentoptions.contactname.md) | string | The contact name associated with the user agent. | -| [contactParams](./sip.js.useragentoptions.contactparams.md) | {
[name: string]: string;
} | The URI parameters associated with the user agent. | +| [contactName](./sip.js.useragentoptions.contactname.md) | string | The user portion of user agent's contact URI. | +| [contactParams](./sip.js.useragentoptions.contactparams.md) | {
[name: string]: string;
} | The URI parameters of the user agent's contact URI. | | [delegate](./sip.js.useragentoptions.delegate.md) | UserAgentDelegate | Delegate for [UserAgent](./sip.js.useragent.md). | | [displayName](./sip.js.useragentoptions.displayname.md) | string | The display name associated with the user agent. | | [forceRport](./sip.js.useragentoptions.forcerport.md) | boolean | Force adding rport to Via header. | diff --git a/docs/core/sip.js.uri._constructor_.md b/docs/core/sip.js.uri._constructor_.md index ecf465275..4ddbd4055 100644 --- a/docs/core/sip.js.uri._constructor_.md +++ b/docs/core/sip.js.uri._constructor_.md @@ -11,7 +11,9 @@ Constructor ```typescript constructor(scheme: string | undefined, user: string, host: string, port?: number, parameters?: { [name: string]: string | number | null; - }, headers?: any); + }, headers?: { + [name: string]: Array; + }); ``` ## Parameters @@ -23,5 +25,5 @@ constructor(scheme: string | undefined, user: string, host: string, port?: numbe | host | string | | | port | number | | | parameters | {
[name: string]: string | number | null;
} | | -| headers | any | | +| headers | {
[name: string]: Array<string>;
} | | diff --git a/docs/core/sip.js.uri.deleteheader.md b/docs/core/sip.js.uri.deleteheader.md index 2534bee30..ddac5c514 100644 --- a/docs/core/sip.js.uri.deleteheader.md +++ b/docs/core/sip.js.uri.deleteheader.md @@ -7,7 +7,7 @@ Signature: ```typescript -deleteHeader(header: string): any; +deleteHeader(header: string): Array | undefined; ``` ## Parameters @@ -18,5 +18,5 @@ deleteHeader(header: string): any; Returns: -`any` +`Array | undefined` diff --git a/docs/core/sip.js.uri.getheader.md b/docs/core/sip.js.uri.getheader.md index a0d69ddc6..9e0d6597c 100644 --- a/docs/core/sip.js.uri.getheader.md +++ b/docs/core/sip.js.uri.getheader.md @@ -7,7 +7,7 @@ Signature: ```typescript -getHeader(name: string): string | undefined; +getHeader(name: string): Array | undefined; ``` ## Parameters @@ -18,5 +18,5 @@ getHeader(name: string): string | undefined; Returns: -`string | undefined` +`Array | undefined` diff --git a/docs/core/sip.js.uri.headers.md b/docs/core/sip.js.uri.headers.md new file mode 100644 index 000000000..944676c52 --- /dev/null +++ b/docs/core/sip.js.uri.headers.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [sip.js](./sip.js.md) > [URI](./sip.js.uri.md) > [headers](./sip.js.uri.headers.md) + +## URI.headers property + +Signature: + +```typescript +headers: { + [name: string]: Array; + }; +``` diff --git a/docs/core/sip.js.uri.md b/docs/core/sip.js.uri.md index c0c89c224..dc24952e0 100644 --- a/docs/core/sip.js.uri.md +++ b/docs/core/sip.js.uri.md @@ -23,6 +23,7 @@ export declare class URI extends Parameters | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [aor](./sip.js.uri.aor.md) | | string | | +| [headers](./sip.js.uri.headers.md) | | {
[name: string]: Array<string>;
} | | | [host](./sip.js.uri.host.md) | | string | | | [port](./sip.js.uri.port.md) | | number | undefined | | | [scheme](./sip.js.uri.scheme.md) | | string | | diff --git a/docs/core/sip.js.uri.setheader.md b/docs/core/sip.js.uri.setheader.md index 029063998..cac3c4f16 100644 --- a/docs/core/sip.js.uri.setheader.md +++ b/docs/core/sip.js.uri.setheader.md @@ -7,7 +7,7 @@ Signature: ```typescript -setHeader(name: string, value: any): void; +setHeader(name: string, value: Array | string): void; ``` ## Parameters @@ -15,7 +15,7 @@ setHeader(name: string, value: any): void; | Parameter | Type | Description | | --- | --- | --- | | name | string | | -| value | any | | +| value | Array<string> | string | | Returns: diff --git a/etc/core/sip.js.api.md b/etc/core/sip.js.api.md index 96e021308..dd1e4456a 100644 --- a/etc/core/sip.js.api.md +++ b/etc/core/sip.js.api.md @@ -177,6 +177,11 @@ export class DigestAuthentication { toString(): string; } +// Warning: (ae-internal-missing-underscore) The name "equivalentURI" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export function equivalentURI(a: URI, b: URI): boolean; + // @public export abstract class Exception extends Error { protected constructor(message?: string); @@ -1134,7 +1139,9 @@ export class TransportError extends Exception { export class URI extends Parameters { constructor(scheme: string | undefined, user: string, host: string, port?: number, parameters?: { [name: string]: string | number | null; - }, headers?: any); + }, headers?: { + [name: string]: Array; + }); // (undocumented) get aor(): string; // (undocumented) @@ -1142,12 +1149,16 @@ export class URI extends Parameters { // (undocumented) clone(): URI; // (undocumented) - deleteHeader(header: string): any; + deleteHeader(header: string): Array | undefined; // (undocumented) - getHeader(name: string): string | undefined; + getHeader(name: string): Array | undefined; // (undocumented) hasHeader(name: string): boolean; // (undocumented) + headers: { + [name: string]: Array; + }; + // (undocumented) get host(): string; set host(value: string); // (undocumented) @@ -1157,7 +1168,7 @@ export class URI extends Parameters { get scheme(): string; set scheme(value: string); // (undocumented) - setHeader(name: string, value: any): void; + setHeader(name: string, value: Array | string): void; // (undocumented) toRaw(): string; // (undocumented) diff --git a/src/grammar/uri.ts b/src/grammar/uri.ts index 57200f2df..499f0b611 100644 --- a/src/grammar/uri.ts +++ b/src/grammar/uri.ts @@ -252,6 +252,7 @@ export class URI extends Parameters { * SIP or SIPS URIs are equivalent. * * https://tools.ietf.org/html/rfc3261#section-19.1.4 + * @internal */ export function equivalentURI(a: URI, b: URI): boolean { From ef58bdec9fafac211184bbfbce707997aeb321a2 Mon Sep 17 00:00:00 2001 From: James Criscuolo Date: Thu, 21 Jan 2021 10:59:29 -0500 Subject: [PATCH 05/14] update dependencies to clear github PR --- package-lock.json | 696 ++++++++++++++++------------------------------ package.json | 16 +- 2 files changed, 248 insertions(+), 464 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca05a35e9..8305287de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,9 +44,9 @@ } }, "@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -56,7 +56,7 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" } @@ -185,6 +185,24 @@ "integrity": "sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ==", "dev": true }, + "@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==", + "dev": true + }, + "@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==", + "dev": true + }, + "@types/cors": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.9.tgz", + "integrity": "sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg==", + "dev": true + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -192,9 +210,9 @@ "dev": true }, "@types/jasmine": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.6.2.tgz", - "integrity": "sha512-AzfesNFLvOs6Q1mHzIsVJXSeUnqVh4ZHG8ngygKJfbkcSLwzrBVm/LKa+mR8KrOfnWtUL47112gde1MC0IXqpQ==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.6.3.tgz", + "integrity": "sha512-5QKAG8WfC9XrOgYLXPrxv1G2IIUE6zDyzTWamhNWJO0LqPRUbZ0q0zGHDhDJ7MpFloUuyME/jpBIdPjq3/P3jA==", "dev": true }, "@types/json-schema": { @@ -480,12 +498,6 @@ "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -580,12 +592,6 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true - }, "asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -652,24 +658,12 @@ "dev": true, "optional": true }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -749,15 +743,6 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "dev": true, - "requires": { - "callsite": "1.0.0" - } - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -780,12 +765,6 @@ "file-uri-to-path": "1.0.0" } }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true - }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -1059,12 +1038,6 @@ "unset-value": "^1.0.0" } }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true - }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1199,14 +1172,14 @@ } }, "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "wrap-ansi": "^7.0.0" }, "dependencies": { "emoji-regex": { @@ -1288,24 +1261,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", "dev": true }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1372,9 +1333,9 @@ "dev": true }, "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", "dev": true }, "copy-concurrently": { @@ -1414,6 +1375,16 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "create-ecdh": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", @@ -1718,104 +1689,27 @@ } }, "engine.io": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", - "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.0.tgz", + "integrity": "sha512-vW7EAtn0HDQ4MtT5QbmCHF17TaYLONv2/JwdYsq9USPRZVM4zG7WB3k0Nc321z8EuSOlhGokrYlYx4176QhD0A==", "dev": true, "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "0.3.1", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "ws": "^7.1.2" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "engine.io-client": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.4.tgz", - "integrity": "sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ==", - "dev": true, - "requires": { - "component-emitter": "~1.3.0", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.2.0", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "ws": "~6.1.0", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - }, - "dependencies": { - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "dev": true - }, - "parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "dev": true - }, - "ws": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", - "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - } + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.0", + "ws": "~7.4.2" } }, "engine.io-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", - "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", "dev": true, "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.4", - "blob": "0.0.5", - "has-binary2": "~1.0.2" + "base64-arraybuffer": "0.1.4" } }, "enhanced-resolve": { @@ -1865,6 +1759,12 @@ "prr": "~1.0.1" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1878,13 +1778,13 @@ "dev": true }, "eslint": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.16.0.tgz", - "integrity": "sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", + "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -1908,7 +1808,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -1940,15 +1840,15 @@ } }, "eslint-config-prettier": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz", - "integrity": "sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", + "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", "dev": true }, "eslint-plugin-prettier": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.0.tgz", - "integrity": "sha512-tMTwO8iUWlSRZIwS9k7/E4vrTsfvsrcM5p1eftyuqWH25nKsz/o6/54I7jwQ/3zobISyC7wMy9ZsFwgTxOcOpQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz", + "integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" @@ -2319,16 +2219,6 @@ "pkg-dir": "^3.0.0" } }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, "findup-sync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", @@ -2473,9 +2363,9 @@ } }, "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==", "dev": true }, "for-in": { @@ -2650,21 +2540,6 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "dev": true, - "requires": { - "isarray": "2.0.1" - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -2874,12 +2749,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -3042,12 +2911,6 @@ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, "isbinaryfile": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", @@ -3131,9 +2994,9 @@ } }, "karma": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/karma/-/karma-5.2.3.tgz", - "integrity": "sha512-tHdyFADhVVPBorIKCX8A37iLHxc6RBRphkSoQ+MLKdAtFn1k97tD8WUGi1KlEtDZKL3hui0qhsY9HXUfSNDYPQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.0.1.tgz", + "integrity": "sha512-LHE8Ywfeji6Sk3jt072j7gUAYZrpw7tCzvQWpgJqwCC9inW4eA3g1yl6srTtC0xiOvZDqXIzx8tgBPm0qhfYkg==", "dev": true, "requires": { "body-parser": "^1.19.0", @@ -3154,11 +3017,11 @@ "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^2.3.0", + "socket.io": "^3.0.4", "source-map": "^0.6.1", "tmp": "0.2.1", - "ua-parser-js": "0.7.22", - "yargs": "^15.3.1" + "ua-parser-js": "^0.7.23", + "yargs": "^16.1.1" }, "dependencies": { "colors": { @@ -3316,15 +3179,6 @@ "json5": "^1.0.1" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", @@ -3490,18 +3344,18 @@ "dev": true }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", "dev": true }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "dev": true, "requires": { - "mime-db": "1.44.0" + "mime-db": "1.45.0" } }, "minimalistic-assert": { @@ -3720,12 +3574,6 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -3822,15 +3670,6 @@ "p-try": "^2.0.0" } }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -3882,24 +3721,6 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3925,12 +3746,6 @@ "dev": true, "optional": true }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -4283,6 +4098,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -4363,9 +4184,9 @@ "dev": true }, "rfdc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", - "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.2.0.tgz", + "integrity": "sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA==", "dev": true }, "rimraf": { @@ -4690,128 +4511,52 @@ } }, "socket.io": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", - "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.0.tgz", + "integrity": "sha512-Aqg2dlRh6xSJvRYK31ksG65q4kmBOqU4g+1ukhPcoT6wNGYoIwSYPlCPuRwOO9pgLUajojGFztl6+V2opmKcww==", "dev": true, "requires": { - "debug": "~4.1.0", - "engine.io": "~3.4.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.3.0", - "socket.io-parser": "~3.4.0" + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": "^14.14.10", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.1", + "engine.io": "~4.1.0", + "socket.io-adapter": "~2.1.0", + "socket.io-parser": "~4.0.3" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } + "@types/node": { + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==", + "dev": true } } }, "socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", + "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==", "dev": true }, - "socket.io-client": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", - "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", - "dev": true, - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "engine.io-client": "~3.4.0", - "has-binary2": "~1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "~3.3.0", - "to-array": "0.1.4" - }, - "dependencies": { - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "socket.io-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.1.tgz", - "integrity": "sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ==", - "dev": true, - "requires": { - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - } - } - }, "socket.io-parser": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", - "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", "dev": true, "requires": { - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "isarray": "2.0.1" + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true } } }, @@ -5038,17 +4783,29 @@ } }, "table": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.4.tgz", - "integrity": "sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "dev": true, "requires": { - "ajv": "^6.12.4", + "ajv": "^7.0.2", "lodash": "^4.17.20", "slice-ansi": "^4.0.0", "string-width": "^4.2.0" }, "dependencies": { + "ajv": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", + "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -5061,6 +4818,12 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -5148,12 +4911,6 @@ "rimraf": "^3.0.0" } }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -5208,41 +4965,53 @@ "dev": true }, "ts-loader": { - "version": "8.0.12", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.12.tgz", - "integrity": "sha512-UIivVfGVJDdwwjgSrbtcL9Nf10c1BWnL1mxAQUVcnhNIn/P9W3nP5v60Z0aBMtc7ZrE11lMmU6+5jSgAXmGaYw==", + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.14.tgz", + "integrity": "sha512-Jt/hHlUnApOZjnSjTmZ+AbD5BGlQFx3f1D0nYuNKwz0JJnuDGHJas6az+FlWKwwRTu+26GXpv249A8UAnYUpqA==", "dev": true, "requires": { - "chalk": "^2.3.0", + "chalk": "^4.1.0", "enhanced-resolve": "^4.0.0", - "loader-utils": "^1.0.2", + "loader-utils": "^2.0.0", "micromatch": "^4.0.0", - "semver": "^6.0.0" + "semver": "^7.3.4" }, "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, "ts-pegjs": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/ts-pegjs/-/ts-pegjs-0.2.7.tgz", - "integrity": "sha512-pB+lqVyPWEUDy8w4E53Yu8V6w20No75J6lHUD+sFzogGW/Qn/QtkdR+vYYfMVEsDpz7LEKUOU5Rqf6dgVA3qsQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ts-pegjs/-/ts-pegjs-0.3.0.tgz", + "integrity": "sha512-Hvcg0SXJBJ5Q3Vf7CiWU5ZNsqoh9PLBWg+rcL8xn5Q8t1iF82LEtevWUJxPGilHjfTz++31ZK9m9gVndGy5tdg==", "dev": true, "requires": { "pegjs": "^0.10.0" @@ -5307,9 +5076,9 @@ "dev": true }, "ua-parser-js": { - "version": "0.7.22", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz", - "integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==", + "version": "0.7.23", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.23.tgz", + "integrity": "sha512-m4hvMLxgGHXG3O3fQVAyyAQpZzDOvwnhOTjYz5Xmr7r/+LpkNy3vJXdVRWgd1TkAb7NGROZuSy96CrlNVjA7KA==", "dev": true }, "union-value": { @@ -5503,6 +5272,12 @@ "integrity": "sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA==", "dev": true }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -5758,9 +5533,9 @@ } }, "webpack": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", - "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", + "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", @@ -5771,7 +5546,7 @@ "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.3.0", + "enhanced-resolve": "^4.5.0", "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.4.0", @@ -5823,6 +5598,29 @@ } } }, + "enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -6181,9 +5979,9 @@ } }, "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -6247,15 +6045,9 @@ "dev": true }, "ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", - "dev": true - }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", "dev": true }, "xtend": { @@ -6277,22 +6069,18 @@ "dev": true }, "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "dependencies": { "emoji-regex": { @@ -6317,23 +6105,19 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } + }, + "y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", + "dev": true } } }, "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true }, "z-schema": { diff --git a/package.json b/package.json index 2da3ee627..d8f76889d 100644 --- a/package.json +++ b/package.json @@ -33,16 +33,16 @@ "devDependencies": { "@microsoft/api-documenter": "7.7.20", "@microsoft/api-extractor": "7.7.11", - "@types/jasmine": "^3.6.2", + "@types/jasmine": "^3.6.3", "@typescript-eslint/eslint-plugin": "^2.34.0", "@typescript-eslint/parser": "^2.34.0", "circular-dependency-plugin": "^5.2.2", - "eslint": "^7.16.0", - "eslint-config-prettier": "^7.1.0", - "eslint-plugin-prettier": "^3.3.0", + "eslint": "^7.18.0", + "eslint-config-prettier": "^7.2.0", + "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-tree-shaking": "^1.8.0", "jasmine-core": "^3.6.0", - "karma": "^5.2.3", + "karma": "^6.0.1", "karma-chrome-launcher": "^3.1.0", "karma-cli": "^2.0.0", "karma-jasmine": "4.0.1", @@ -53,10 +53,10 @@ "mock-socket": "^9.0.3", "pegjs": "^0.10.0", "prettier": "2.2.1", - "ts-loader": "^8.0.12", - "ts-pegjs": "0.2.7", + "ts-loader": "^8.0.14", + "ts-pegjs": "0.3.0", "typescript": "4.1.3", - "webpack": "^4.44.2", + "webpack": "^4.46.0", "webpack-cli": "^3.3.12" }, "engines": { From 9e4281531e42336a4068b49cfa346cc058997991 Mon Sep 17 00:00:00 2001 From: John Riordan Date: Sun, 24 Jan 2021 11:53:57 -0500 Subject: [PATCH 06/14] Update Web SDH to support RFC8829 compliant hold --- .../session-description-handler-options.ts | 5 + .../session-description-handler.ts | 189 ++++++++++++++++-- 2 files changed, 179 insertions(+), 15 deletions(-) diff --git a/src/platform/web/session-description-handler/session-description-handler-options.ts b/src/platform/web/session-description-handler/session-description-handler-options.ts index 39611328d..3d7ba41d1 100644 --- a/src/platform/web/session-description-handler/session-description-handler-options.ts +++ b/src/platform/web/session-description-handler/session-description-handler-options.ts @@ -33,6 +33,11 @@ export interface SessionDescriptionHandlerOptions extends SessionDescriptionHand */ dataChannelOptions?: RTCDataChannelInit; + /** + * If true, offer and answer directions will be set to place peer on hold. + */ + hold?: boolean; + /** * The maximum duration to wait in ms for ICE gathering to complete. * No timeout if undefined or zero. diff --git a/src/platform/web/session-description-handler/session-description-handler.ts b/src/platform/web/session-description-handler/session-description-handler.ts index b92c3e314..d3476d13e 100644 --- a/src/platform/web/session-description-handler/session-description-handler.ts +++ b/src/platform/web/session-description-handler/session-description-handler.ts @@ -9,21 +9,6 @@ import { SessionDescriptionHandlerConfiguration } from "./session-description-ha import { SessionDescriptionHandlerOptions } from "./session-description-handler-options"; import { PeerConnectionDelegate } from "./peer-connection-delegate"; -// Terminology notes for those not familiar... -// -// A MediaStream is a collection MediaStreamTracks -// - these are used to get cam/mic tracks and attach tracks to audio/video tags -// - a video tag renders both audio and video tracks -// -// A PeerConnection has Transceivers (only ever one in our use case) -// A Transceiver has a Sender and a Receiver -// A Sender has zero or more MediaStreamTracks -// A Receiver has zero or more MediaStreamTracks -// - a transceiver maps to SDP; sender local SDP, receiver to remote -// - in our use case, there's one audio track optionally one video track per sender/receiver -// - in theory, 3-way calling could be implemented by adding a the audio track from a receiver -// on one peer connection to the sender of another peer connection' - type ResolveFunction = () => void; type RejectFunction = (reason: Error) => void; @@ -214,6 +199,7 @@ export class SessionDescriptionHandler implements SessionDescriptionHandlerDefin : options?.iceGatheringTimeout; return this.getLocalMediaStream(options) + .then(() => this.updateDirection(options)) .then(() => this.createDataChannel(options)) .then(() => this.createLocalOfferOrAnswer(options)) .then((sessionDescription) => this.applyModifiers(sessionDescription, modifiers)) @@ -624,6 +610,179 @@ export class SessionDescriptionHandler implements SessionDescriptionHandlerDefin } } + /** + * Depending on the current signaling state and the session hold state, update transceiver direction. + * @param options - Session description handler options. + */ + protected updateDirection(options?: SessionDescriptionHandlerOptions): Promise { + if (this._peerConnection === undefined) { + return Promise.reject(new Error("Peer connection closed.")); + } + + // 4.2.3. setDirection + // + // The setDirection method sets the direction of a transceiver, which + // affects the direction property of the associated "m=" section on + // future calls to createOffer and createAnswer. The permitted values + // for direction are "recvonly", "sendrecv", "sendonly", and "inactive", + // mirroring the identically named direction attributes defined in + // [RFC4566], Section 6. + // + // When creating offers, the transceiver direction is directly reflected + // in the output, even for re-offers. When creating answers, the + // transceiver direction is intersected with the offered direction, as + // explained in Section 5.3 below. + // + // Note that while setDirection sets the direction property of the + // transceiver immediately (Section 4.2.4), this property does not + // immediately affect whether the transceiver's RtpSender will send or + // its RtpReceiver will receive. The direction in effect is represented + // by the currentDirection property, which is only updated when an + // answer is applied. + // + // 4.2.4. direction + // + // The direction property indicates the last value passed into + // setDirection. If setDirection has never been called, it is set to + // the direction the transceiver was initialized with. + // + // 4.2.5. currentDirection + // + // The currentDirection property indicates the last negotiated direction + // for the transceiver's associated "m=" section. More specifically, it + // indicates the direction attribute [RFC3264] of the associated "m=" + // section in the last applied answer (including provisional answers), + // with "send" and "recv" directions reversed if it was a remote answer. + // For example, if the direction attribute for the associated "m=" + // section in a remote answer is "recvonly", currentDirection is set to + // "sendonly". + // + // If an answer that references this transceiver has not yet been + // applied or if the transceiver is stopped, currentDirection is set to + // "null". + // https://tools.ietf.org/html/rfc8829#section-4.2.3 + // + // * A direction attribute, determined by applying the rules regarding + // the offered direction specified in [RFC3264], Section 6.1, and + // then intersecting with the direction of the associated + // RtpTransceiver. For example, in the case where an "m=" section is + // offered as "sendonly" and the local transceiver is set to + // "sendrecv", the result in the answer is a "recvonly" direction. + // https://tools.ietf.org/html/rfc8829#section-5.3.1 + // + // If a stream is offered as sendonly, the corresponding stream MUST be + // marked as recvonly or inactive in the answer. If a media stream is + // listed as recvonly in the offer, the answer MUST be marked as + // sendonly or inactive in the answer. If an offered media stream is + // listed as sendrecv (or if there is no direction attribute at the + // media or session level, in which case the stream is sendrecv by + // default), the corresponding stream in the answer MAY be marked as + // sendonly, recvonly, sendrecv, or inactive. If an offered media + // stream is listed as inactive, it MUST be marked as inactive in the + // answer. + // https://tools.ietf.org/html/rfc3264#section-6.1 + + switch (this._peerConnection.signalingState) { + case "stable": + // if we are stable, assume we are creating a local offer + this.logger.debug("SessionDescriptionHandler.updateDirection - setting offer direction"); + { + // determine the direction to offer given the current direction and hold state + const directionToOffer = (currentDirection: RTCRtpTransceiverDirection): RTCRtpTransceiverDirection => { + switch (currentDirection) { + case "inactive": + return options?.hold ? "inactive" : "recvonly"; + case "recvonly": + return options?.hold ? "inactive" : "recvonly"; + case "sendonly": + return options?.hold ? "sendonly" : "sendrecv"; + case "sendrecv": + return options?.hold ? "sendonly" : "sendrecv"; + case "stopped": + return "stopped"; + default: + throw new Error("Should never happen"); + } + }; + // set the transceiver direction to the offer direction + this._peerConnection.getTransceivers().forEach((transceiver) => { + if (transceiver.direction /* guarding, but should always be true */) { + const offerDirection = directionToOffer(transceiver.direction); + if (transceiver.direction !== offerDirection) { + transceiver.direction = offerDirection; + } + } + }); + } + break; + case "have-remote-offer": + // if we have a remote offer, assume we are creating a local answer + this.logger.debug("SessionDescriptionHandler.updateDirection - setting answer direction"); + + // FIXME: This is not the correct way to determine the answer direction as it is only + // considering first match in the offered SDP and using that to determine the answer direction. + // While that may be fine for our current use cases, it is not a generally correct approach. + { + // determine the offered direction + const offeredDirection = ((): "inactive" | "recvonly" | "sendonly" | "sendrecv" => { + const description = this._peerConnection.remoteDescription; + if (!description) { + throw new Error("Failed to read remote offer"); + } + const searchResult = /a=sendrecv\r\n|a=sendonly\r\n|a=recvonly\r\n|a=inactive\r\n/.exec(description.sdp); + if (searchResult) { + switch (searchResult[0]) { + case "a=inactive\r\n": + return "inactive"; + case "a=recvonly\r\n": + return "recvonly"; + case "a=sendonly\r\n": + return "sendonly"; + case "a=sendrecv\r\n": + return "sendrecv"; + default: + throw new Error("Should never happen"); + } + } + return "sendrecv"; + })(); + + // determine the answer direction based on the offered direction and our hold state + const answerDirection = ((): "inactive" | "recvonly" | "sendonly" | "sendrecv" => { + switch (offeredDirection) { + case "inactive": + return "inactive"; + case "recvonly": + return "sendonly"; + case "sendonly": + return options?.hold ? "inactive" : "recvonly"; + case "sendrecv": + return options?.hold ? "sendonly" : "sendrecv"; + default: + throw new Error("Should never happen"); + } + })(); + + // set the transceiver direction to the answer direction + this._peerConnection.getTransceivers().forEach((transceiver) => { + if (transceiver.direction /* guarding, but should always be true */) { + if (transceiver.direction !== "stopped" && transceiver.direction !== answerDirection) { + transceiver.direction = answerDirection; + } + } + }); + } + break; + case "have-local-offer": + case "have-local-pranswer": + case "have-remote-pranswer": + case "closed": + default: + return Promise.reject(new Error("Invalid signaling state " + this._peerConnection.signalingState)); + } + return Promise.resolve(); + } + /** * Called when ICE gathering completes and resolves any waiting promise. */ From 02a66a52b04d4a5e55083d224136c0899c2b1670 Mon Sep 17 00:00:00 2001 From: John Riordan Date: Sun, 24 Jan 2021 11:54:09 -0500 Subject: [PATCH 07/14] Add SDH tests --- .../web/session-description-handler.spec.ts | 855 ++++++++++++++++++ 1 file changed, 855 insertions(+) diff --git a/test/spec/platform/web/session-description-handler.spec.ts b/test/spec/platform/web/session-description-handler.spec.ts index c5a369cd7..4d585a232 100644 --- a/test/spec/platform/web/session-description-handler.spec.ts +++ b/test/spec/platform/web/session-description-handler.spec.ts @@ -21,6 +21,18 @@ const atLeastOneField = (startsWith: string): jasmine.AsymmetricMatcher }; }; +const exactlyTwoField = (startsWith: string): jasmine.AsymmetricMatcher => { + return { + asymmetricMatch: function (body: string): boolean { + const fields = splitFields(body); + return fields.filter((field) => field.startsWith(startsWith)).length === 2; + }, + jasmineToString: function (): string { + return ""; + } + }; +}; + const exactlyOneField = (startsWith: string): jasmine.AsymmetricMatcher => { return { asymmetricMatch: function (body: string): boolean { @@ -422,6 +434,14 @@ describe("Web SessionDescriptionHandler", () => { expect(offer.body).toEqual(exactlyZeroField("m=video")); }); + it("offer has one a=sendrecv", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyOneField("a=sendrecv")); + }); + it("offer has at least one a=candidate", () => { if (!offer) { fail("Offer undefined"); @@ -441,6 +461,16 @@ describe("Web SessionDescriptionHandler", () => { expect(sdh1.peerConnection?.signalingState).toBe("have-local-offer"); }); + it("transceiver direction should be sendrecv", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("sendrecv"); + }); + it("local media stream state", () => { expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(1); expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); @@ -527,6 +557,14 @@ describe("Web SessionDescriptionHandler", () => { expect(answer.body).toEqual(exactlyZeroField("m=video")); }); + it("answer has one a=sendrecv", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).toEqual(exactlyOneField("a=sendrecv")); + }); + it("answer has at least one a=candidate", () => { if (!answer) { fail("Answer undefined"); @@ -546,6 +584,16 @@ describe("Web SessionDescriptionHandler", () => { expect(sdh2.peerConnection?.signalingState).toBe("stable"); }); + it("transceiver direction should be sendrecv", () => { + const transceivers = sdh2.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("sendrecv"); + }); + it("local media stream state", () => { expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); @@ -569,6 +617,16 @@ describe("Web SessionDescriptionHandler", () => { expect(sdh1.peerConnection?.signalingState).toBe("stable"); }); + it("transceiver direction should be sendrecv", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("sendrecv"); + }); + it("local media stream state", () => { expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); @@ -828,6 +886,754 @@ describe("Web SessionDescriptionHandler", () => { }); }); + describe("sdh1 getDescription - audio hold", () => { + beforeEach(async () => { + resetSpies(); + offer = undefined; + return sdh1 + .getDescription({ + constraints: { + audio: true + }, + hold: true + }) + .then((description) => { + offer = description; + }); + }); + + it("offer was created", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).not.toBe(""); + expect(offer.contentType).toBe("application/sdp"); + }); + + it("offer has one m=audio and zero m=video", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyOneField("m=audio")); + expect(offer.body).toEqual(exactlyZeroField("m=video")); + }); + + it("offer has one a=sendonly", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyOneField("a=sendonly")); + }); + + it("offer has at least one a=candidate", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(atLeastOneField("a=candidate")); + }); + + it("transceiver direction should be sendonly", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("sendonly"); + }); + + it("local media stream state", () => { + expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh1RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh2 setDescription", () => { + beforeEach(async () => { + resetSpies(); + if (!offer) { + throw new Error("Offer undefined."); + } + return sdh2.setDescription(offer.body); + }); + + it("signaling state have-remote-offer", () => { + expect(sdh2.peerConnection?.signalingState).toBe("have-remote-offer"); + }); + + it("local media stream state", () => { + expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh2RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh2 getDescription", () => { + beforeEach(async () => { + resetSpies(); + answer = undefined; + return sdh2.getDescription().then((description) => { + answer = description; + }); + }); + + it("answer was created", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).not.toBe(""); + expect(answer.contentType).toBe("application/sdp"); + }); + + it("answer has one a=recvonly", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).toEqual(exactlyOneField("a=recvonly")); + }); + + it("hasDescription should be true", () => { + expect(answer).not.toBe(undefined); + if (answer) { + expect(sdh2.hasDescription(answer.contentType)).toBe(true); + } + }); + + it("signaling state should be stable", () => { + expect(sdh2.peerConnection?.signalingState).toBe("stable"); + }); + + it("transceiver direction should be recvonly", () => { + const transceivers = sdh2.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("recvonly"); + }); + + it("local media stream state", () => { + expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh2RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh1 setDescription", () => { + beforeEach(async () => { + resetSpies(); + if (!answer) { + throw new Error("Answer undefined."); + } + return sdh1.setDescription(answer.body); + }); + + it("signaling state should be stable", () => { + expect(sdh1.peerConnection?.signalingState).toBe("stable"); + }); + + it("transceiver direction should be sendonly", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("sendonly"); + }); + + it("local media stream state", () => { + expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh1RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh1 getDescription - audio unhold", () => { + beforeEach(async () => { + resetSpies(); + offer = undefined; + return sdh1 + .getDescription({ + constraints: { + audio: true + }, + hold: false + }) + .then((description) => { + offer = description; + }); + }); + + it("offer was created", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).not.toBe(""); + expect(offer.contentType).toBe("application/sdp"); + }); + + it("offer has one m=audio and zero m=video", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyOneField("m=audio")); + expect(offer.body).toEqual(exactlyZeroField("m=video")); + }); + + it("offer has one a=sendrecv", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyOneField("a=sendrecv")); + }); + + it("offer has at least one a=candidate", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(atLeastOneField("a=candidate")); + }); + + it("transceiver direction should be sendrecv", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("sendrecv"); + }); + + it("local media stream state", () => { + expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh1RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh2 setDescription", () => { + beforeEach(async () => { + resetSpies(); + if (!offer) { + throw new Error("Offer undefined."); + } + return sdh2.setDescription(offer.body); + }); + + it("signaling state have-remote-offer", () => { + expect(sdh2.peerConnection?.signalingState).toBe("have-remote-offer"); + }); + + it("local media stream state", () => { + expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh2RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh2 getDescription", () => { + beforeEach(async () => { + resetSpies(); + answer = undefined; + return sdh2.getDescription().then((description) => { + answer = description; + }); + }); + + it("answer was created", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).not.toBe(""); + expect(answer.contentType).toBe("application/sdp"); + }); + + it("answer has one a=sendrecv", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).toEqual(exactlyOneField("a=sendrecv")); + }); + + it("hasDescription should be true", () => { + expect(answer).not.toBe(undefined); + if (answer) { + expect(sdh2.hasDescription(answer.contentType)).toBe(true); + } + }); + + it("signaling state should be stable", () => { + expect(sdh2.peerConnection?.signalingState).toBe("stable"); + }); + + it("transceiver direction should be sendrecv", () => { + const transceivers = sdh2.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("sendrecv"); + }); + + it("local media stream state", () => { + expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh2RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh1 setDescription", () => { + beforeEach(async () => { + resetSpies(); + if (!answer) { + throw new Error("Answer undefined."); + } + return sdh1.setDescription(answer.body); + }); + + it("signaling state should be stable", () => { + expect(sdh1.peerConnection?.signalingState).toBe("stable"); + }); + + it("transceiver direction should be sendrecv", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("sendrecv"); + }); + + it("local media stream state", () => { + expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh1RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + }); + }); + }); + }); + + describe("sdh2 getDescription - audio hold", () => { + beforeEach(async () => { + resetSpies(); + offer = undefined; + return sdh2 + .getDescription({ + constraints: { + audio: true + }, + hold: true + }) + .then((description) => { + offer = description; + }); + }); + + it("offer was created", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).not.toBe(""); + expect(offer.contentType).toBe("application/sdp"); + }); + + it("offer has one m=audio and zero m=video", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyOneField("m=audio")); + expect(offer.body).toEqual(exactlyZeroField("m=video")); + }); + + it("offer has one a=inactive", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyOneField("a=inactive")); + }); + + it("offer has at least one a=candidate", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(atLeastOneField("a=candidate")); + }); + + it("transceiver direction should be inactive", () => { + const transceivers = sdh2.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("inactive"); + }); + + it("local media stream state", () => { + expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh2RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh1 setDescription", () => { + beforeEach(async () => { + resetSpies(); + if (!offer) { + throw new Error("Offer undefined."); + } + return sdh1.setDescription(offer.body); + }); + + it("signaling state have-remote-offer", () => { + expect(sdh1.peerConnection?.signalingState).toBe("have-remote-offer"); + }); + + it("local media stream state", () => { + expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh1RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh1 getDescription", () => { + beforeEach(async () => { + resetSpies(); + answer = undefined; + return sdh1.getDescription().then((description) => { + answer = description; + }); + }); + + it("answer was created", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).not.toBe(""); + expect(answer.contentType).toBe("application/sdp"); + }); + + it("answer has one a=inactive", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).toEqual(exactlyOneField("a=inactive")); + }); + + it("hasDescription should be true", () => { + expect(answer).not.toBe(undefined); + if (answer) { + expect(sdh1.hasDescription(answer.contentType)).toBe(true); + } + }); + + it("signaling state should be stable", () => { + expect(sdh1.peerConnection?.signalingState).toBe("stable"); + }); + + it("transceiver direction should be inactive", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("inactive"); + }); + + it("local media stream state", () => { + expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh1RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh2 setDescription", () => { + beforeEach(async () => { + resetSpies(); + if (!answer) { + throw new Error("Answer undefined."); + } + return sdh2.setDescription(answer.body); + }); + + it("signaling state should be stable", () => { + expect(sdh2.peerConnection?.signalingState).toBe("stable"); + }); + + it("transceiver direction should be inactive", () => { + const transceivers = sdh2.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("inactive"); + }); + + it("local media stream state", () => { + expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh2RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh2 getDescription - audio unhold", () => { + beforeEach(async () => { + resetSpies(); + offer = undefined; + return sdh2 + .getDescription({ + constraints: { + audio: true + }, + hold: false + }) + .then((description) => { + offer = description; + }); + }); + + it("offer was created", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).not.toBe(""); + expect(offer.contentType).toBe("application/sdp"); + }); + + it("offer has one m=audio and zero m=video", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyOneField("m=audio")); + expect(offer.body).toEqual(exactlyZeroField("m=video")); + }); + + it("offer has one a=recvonly", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyOneField("a=recvonly")); + }); + + it("offer has at least one a=candidate", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(atLeastOneField("a=candidate")); + }); + + it("transceiver direction should be recvonly", () => { + const transceivers = sdh2.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("recvonly"); + }); + + it("local media stream state", () => { + expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh2RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh1 setDescription", () => { + beforeEach(async () => { + resetSpies(); + if (!offer) { + throw new Error("Offer undefined."); + } + return sdh1.setDescription(offer.body); + }); + + it("signaling state have-remote-offer", () => { + expect(sdh1.peerConnection?.signalingState).toBe("have-remote-offer"); + }); + + it("local media stream state", () => { + expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh1RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh1 getDescription", () => { + beforeEach(async () => { + resetSpies(); + answer = undefined; + return sdh1.getDescription().then((description) => { + answer = description; + }); + }); + + it("answer was created", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).not.toBe(""); + expect(answer.contentType).toBe("application/sdp"); + }); + + it("answer has one a=sendonly", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).toEqual(exactlyOneField("a=sendonly")); + }); + + it("hasDescription should be true", () => { + expect(answer).not.toBe(undefined); + if (answer) { + expect(sdh1.hasDescription(answer.contentType)).toBe(true); + } + }); + + it("signaling state should be stable", () => { + expect(sdh1.peerConnection?.signalingState).toBe("stable"); + }); + + it("transceiver direction should be sendonly", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("sendonly"); + }); + + it("local media stream state", () => { + expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh1RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh1RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + describe("sdh2 setDescription", () => { + beforeEach(async () => { + resetSpies(); + if (!answer) { + throw new Error("Answer undefined."); + } + return sdh2.setDescription(answer.body); + }); + + it("signaling state should be stable", () => { + expect(sdh2.peerConnection?.signalingState).toBe("stable"); + }); + + it("transceiver direction should be recvonly", () => { + const transceivers = sdh2.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(1); + expect(transceivers[0].direction).toBe("recvonly"); + }); + + it("local media stream state", () => { + expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + + it("remote media stream state", () => { + expect(sdh2RemoteOnAddTrackSpy).toHaveBeenCalledTimes(0); + expect(sdh2RemoteOnRemoveTrackSpy).toHaveBeenCalledTimes(0); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + describe("sdh1 getDescription - video (upgrade)", () => { beforeEach(async () => { resetSpies(); @@ -861,6 +1667,14 @@ describe("Web SessionDescriptionHandler", () => { expect(offer.body).toEqual(exactlyOneField("m=video")); }); + it("offer has two a=sendrecv", () => { + if (!offer) { + fail("Offer undefined"); + return; + } + expect(offer.body).toEqual(exactlyTwoField("a=sendrecv")); + }); + it("offer has at least one a=candidate", () => { if (!offer) { fail("Offer undefined"); @@ -869,6 +1683,17 @@ describe("Web SessionDescriptionHandler", () => { expect(offer.body).toEqual(atLeastOneField("a=candidate")); }); + it("transceiver direction should be sendrecv", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(2); + expect(transceivers[0].direction).toBe("sendrecv"); + expect(transceivers[1].direction).toBe("sendrecv"); + }); + it("local media stream state", () => { expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(2); expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(1); @@ -935,6 +1760,14 @@ describe("Web SessionDescriptionHandler", () => { expect(answer.body).toEqual(exactlyOneField("m=video")); }); + it("answer has two a=sendrecv", () => { + if (!answer) { + fail("Answer undefined"); + return; + } + expect(answer.body).toEqual(exactlyTwoField("a=sendrecv")); + }); + it("answer has at least one a=candidate", () => { if (!answer) { fail("Answer undefined"); @@ -954,6 +1787,17 @@ describe("Web SessionDescriptionHandler", () => { expect(sdh2.peerConnection?.signalingState).toBe("stable"); }); + it("transceiver direction should be sendrecv", () => { + const transceivers = sdh2.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(2); + expect(transceivers[0].direction).toBe("sendrecv"); + expect(transceivers[1].direction).toBe("sendrecv"); + }); + it("local media stream state", () => { expect(sdh2LocalOnAddTrackSpy).toHaveBeenCalledTimes(2); expect(sdh2LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(1); @@ -977,6 +1821,17 @@ describe("Web SessionDescriptionHandler", () => { expect(sdh1.peerConnection?.signalingState).toBe("stable"); }); + it("transceiver direction should be sendrecv", () => { + const transceivers = sdh1.peerConnection?.getTransceivers(); + if (!transceivers) { + fail("Transceivers undefined"); + return; + } + expect(transceivers.length).toBe(2); + expect(transceivers[0].direction).toBe("sendrecv"); + expect(transceivers[1].direction).toBe("sendrecv"); + }); + it("local media stream state", () => { expect(sdh1LocalOnAddTrackSpy).toHaveBeenCalledTimes(0); expect(sdh1LocalOnRemoveTrackSpy).toHaveBeenCalledTimes(0); From 635c167530b78f58090965292a5a8561a01952e9 Mon Sep 17 00:00:00 2001 From: John Riordan Date: Sun, 24 Jan 2021 11:54:30 -0500 Subject: [PATCH 08/14] Update SimpleUser --- src/platform/web/simple-user/simple-user.ts | 75 ++++++++++++++------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/platform/web/simple-user/simple-user.ts b/src/platform/web/simple-user/simple-user.ts index ed1f8ba1e..5d9e5cac2 100644 --- a/src/platform/web/simple-user/simple-user.ts +++ b/src/platform/web/simple-user/simple-user.ts @@ -22,8 +22,7 @@ import { UserAgentState } from "../../../api"; import { Logger } from "../../../core"; -import { holdModifier } from "../modifiers"; -import { SessionDescriptionHandler } from "../session-description-handler"; +import { SessionDescriptionHandler, SessionDescriptionHandlerOptions } from "../session-description-handler"; import { Transport } from "../transport"; import { SimpleUserDelegate } from "./simple-user-delegate"; import { SimpleUserOptions } from "./simple-user-options"; @@ -45,6 +44,7 @@ export class SimpleUser { private connectRequested = false; private logger: Logger; private held = false; + private muted = false; private options: SimpleUserOptions; private registerer: Registerer | undefined = undefined; private registerRequested = false; @@ -535,8 +535,7 @@ export class SimpleUser { * True if sender's media track is disabled. */ public isMuted(): boolean { - const track = this.localAudioTrack || this.localVideoTrack; - return track ? !track.enabled : false; + return this.muted; } /** @@ -699,6 +698,29 @@ export class SimpleUser { } } + /** Helper function to enable/disable media tracks. */ + private enableReceiverTracks(enable: boolean): void { + if (!this.session) { + throw new Error("Session does not exist."); + } + + const sessionDescriptionHandler = this.session.sessionDescriptionHandler; + if (!(sessionDescriptionHandler instanceof SessionDescriptionHandler)) { + throw new Error("Session's session description handler not instance of SessionDescriptionHandler."); + } + + const peerConnection = sessionDescriptionHandler.peerConnection; + if (!peerConnection) { + throw new Error("Peer connection closed."); + } + + peerConnection.getReceivers().forEach((receiver) => { + if (receiver.track) { + receiver.track.enabled = enable; + } + }); + } + /** Helper function to enable/disable media tracks. */ private enableSenderTracks(enable: boolean): void { if (!this.session) { @@ -896,12 +918,16 @@ export class SimpleUser { requestDelegate: { onAccept: (): void => { this.held = hold; + this.enableReceiverTracks(!this.held); + this.enableSenderTracks(!this.held && !this.muted); if (this.delegate && this.delegate.onCallHold) { this.delegate.onCallHold(this.held); } }, onReject: (): void => { this.logger.warn(`[${this.id}] re-invite request was rejected`); + this.enableReceiverTracks(!this.held); + this.enableSenderTracks(!this.held && !this.muted); if (this.delegate && this.delegate.onCallHold) { this.delegate.onCallHold(this.held); } @@ -909,32 +935,33 @@ export class SimpleUser { } }; - // Session properties used to pass modifiers to the SessionDescriptionHandler: + // Session properties used to pass options to the SessionDescriptionHandler: // - // 1) Session.sessionDescriptionHandlerModifiers - // - used in all cases when handling the initial INVITE transaction as either UAC or UAS - // - may be set directly at anytime - // - may optionally be set via constructor option - // - may optionally be set via options passed to Inviter.invite() or Invitation.accept() + // 1) Session.sessionDescriptionHandlerOptions + // SDH options for the initial INVITE transaction. + // - Used in all cases when handling the initial INVITE transaction as either UAC or UAS. + // - May be set directly at anytime. + // - May optionally be set via constructor option. + // - May optionally be set via options passed to Inviter.invite() or Invitation.accept(). // - // 2) Session.sessionDescriptionHandlerModifiersReInvite - // - used in all cases when handling a re-INVITE transaction as either UAC or UAS - // - may be set directly at anytime - // - may optionally be set via constructor option - // - may optionally be set via options passed to Session.invite() + // 2) Session.sessionDescriptionHandlerOptionsReInvite + // SDH options for re-INVITE transactions. + // - Used in all cases when handling a re-INVITE transaction as either UAC or UAS. + // - May be set directly at anytime. + // - May optionally be set via constructor option. + // - May optionally be set via options passed to Session.invite(). - // Set the session's SDH re-INVITE modifiers to produce the appropriate SDP offer to place call on hold - session.sessionDescriptionHandlerModifiersReInvite = hold ? [holdModifier] : []; + const sessionDescriptionHandlerOptions = session.sessionDescriptionHandlerOptionsReInvite as SessionDescriptionHandlerOptions; + sessionDescriptionHandlerOptions.hold = hold; + session.sessionDescriptionHandlerOptionsReInvite = sessionDescriptionHandlerOptions; // Send re-INVITE return this.session .invite(options) .then(() => { - // Reset the session's SDH re-INVITE modifiers. - // Note that if the modifiers are not reset, they will be applied - // to the SDP answer as well (which we do not want in this case). - session.sessionDescriptionHandlerModifiersReInvite = []; - this.enableSenderTracks(!hold); // mute/unmute + // preemptively enable/disable tracks + this.enableReceiverTracks(!hold); + this.enableSenderTracks(!hold && !this.muted); }) .catch((error: Error) => { if (error instanceof RequestPendingError) { @@ -959,7 +986,9 @@ export class SimpleUser { return; } - this.enableSenderTracks(!mute); + this.muted = mute; + + this.enableSenderTracks(!this.held && !this.muted); } /** Helper function to attach local media to html elements. */ From ace1818c41f9898cec9367b0690c44b897b346a0 Mon Sep 17 00:00:00 2001 From: John Riordan Date: Sun, 24 Jan 2021 11:54:52 -0500 Subject: [PATCH 09/14] Update demos - add hold/mute to demo 2 --- demo/demo-1.ts | 15 ++++-- demo/demo-2.css | 1 + demo/demo-2.html | 138 ++++++++++++++++++++++++++--------------------- demo/demo-2.ts | 119 +++++++++++++++++++++++++++++++++++++--- demo/demo-3.ts | 1 + 5 files changed, 201 insertions(+), 73 deletions(-) diff --git a/demo/demo-1.ts b/demo/demo-1.ts index a45224687..abebca356 100644 --- a/demo/demo-1.ts +++ b/demo/demo-1.ts @@ -65,6 +65,7 @@ const simpleUserOptions: SimpleUserOptions = { } }, userAgentOptions: { + // logLevel: "debug", displayName } }; @@ -98,11 +99,15 @@ connectButton.addEventListener("click", () => { callButton.addEventListener("click", () => { callButton.disabled = true; hangupButton.disabled = true; - simpleUser.call(target).catch((error: Error) => { - console.error(`[${simpleUser.id}] failed to place call`); - console.error(error); - alert("Failed to place call.\n" + error); - }); + simpleUser + .call(target, { + inviteWithoutSdp: false + }) + .catch((error: Error) => { + console.error(`[${simpleUser.id}] failed to place call`); + console.error(error); + alert("Failed to place call.\n" + error); + }); }); // Add click listener to hangup button diff --git a/demo/demo-2.css b/demo/demo-2.css index efd8627a9..3446f59f1 100644 --- a/demo/demo-2.css +++ b/demo/demo-2.css @@ -30,4 +30,5 @@ button { width: 40px; top: 150px; left: 10px; + transform: scaleX(-1); } diff --git a/demo/demo-2.html b/demo/demo-2.html index 70ee1e04d..181e41fde 100644 --- a/demo/demo-2.html +++ b/demo/demo-2.html @@ -1,80 +1,94 @@ + + + SIP.js Demo 2 + + - - - SIP.js Demo 2 - - + + < Index - - < Index +

Demo: Video Call - Between Two Users

-

Demo: Video Call - Between Two Users

- -
- When this page was loaded, a SimpleUser was created for two users - Alice & Bob -
    -
  1. Connect with SIP WebSocket Server
  2. -
  3. Register user to receive calls
  4. -
  5. Initiate a video session
  6. -
  7. End the video session
  8. -
  9. Unregister
  10. -
  11. Disconnect
  12. -
-
+
+ When this page was loaded, a SimpleUser was created for two users - Alice & Bob +
    +
  1. Connect with SIP WebSocket Server
  2. +
  3. Register user to receive calls
  4. +
  5. Initiate a video session
  6. +
  7. End the video session
  8. +
  9. Unregister
  10. +
  11. Disconnect
  12. +
+
-
-
-

Alice

-
- -
-