Refactor Signal app routing
This commit is contained in:
parent
86e6c2499c
commit
3ef0d221d1
28 changed files with 1347 additions and 1044 deletions
|
@ -1,27 +0,0 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { size } from '../../util/iterables';
|
||||
|
||||
import { getProvisioningUrl } from '../../util/getProvisioningUrl';
|
||||
|
||||
// It'd be nice to run these tests in the renderer, too, but [Chromium's `URL` doesn't
|
||||
// handle `sgnl:` links correctly][0].
|
||||
//
|
||||
// [0]: https://bugs.chromium.org/p/chromium/issues/detail?id=869291
|
||||
describe('getProvisioningUrl', () => {
|
||||
it('returns a URL with a UUID and public key', () => {
|
||||
const uuid = 'a08bf1fd-1799-427f-a551-70af747e3956';
|
||||
const publicKey = new Uint8Array([9, 8, 7, 6, 5, 4, 3]);
|
||||
|
||||
const result = getProvisioningUrl(uuid, publicKey);
|
||||
const resultUrl = new URL(result);
|
||||
|
||||
assert.strictEqual(resultUrl.protocol, 'sgnl:');
|
||||
assert.strictEqual(resultUrl.host, 'linkdevice');
|
||||
assert.strictEqual(size(resultUrl.searchParams.entries()), 2);
|
||||
assert.strictEqual(resultUrl.searchParams.get('uuid'), uuid);
|
||||
assert.strictEqual(resultUrl.searchParams.get('pub_key'), 'CQgHBgUEAw==');
|
||||
});
|
||||
});
|
|
@ -1,530 +0,0 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import Sinon from 'sinon';
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
|
||||
import {
|
||||
isSgnlHref,
|
||||
isCaptchaHref,
|
||||
isSignalHttpsLink,
|
||||
parseSgnlHref,
|
||||
parseCaptchaHref,
|
||||
parseE164FromSignalDotMeHash,
|
||||
parseUsernameBase64FromSignalDotMeHash,
|
||||
parseSignalHttpsLink,
|
||||
generateUsernameLink,
|
||||
rewriteSignalHrefsIfNecessary,
|
||||
} from '../../util/sgnlHref';
|
||||
|
||||
function shouldNeverBeCalled() {
|
||||
assert.fail('This should never be called');
|
||||
}
|
||||
|
||||
const explodingLogger: LoggerType = {
|
||||
fatal: shouldNeverBeCalled,
|
||||
error: shouldNeverBeCalled,
|
||||
warn: shouldNeverBeCalled,
|
||||
info: shouldNeverBeCalled,
|
||||
debug: shouldNeverBeCalled,
|
||||
trace: shouldNeverBeCalled,
|
||||
};
|
||||
|
||||
describe('sgnlHref', () => {
|
||||
[
|
||||
{ protocol: 'sgnl', check: isSgnlHref, name: 'isSgnlHref' },
|
||||
{ protocol: 'signalcaptcha', check: isCaptchaHref, name: 'isCaptchaHref' },
|
||||
].forEach(({ protocol, check, name }) => {
|
||||
describe(name, () => {
|
||||
it('returns false for non-strings', () => {
|
||||
const logger = {
|
||||
...explodingLogger,
|
||||
warn: Sinon.spy(),
|
||||
};
|
||||
|
||||
const castToString = (value: unknown): string => value as string;
|
||||
|
||||
assert.isFalse(check(castToString(undefined), logger));
|
||||
assert.isFalse(check(castToString(null), logger));
|
||||
assert.isFalse(check(castToString(123), logger));
|
||||
|
||||
Sinon.assert.calledThrice(logger.warn);
|
||||
});
|
||||
|
||||
it('returns false for invalid URLs', () => {
|
||||
assert.isFalse(check('', explodingLogger));
|
||||
assert.isFalse(check(protocol, explodingLogger));
|
||||
assert.isFalse(check(`${protocol}://::`, explodingLogger));
|
||||
});
|
||||
|
||||
it(`returns false if the protocol is not "${protocol}:"`, () => {
|
||||
assert.isFalse(check('https://example', explodingLogger));
|
||||
assert.isFalse(
|
||||
check('https://signal.art/addstickers/?pack_id=abc', explodingLogger)
|
||||
);
|
||||
assert.isFalse(check('signal://example', explodingLogger));
|
||||
});
|
||||
|
||||
it(`returns true if the protocol is "${protocol}:"`, () => {
|
||||
assert.isTrue(check(`${protocol}://`, explodingLogger));
|
||||
assert.isTrue(check(`${protocol}://example`, explodingLogger));
|
||||
assert.isTrue(check(`${protocol}://example.com`, explodingLogger));
|
||||
assert.isTrue(
|
||||
check(`${protocol.toUpperCase()}://example`, explodingLogger)
|
||||
);
|
||||
assert.isTrue(check(`${protocol}://example?foo=bar`, explodingLogger));
|
||||
assert.isTrue(check(`${protocol}://example/`, explodingLogger));
|
||||
assert.isTrue(check(`${protocol}://example#`, explodingLogger));
|
||||
|
||||
assert.isTrue(check(`${protocol}:foo`, explodingLogger));
|
||||
|
||||
assert.isTrue(
|
||||
check(`${protocol}://user:pass@example`, explodingLogger)
|
||||
);
|
||||
assert.isTrue(check(`${protocol}://example.com:1234`, explodingLogger));
|
||||
assert.isTrue(
|
||||
check(`${protocol}://example.com/extra/path/data`, explodingLogger)
|
||||
);
|
||||
assert.isTrue(
|
||||
check(`${protocol}://example/?foo=bar#hash`, explodingLogger)
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts URL objects', () => {
|
||||
const invalid = new URL('https://example.com');
|
||||
assert.isFalse(check(invalid, explodingLogger));
|
||||
const valid = new URL(`${protocol}://example`);
|
||||
assert.isTrue(check(valid, explodingLogger));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSignalHttpsLink', () => {
|
||||
it('returns false for non-strings', () => {
|
||||
const logger = {
|
||||
...explodingLogger,
|
||||
warn: Sinon.spy(),
|
||||
};
|
||||
|
||||
const castToString = (value: unknown): string => value as string;
|
||||
|
||||
assert.isFalse(isSignalHttpsLink(castToString(undefined), logger));
|
||||
assert.isFalse(isSignalHttpsLink(castToString(null), logger));
|
||||
assert.isFalse(isSignalHttpsLink(castToString(123), logger));
|
||||
|
||||
Sinon.assert.calledThrice(logger.warn);
|
||||
});
|
||||
|
||||
it('returns false for invalid URLs', () => {
|
||||
assert.isFalse(isSignalHttpsLink('', explodingLogger));
|
||||
assert.isFalse(isSignalHttpsLink('https', explodingLogger));
|
||||
assert.isFalse(isSignalHttpsLink('https://::', explodingLogger));
|
||||
});
|
||||
|
||||
it('returns false if the protocol is not "https:"', () => {
|
||||
assert.isFalse(
|
||||
isSignalHttpsLink(
|
||||
'sgnl://signal.art/#pack_id=234234&pack_key=342342',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
assert.isFalse(
|
||||
isSignalHttpsLink(
|
||||
'sgnl://signal.art/addstickers/#pack_id=234234&pack_key=342342',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
assert.isFalse(
|
||||
isSignalHttpsLink(
|
||||
'signal://signal.group/#AD234Dq342dSDJWE',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false if missing path/hash/query', () => {
|
||||
assert.isFalse(
|
||||
isSignalHttpsLink('https://signal.group/', explodingLogger)
|
||||
);
|
||||
assert.isFalse(isSignalHttpsLink('https://signal.art/', explodingLogger));
|
||||
assert.isFalse(isSignalHttpsLink('https://signal.me/', explodingLogger));
|
||||
});
|
||||
|
||||
it('returns false if the URL is not a valid Signal URL', () => {
|
||||
assert.isFalse(isSignalHttpsLink('https://signal.org', explodingLogger));
|
||||
assert.isFalse(isSignalHttpsLink('https://example.com', explodingLogger));
|
||||
});
|
||||
|
||||
it('returns true if the protocol is "https:"', () => {
|
||||
assert.isTrue(
|
||||
isSignalHttpsLink(
|
||||
'https://signal.group/#AD234Dq342dSDJWE',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
assert.isTrue(
|
||||
isSignalHttpsLink(
|
||||
'https://signal.group/AD234Dq342dSDJWE',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
assert.isTrue(
|
||||
isSignalHttpsLink(
|
||||
'https://signal.group/?AD234Dq342dSDJWE',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
assert.isTrue(
|
||||
isSignalHttpsLink(
|
||||
'https://signal.art/addstickers/#pack_id=234234&pack_key=342342',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
assert.isTrue(
|
||||
isSignalHttpsLink(
|
||||
'HTTPS://signal.art/addstickers/#pack_id=234234&pack_key=342342',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
assert.isTrue(
|
||||
isSignalHttpsLink('https://signal.me/#p/+32423432', explodingLogger)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false if username or password are set', () => {
|
||||
assert.isFalse(
|
||||
isSignalHttpsLink('https://user:password@signal.group', explodingLogger)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false if port is set', () => {
|
||||
assert.isFalse(
|
||||
isSignalHttpsLink(
|
||||
'https://signal.group:1234/#AD234Dq342dSDJWE',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts URL objects', () => {
|
||||
const invalid = new URL('sgnl://example.com');
|
||||
assert.isFalse(isSignalHttpsLink(invalid, explodingLogger));
|
||||
const valid = new URL('https://signal.art/#AD234Dq342dSDJWE');
|
||||
assert.isTrue(isSignalHttpsLink(valid, explodingLogger));
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseSgnlHref', () => {
|
||||
it('returns a null command for invalid URLs', () => {
|
||||
['', 'sgnl', 'https://example/?foo=bar'].forEach(href => {
|
||||
assert.deepEqual(parseSgnlHref(href, explodingLogger), {
|
||||
command: null,
|
||||
args: new Map<never, never>(),
|
||||
hash: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('parses the command for URLs with no arguments', () => {
|
||||
[
|
||||
'sgnl://foo',
|
||||
'sgnl://foo/',
|
||||
'sgnl://foo?',
|
||||
'SGNL://foo?',
|
||||
'sgnl://user:pass@foo',
|
||||
'sgnl://foo/path/data',
|
||||
].forEach(href => {
|
||||
assert.deepEqual(parseSgnlHref(href, explodingLogger), {
|
||||
command: 'foo',
|
||||
args: new Map<string, string>(),
|
||||
hash: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("parses a command's arguments", () => {
|
||||
assert.deepEqual(
|
||||
parseSgnlHref(
|
||||
'sgnl://Foo?bar=baz&qux=Quux&num=123&empty=&encoded=hello%20world',
|
||||
explodingLogger
|
||||
),
|
||||
{
|
||||
command: 'Foo',
|
||||
args: new Map([
|
||||
['bar', 'baz'],
|
||||
['qux', 'Quux'],
|
||||
['num', '123'],
|
||||
['empty', ''],
|
||||
['encoded', 'hello world'],
|
||||
]),
|
||||
hash: undefined,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('treats the port as part of the command', () => {
|
||||
assert.propertyVal(
|
||||
parseSgnlHref('sgnl://foo:1234', explodingLogger),
|
||||
'command',
|
||||
'foo:1234'
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores duplicate query parameters', () => {
|
||||
assert.deepPropertyVal(
|
||||
parseSgnlHref('sgnl://x?foo=bar&foo=totally-ignored', explodingLogger),
|
||||
'args',
|
||||
new Map([['foo', 'bar']])
|
||||
);
|
||||
});
|
||||
|
||||
it('includes hash', () => {
|
||||
[
|
||||
'sgnl://foo?bar=baz#somehash',
|
||||
'sgnl://user:pass@foo?bar=baz#somehash',
|
||||
].forEach(href => {
|
||||
assert.deepEqual(parseSgnlHref(href, explodingLogger), {
|
||||
command: 'foo',
|
||||
args: new Map([['bar', 'baz']]),
|
||||
hash: 'somehash',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores other parts of the URL', () => {
|
||||
[
|
||||
'sgnl://foo?bar=baz',
|
||||
'sgnl://foo/?bar=baz',
|
||||
'sgnl://foo/lots/of/path?bar=baz',
|
||||
'sgnl://user:pass@foo?bar=baz',
|
||||
].forEach(href => {
|
||||
assert.deepEqual(parseSgnlHref(href, explodingLogger), {
|
||||
command: 'foo',
|
||||
args: new Map([['bar', 'baz']]),
|
||||
hash: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't do anything fancy with arrays or objects in the query string", () => {
|
||||
// The `qs` module does things like this, which we don't want.
|
||||
assert.deepPropertyVal(
|
||||
parseSgnlHref('sgnl://x?foo[]=bar&foo[]=baz', explodingLogger),
|
||||
'args',
|
||||
new Map([['foo[]', 'bar']])
|
||||
);
|
||||
assert.deepPropertyVal(
|
||||
parseSgnlHref('sgnl://x?foo[bar][baz]=foobarbaz', explodingLogger),
|
||||
'args',
|
||||
new Map([['foo[bar][baz]', 'foobarbaz']])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseCaptchaHref', () => {
|
||||
it('throws on invalid URLs', () => {
|
||||
['', 'sgnl', 'https://example/?foo=bar'].forEach(href => {
|
||||
assert.throws(
|
||||
() => parseCaptchaHref(href, explodingLogger),
|
||||
'Not a captcha href'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('parses the command for URLs with no arguments', () => {
|
||||
[
|
||||
'signalcaptcha://foo',
|
||||
'signalcaptcha://foo?x=y',
|
||||
'signalcaptcha://a:b@foo?x=y',
|
||||
'signalcaptcha://foo#hash',
|
||||
'signalcaptcha://foo/',
|
||||
].forEach(href => {
|
||||
assert.deepEqual(parseCaptchaHref(href, explodingLogger), {
|
||||
captcha: 'foo',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseE164FromSignalDotMeHash', () => {
|
||||
it('returns undefined for invalid inputs', () => {
|
||||
[
|
||||
'',
|
||||
' p/+18885551234',
|
||||
'p/+18885551234 ',
|
||||
'x/+18885551234',
|
||||
'p/+notanumber',
|
||||
'p/7c7e87a0-3b74-4efd-9a00-6eb8b1dd5be8',
|
||||
'p/+08885551234',
|
||||
'p/18885551234',
|
||||
].forEach(hash => {
|
||||
assert.isUndefined(parseE164FromSignalDotMeHash(hash));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the E164 for valid inputs', () => {
|
||||
assert.strictEqual(
|
||||
parseE164FromSignalDotMeHash('p/+18885551234'),
|
||||
'+18885551234'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parseE164FromSignalDotMeHash('p/+441632960104'),
|
||||
'+441632960104'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseUsernameBase64FromSignalDotMeHash', () => {
|
||||
it('returns undefined for invalid inputs', () => {
|
||||
['', ' eu/+18885551234', 'z/18885551234'].forEach(hash => {
|
||||
assert.isUndefined(parseUsernameBase64FromSignalDotMeHash(hash));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the username for valid inputs', () => {
|
||||
assert.strictEqual(
|
||||
parseUsernameBase64FromSignalDotMeHash(
|
||||
'eu/E7wk7FTMz_UYjLAsswHpDsGku8CW7yTmlBh8gtd4yqjQlqcbh09F25x0aQT4_Efe'
|
||||
),
|
||||
'E7wk7FTMz/UYjLAsswHpDsGku8CW7yTmlBh8gtd4yqjQlqcbh09F25x0aQT4/Efe'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateUsernameLink', () => {
|
||||
it('generates regular link', () => {
|
||||
assert.strictEqual(
|
||||
generateUsernameLink(
|
||||
'E7wk7FTMz/UYjLAsswHpDsGku8CW7yTmlBh8gtd4yqjQlqcbh09F25x0aQT4/Efe'
|
||||
),
|
||||
'https://signal.me/#eu/E7wk7FTMz_UYjLAsswHpDsGku8CW7yTmlBh8gtd4yqjQlqcbh09F25x0aQT4_Efe'
|
||||
);
|
||||
});
|
||||
|
||||
it('generates short link', () => {
|
||||
assert.strictEqual(
|
||||
generateUsernameLink(
|
||||
'E7wk7FTMz/UYjLAsswHpDsGku8CW7yTmlBh8gtd4yqjQlqcbh09F25x0aQT4/Efe',
|
||||
{ short: true }
|
||||
),
|
||||
'signal.me/#eu/E7wk7FTMz_UYjLAsswHpDsGku8CW7yTmlBh8gtd4yqjQlqcbh09F25x0aQT4_Efe'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseSignalHttpsLink', () => {
|
||||
it('returns a null command for invalid URLs', () => {
|
||||
['', 'https', 'https://example/?foo=bar'].forEach(href => {
|
||||
assert.deepEqual(parseSignalHttpsLink(href, explodingLogger), {
|
||||
command: null,
|
||||
args: new Map<never, never>(),
|
||||
hash: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles signal.art links', () => {
|
||||
assert.deepEqual(
|
||||
parseSignalHttpsLink(
|
||||
'https://signal.art/addstickers/#pack_id=baz&pack_key=Quux&num=123&empty=&encoded=hello%20world',
|
||||
explodingLogger
|
||||
),
|
||||
{
|
||||
command: 'addstickers',
|
||||
args: new Map([
|
||||
['pack_id', 'baz'],
|
||||
['pack_key', 'Quux'],
|
||||
['num', '123'],
|
||||
['empty', ''],
|
||||
['encoded', 'hello world'],
|
||||
]),
|
||||
hash: 'pack_id=baz&pack_key=Quux&num=123&empty=&encoded=hello%20world',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('handles signal.group links', () => {
|
||||
assert.deepEqual(
|
||||
parseSignalHttpsLink('https://signal.group/#data', explodingLogger),
|
||||
{
|
||||
command: 'signal.group',
|
||||
args: new Map<never, never>(),
|
||||
hash: 'data',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('handles signal.me links', () => {
|
||||
assert.deepEqual(
|
||||
parseSignalHttpsLink(
|
||||
'https://signal.me/#p/+18885551234',
|
||||
explodingLogger
|
||||
),
|
||||
{
|
||||
command: 'signal.me',
|
||||
args: new Map<never, never>(),
|
||||
hash: 'p/+18885551234',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rewriteSignalHrefsIfNecessary', () => {
|
||||
it('rewrites http://signal.group hrefs, making them use HTTPS', () => {
|
||||
assert.strictEqual(
|
||||
rewriteSignalHrefsIfNecessary('http://signal.group/#abc123'),
|
||||
'https://signal.group/#abc123'
|
||||
);
|
||||
});
|
||||
|
||||
it('rewrites http://signal.art hrefs, making them use HTTPS', () => {
|
||||
assert.strictEqual(
|
||||
rewriteSignalHrefsIfNecessary(
|
||||
'http://signal.art/addstickers/#pack_id=abc123'
|
||||
),
|
||||
'https://signal.art/addstickers/#pack_id=abc123'
|
||||
);
|
||||
});
|
||||
|
||||
it('rewrites http://signal.me hrefs, making them use HTTPS', () => {
|
||||
assert.strictEqual(
|
||||
rewriteSignalHrefsIfNecessary('http://signal.me/#p/+18885551234'),
|
||||
'https://signal.me/#p/+18885551234'
|
||||
);
|
||||
});
|
||||
|
||||
it('removes auth if present', () => {
|
||||
assert.strictEqual(
|
||||
rewriteSignalHrefsIfNecessary(
|
||||
'http://user:pass@signal.group/ab?c=d#ef'
|
||||
),
|
||||
'https://signal.group/ab?c=d#ef'
|
||||
);
|
||||
assert.strictEqual(
|
||||
rewriteSignalHrefsIfNecessary(
|
||||
'https://user:pass@signal.group/ab?c=d#ef'
|
||||
),
|
||||
'https://signal.group/ab?c=d#ef'
|
||||
);
|
||||
});
|
||||
|
||||
it('does nothing to other hrefs', () => {
|
||||
[
|
||||
// Normal URLs
|
||||
'http://example.com',
|
||||
// Already HTTPS
|
||||
'https://signal.art/addstickers/#pack_id=abc123',
|
||||
// Different port
|
||||
'http://signal.group:1234/abc?d=e#fg',
|
||||
// Different subdomain
|
||||
'http://subdomain.signal.group/#abcdef',
|
||||
// Different protocol
|
||||
'ftp://signal.group/#abc123',
|
||||
'ftp://user:pass@signal.group/#abc123',
|
||||
].forEach(href => {
|
||||
assert.strictEqual(rewriteSignalHrefsIfNecessary(href), href);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
205
ts/test-node/util/signalRoutes_test.ts
Normal file
205
ts/test-node/util/signalRoutes_test.ts
Normal file
|
@ -0,0 +1,205 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { assert } from 'chai';
|
||||
import type { ParsedSignalRoute } from '../../util/signalRoutes';
|
||||
import {
|
||||
isSignalRoute,
|
||||
parseSignalRoute,
|
||||
toSignalRouteAppUrl,
|
||||
toSignalRouteUrl,
|
||||
toSignalRouteWebUrl,
|
||||
} from '../../util/signalRoutes';
|
||||
|
||||
describe('signalRoutes', () => {
|
||||
type CheckConfig = {
|
||||
hasAppUrl: boolean;
|
||||
hasWebUrl: boolean;
|
||||
isRoute: boolean;
|
||||
};
|
||||
|
||||
function createCheck(options: Partial<CheckConfig> = {}) {
|
||||
const config: CheckConfig = {
|
||||
hasAppUrl: true,
|
||||
hasWebUrl: true,
|
||||
isRoute: true,
|
||||
...options,
|
||||
};
|
||||
// Different than `isRoute` because of normalization
|
||||
const hasRouteUrl = config.hasAppUrl || config.hasWebUrl;
|
||||
return function check(input: string, expected: ParsedSignalRoute | null) {
|
||||
const url = new URL(input);
|
||||
assert.deepEqual(parseSignalRoute(url), expected);
|
||||
assert.deepEqual(isSignalRoute(url), config.isRoute);
|
||||
assert.deepEqual(toSignalRouteUrl(url) != null, hasRouteUrl);
|
||||
assert.deepEqual(toSignalRouteAppUrl(url) != null, config.hasAppUrl);
|
||||
assert.deepEqual(toSignalRouteWebUrl(url) != null, config.hasWebUrl);
|
||||
};
|
||||
}
|
||||
|
||||
it('nonsense', () => {
|
||||
const check = createCheck({
|
||||
isRoute: false,
|
||||
hasAppUrl: false,
|
||||
hasWebUrl: false,
|
||||
});
|
||||
// Charles Entertainment Cheese, what are you doing here?
|
||||
check('https://www.chuckecheese.com/#p/+1234567890', null);
|
||||
// Non-route signal urls
|
||||
check('https://signal.me', null);
|
||||
check('sgnl://signal.me/#p', null);
|
||||
check('sgnl://signal.me/#p/', null);
|
||||
check('sgnl://signal.me/p/+1234567890', null);
|
||||
check('https://signal.me/?p/+1234567890', null);
|
||||
});
|
||||
|
||||
it('normalize', () => {
|
||||
const check = createCheck({ isRoute: false, hasAppUrl: true });
|
||||
check('http://username:password@signal.me:8888/#p/+1234567890', null);
|
||||
});
|
||||
|
||||
it('contactByPhoneNumber', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'contactByPhoneNumber',
|
||||
args: { phoneNumber: '+1234567890' },
|
||||
};
|
||||
const check = createCheck();
|
||||
check('https://signal.me/#p/+1234567890', result);
|
||||
check('https://signal.me#p/+1234567890', result);
|
||||
check('sgnl://signal.me/#p/+1234567890', result);
|
||||
check('sgnl://signal.me#p/+1234567890', result);
|
||||
});
|
||||
|
||||
it('contactByEncryptedUsername', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'contactByEncryptedUsername',
|
||||
args: { encryptedUsername: 'foobar' },
|
||||
};
|
||||
const check = createCheck();
|
||||
check('https://signal.me/#eu/foobar', result);
|
||||
check('https://signal.me#eu/foobar', result);
|
||||
check('sgnl://signal.me/#eu/foobar', result);
|
||||
check('sgnl://signal.me#eu/foobar', result);
|
||||
});
|
||||
|
||||
it('groupInvites', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'groupInvites',
|
||||
args: { inviteCode: 'foobar' },
|
||||
};
|
||||
const check = createCheck();
|
||||
check('https://signal.group/#foobar', result);
|
||||
check('https://signal.group#foobar', result);
|
||||
check('sgnl://signal.group/#foobar', result);
|
||||
check('sgnl://signal.group#foobar', result);
|
||||
check('sgnl://joingroup/#foobar', result);
|
||||
check('sgnl://joingroup#foobar', result);
|
||||
});
|
||||
|
||||
it('linkDevice', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'linkDevice',
|
||||
args: { uuid: 'foo', pubKey: 'bar' },
|
||||
};
|
||||
const check = createCheck({ hasWebUrl: false });
|
||||
check('sgnl://linkdevice/?uuid=foo&pub_key=bar', result);
|
||||
check('sgnl://linkdevice?uuid=foo&pub_key=bar', result);
|
||||
});
|
||||
|
||||
it('captcha', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'captcha',
|
||||
args: { captchaId: 'foobar' },
|
||||
};
|
||||
const check = createCheck({ hasWebUrl: false });
|
||||
check('signalcaptcha://foobar', result);
|
||||
});
|
||||
|
||||
it('linkCall', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'linkCall',
|
||||
args: { key: 'foobar' },
|
||||
};
|
||||
const check = createCheck();
|
||||
check('https://signal.link/call/#key=foobar', result);
|
||||
check('https://signal.link/call#key=foobar', result);
|
||||
check('sgnl://signal.link/call/#key=foobar', result);
|
||||
check('sgnl://signal.link/call#key=foobar', result);
|
||||
});
|
||||
|
||||
it('artAuth', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'artAuth',
|
||||
args: { token: 'foo', pubKey: 'bar' },
|
||||
};
|
||||
const check = createCheck({ hasWebUrl: false });
|
||||
check('sgnl://art-auth/?token=foo&pub_key=bar', result);
|
||||
check('sgnl://art-auth?token=foo&pub_key=bar', result);
|
||||
});
|
||||
|
||||
it('artAddStickers', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'artAddStickers',
|
||||
args: { packId: 'foo', packKey: 'bar' },
|
||||
};
|
||||
const check = createCheck();
|
||||
check('https://signal.art/addstickers/#pack_id=foo&pack_key=bar', result);
|
||||
check('https://signal.art/addstickers#pack_id=foo&pack_key=bar', result);
|
||||
check('sgnl://addstickers/?pack_id=foo&pack_key=bar', result);
|
||||
check('sgnl://addstickers?pack_id=foo&pack_key=bar', result);
|
||||
});
|
||||
|
||||
it('showConversation', () => {
|
||||
const check = createCheck({ isRoute: true, hasWebUrl: false });
|
||||
const args1 = 'conversationId=abc';
|
||||
const args2 = 'conversationId=abc&messageId=def';
|
||||
const args3 = 'conversationId=abc&messageId=def&storyId=ghi';
|
||||
const result1: ParsedSignalRoute = {
|
||||
key: 'showConversation',
|
||||
args: { conversationId: 'abc', messageId: null, storyId: null },
|
||||
};
|
||||
const result2: ParsedSignalRoute = {
|
||||
key: 'showConversation',
|
||||
args: { conversationId: 'abc', messageId: 'def', storyId: null },
|
||||
};
|
||||
const result3: ParsedSignalRoute = {
|
||||
key: 'showConversation',
|
||||
args: { conversationId: 'abc', messageId: 'def', storyId: 'ghi' },
|
||||
};
|
||||
check(`sgnl://show-conversation/?${args1}`, result1);
|
||||
check(`sgnl://show-conversation?${args1}`, result1);
|
||||
check(`sgnl://show-conversation/?${args2}`, result2);
|
||||
check(`sgnl://show-conversation?${args2}`, result2);
|
||||
check(`sgnl://show-conversation/?${args3}`, result3);
|
||||
check(`sgnl://show-conversation?${args3}`, result3);
|
||||
});
|
||||
|
||||
it('startCallLobby', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'startCallLobby',
|
||||
args: { conversationId: 'abc' },
|
||||
};
|
||||
const check = createCheck({ isRoute: true, hasWebUrl: false });
|
||||
check('sgnl://start-call-lobby/?conversationId=abc', result);
|
||||
check('sgnl://start-call-lobby?conversationId=abc', result);
|
||||
});
|
||||
|
||||
it('showWindow', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'showWindow',
|
||||
args: {},
|
||||
};
|
||||
const check = createCheck({ isRoute: true, hasWebUrl: false });
|
||||
check('sgnl://show-window/', result);
|
||||
check('sgnl://show-window', result);
|
||||
});
|
||||
|
||||
it('setIsPresenting', () => {
|
||||
const result: ParsedSignalRoute = {
|
||||
key: 'setIsPresenting',
|
||||
args: {},
|
||||
};
|
||||
const check = createCheck({ isRoute: true, hasWebUrl: false });
|
||||
check('sgnl://set-is-presenting/', result);
|
||||
check('sgnl://set-is-presenting', result);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue