Create group link previews; don't open Signal links in browser first; allow ephemeral download of previously-error'd pack
This commit is contained in:
parent
f832b018fc
commit
e10ae03bb7
8 changed files with 338 additions and 33 deletions
2
js/modules/link_previews.d.ts
vendored
2
js/modules/link_previews.d.ts
vendored
|
@ -7,6 +7,8 @@ export function findLinks(text: string, caretLocation?: number): Array<string>;
|
||||||
|
|
||||||
export function getDomain(href: string): string;
|
export function getDomain(href: string): string;
|
||||||
|
|
||||||
|
export function isGroupLink(href: string): boolean;
|
||||||
|
|
||||||
export function isLinkSneaky(link: string): boolean;
|
export function isLinkSneaky(link: string): boolean;
|
||||||
|
|
||||||
export function isStickerPack(href: string): boolean;
|
export function isStickerPack(href: string): boolean;
|
||||||
|
|
|
@ -12,6 +12,7 @@ const linkify = LinkifyIt();
|
||||||
module.exports = {
|
module.exports = {
|
||||||
findLinks,
|
findLinks,
|
||||||
getDomain,
|
getDomain,
|
||||||
|
isGroupLink,
|
||||||
isLinkSafeToPreview,
|
isLinkSafeToPreview,
|
||||||
isLinkSneaky,
|
isLinkSneaky,
|
||||||
isStickerPack,
|
isStickerPack,
|
||||||
|
@ -34,6 +35,10 @@ function isStickerPack(link) {
|
||||||
return (link || '').startsWith('https://signal.art/addstickers/');
|
return (link || '').startsWith('https://signal.art/addstickers/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isGroupLink(link) {
|
||||||
|
return (link || '').startsWith('https://signal.group/');
|
||||||
|
}
|
||||||
|
|
||||||
function findLinks(text, caretLocation) {
|
function findLinks(text, caretLocation) {
|
||||||
const haveCaretLocation = isNumber(caretLocation);
|
const haveCaretLocation = isNumber(caretLocation);
|
||||||
const textLength = text ? text.length : 0;
|
const textLength = text ? text.length : 0;
|
||||||
|
|
|
@ -354,7 +354,12 @@ async function downloadEphemeralPack(packId, packKey) {
|
||||||
} = getReduxStickerActions();
|
} = getReduxStickerActions();
|
||||||
|
|
||||||
const existingPack = getStickerPack(packId);
|
const existingPack = getStickerPack(packId);
|
||||||
if (existingPack) {
|
if (
|
||||||
|
existingPack &&
|
||||||
|
(existingPack.status === 'downloaded' ||
|
||||||
|
existingPack.status === 'installed' ||
|
||||||
|
existingPack.status === 'pending')
|
||||||
|
) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`Ephemeral download for pack ${redactPackId(
|
`Ephemeral download for pack ${redactPackId(
|
||||||
packId
|
packId
|
||||||
|
|
23
main.js
23
main.js
|
@ -99,7 +99,12 @@ const {
|
||||||
const { installPermissionsHandler } = require('./app/permissions');
|
const { installPermissionsHandler } = require('./app/permissions');
|
||||||
const OS = require('./ts/OS');
|
const OS = require('./ts/OS');
|
||||||
const { isBeta } = require('./ts/util/version');
|
const { isBeta } = require('./ts/util/version');
|
||||||
const { isSgnlHref, parseSgnlHref } = require('./ts/util/sgnlHref');
|
const {
|
||||||
|
isSgnlHref,
|
||||||
|
isSignalHttpsLink,
|
||||||
|
parseSgnlHref,
|
||||||
|
parseSignalHttpsLink,
|
||||||
|
} = require('./ts/util/sgnlHref');
|
||||||
const {
|
const {
|
||||||
toggleMaximizedBrowserWindow,
|
toggleMaximizedBrowserWindow,
|
||||||
} = require('./ts/util/toggleMaximizedBrowserWindow');
|
} = require('./ts/util/toggleMaximizedBrowserWindow');
|
||||||
|
@ -227,6 +232,11 @@ async function handleUrl(event, target) {
|
||||||
const { protocol, hostname } = url.parse(target);
|
const { protocol, hostname } = url.parse(target);
|
||||||
const isDevServer = config.enableHttp && hostname === 'localhost';
|
const isDevServer = config.enableHttp && hostname === 'localhost';
|
||||||
// We only want to specially handle urls that aren't requesting the dev server
|
// We only want to specially handle urls that aren't requesting the dev server
|
||||||
|
if (isSgnlHref(target) || isSignalHttpsLink(target)) {
|
||||||
|
handleSgnlHref(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((protocol === 'http:' || protocol === 'https:') && !isDevServer) {
|
if ((protocol === 'http:' || protocol === 'https:') && !isDevServer) {
|
||||||
try {
|
try {
|
||||||
await shell.openExternal(target);
|
await shell.openExternal(target);
|
||||||
|
@ -1476,7 +1486,16 @@ function getIncomingHref(argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSgnlHref(incomingHref) {
|
function handleSgnlHref(incomingHref) {
|
||||||
const { command, args, hash } = parseSgnlHref(incomingHref, logger);
|
let command;
|
||||||
|
let args;
|
||||||
|
let hash;
|
||||||
|
|
||||||
|
if (isSgnlHref(incomingHref)) {
|
||||||
|
({ command, args, hash } = parseSgnlHref(incomingHref, logger));
|
||||||
|
} else if (isSignalHttpsLink(incomingHref)) {
|
||||||
|
({ command, args, hash } = parseSignalHttpsLink(incomingHref, logger));
|
||||||
|
}
|
||||||
|
|
||||||
if (command === 'addstickers' && mainWindow && mainWindow.webContents) {
|
if (command === 'addstickers' && mainWindow && mainWindow.webContents) {
|
||||||
console.log('Opening sticker pack from sgnl protocol link');
|
console.log('Opening sticker pack from sgnl protocol link');
|
||||||
const packId = args.get('pack_id');
|
const packId = args.get('pack_id');
|
||||||
|
|
45
ts/groups.ts
45
ts/groups.ts
|
@ -3809,6 +3809,30 @@ async function applyGroupChange({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function decryptGroupAvatar(
|
||||||
|
avatarKey: string,
|
||||||
|
secretParamsBase64: string
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
|
const sender = window.textsecure.messaging;
|
||||||
|
if (!sender) {
|
||||||
|
throw new Error(
|
||||||
|
'decryptGroupAvatar: textsecure.messaging is not available!'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphertext = await sender.getGroupAvatar(avatarKey);
|
||||||
|
const clientZkGroupCipher = getClientZkGroupCipher(secretParamsBase64);
|
||||||
|
const plaintext = decryptGroupBlob(clientZkGroupCipher, ciphertext);
|
||||||
|
const blob = window.textsecure.protobuf.GroupAttributeBlob.decode(plaintext);
|
||||||
|
if (blob.content !== 'avatar') {
|
||||||
|
throw new Error(
|
||||||
|
`decryptGroupAvatar: Returned blob had incorrect content: ${blob.content}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob.avatar.toArrayBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
// Ovewriting result.avatar as part of functionality
|
// Ovewriting result.avatar as part of functionality
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
export async function applyNewAvatar(
|
export async function applyNewAvatar(
|
||||||
|
@ -3825,30 +3849,11 @@ export async function applyNewAvatar(
|
||||||
|
|
||||||
// Group has avatar; has it changed?
|
// Group has avatar; has it changed?
|
||||||
if (newAvatar && (!result.avatar || result.avatar.url !== newAvatar)) {
|
if (newAvatar && (!result.avatar || result.avatar.url !== newAvatar)) {
|
||||||
const sender = window.textsecure.messaging;
|
|
||||||
if (!sender) {
|
|
||||||
throw new Error(
|
|
||||||
'applyNewAvatar: textsecure.messaging is not available!'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.secretParams) {
|
if (!result.secretParams) {
|
||||||
throw new Error('applyNewAvatar: group was missing secretParams!');
|
throw new Error('applyNewAvatar: group was missing secretParams!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const ciphertext = await sender.getGroupAvatar(newAvatar);
|
const data = await decryptGroupAvatar(newAvatar, result.secretParams);
|
||||||
const clientZkGroupCipher = getClientZkGroupCipher(result.secretParams);
|
|
||||||
const plaintext = decryptGroupBlob(clientZkGroupCipher, ciphertext);
|
|
||||||
const blob = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
|
||||||
plaintext
|
|
||||||
);
|
|
||||||
if (blob.content !== 'avatar') {
|
|
||||||
throw new Error(
|
|
||||||
`applyNewAvatar: Returned blob had incorrect content: ${blob.content}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = blob.avatar.toArrayBuffer();
|
|
||||||
const hash = await computeHash(data);
|
const hash = await computeHash(data);
|
||||||
|
|
||||||
if (result.avatar && result.avatar.path && result.avatar.hash !== hash) {
|
if (result.avatar && result.avatar.path && result.avatar.hash !== hash) {
|
||||||
|
|
|
@ -5,7 +5,12 @@ import { assert } from 'chai';
|
||||||
import Sinon from 'sinon';
|
import Sinon from 'sinon';
|
||||||
import { LoggerType } from '../../types/Logging';
|
import { LoggerType } from '../../types/Logging';
|
||||||
|
|
||||||
import { isSgnlHref, parseSgnlHref } from '../../util/sgnlHref';
|
import {
|
||||||
|
isSgnlHref,
|
||||||
|
isSignalHttpsLink,
|
||||||
|
parseSgnlHref,
|
||||||
|
parseSignalHttpsLink,
|
||||||
|
} from '../../util/sgnlHref';
|
||||||
|
|
||||||
function shouldNeverBeCalled() {
|
function shouldNeverBeCalled() {
|
||||||
assert.fail('This should never be called');
|
assert.fail('This should never be called');
|
||||||
|
@ -83,6 +88,67 @@ describe('sgnlHref', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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', explodingLogger));
|
||||||
|
assert.isFalse(
|
||||||
|
isSignalHttpsLink(
|
||||||
|
'sgnl://signal.art/addstickers/?pack_id=abc',
|
||||||
|
explodingLogger
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert.isFalse(
|
||||||
|
isSignalHttpsLink('signal://signal.group', explodingLogger)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if the protocol is "https:"', () => {
|
||||||
|
assert.isTrue(isSignalHttpsLink('https://signal.group', explodingLogger));
|
||||||
|
assert.isTrue(isSignalHttpsLink('https://signal.art', explodingLogger));
|
||||||
|
assert.isTrue(isSignalHttpsLink('HTTPS://signal.art', 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', explodingLogger)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts URL objects', () => {
|
||||||
|
const invalid = new URL('sgnl://example.com');
|
||||||
|
assert.isFalse(isSignalHttpsLink(invalid, explodingLogger));
|
||||||
|
const valid = new URL('https://signal.art');
|
||||||
|
assert.isTrue(isSignalHttpsLink(valid, explodingLogger));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('parseSgnlHref', () => {
|
describe('parseSgnlHref', () => {
|
||||||
it('returns a null command for invalid URLs', () => {
|
it('returns a null command for invalid URLs', () => {
|
||||||
['', 'sgnl', 'https://example/?foo=bar'].forEach(href => {
|
['', 'sgnl', 'https://example/?foo=bar'].forEach(href => {
|
||||||
|
@ -188,4 +254,47 @@ describe('sgnlHref', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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>(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,6 +23,21 @@ export function isSgnlHref(value: string | URL, logger: LoggerType): boolean {
|
||||||
return url !== null && url.protocol === 'sgnl:';
|
return url !== null && url.protocol === 'sgnl:';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSignalHttpsLink(
|
||||||
|
value: string | URL,
|
||||||
|
logger: LoggerType
|
||||||
|
): boolean {
|
||||||
|
const url = parseUrl(value, logger);
|
||||||
|
return Boolean(
|
||||||
|
url &&
|
||||||
|
!url.username &&
|
||||||
|
!url.password &&
|
||||||
|
!url.port &&
|
||||||
|
url.protocol === 'https:' &&
|
||||||
|
(url.host === 'signal.group' || url.host === 'signal.art')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type ParsedSgnlHref =
|
type ParsedSgnlHref =
|
||||||
| { command: null; args: Map<never, never> }
|
| { command: null; args: Map<never, never> }
|
||||||
| { command: string; args: Map<string, string>; hash: string | undefined };
|
| { command: string; args: Map<string, string>; hash: string | undefined };
|
||||||
|
@ -48,3 +63,45 @@ export function parseSgnlHref(
|
||||||
hash: url.hash ? url.hash.slice(1) : undefined,
|
hash: url.hash ? url.hash.slice(1) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseSignalHttpsLink(
|
||||||
|
href: string,
|
||||||
|
logger: LoggerType
|
||||||
|
): ParsedSgnlHref {
|
||||||
|
const url = parseUrl(href, logger);
|
||||||
|
if (!url || !isSignalHttpsLink(url, logger)) {
|
||||||
|
return { command: null, args: new Map<never, never>() };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.host === 'signal.art') {
|
||||||
|
const hash = url.hash.slice(1);
|
||||||
|
const hashParams = new URLSearchParams(hash);
|
||||||
|
|
||||||
|
const args = new Map<string, string>();
|
||||||
|
hashParams.forEach((value, key) => {
|
||||||
|
if (!args.has(key)) {
|
||||||
|
args.set(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!args.get('pack_id') || !args.get('pack_key')) {
|
||||||
|
return { command: null, args: new Map<never, never>() };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
command: url.pathname.replace(/\//g, ''),
|
||||||
|
args,
|
||||||
|
hash: url.hash ? url.hash.slice(1) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.host === 'signal.group') {
|
||||||
|
return {
|
||||||
|
command: url.host,
|
||||||
|
args: new Map<string, string>(),
|
||||||
|
hash: url.hash ? url.hash.slice(1) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { command: null, args: new Map<never, never>() };
|
||||||
|
}
|
||||||
|
|
|
@ -12,16 +12,18 @@ type GroupV2PendingMemberType = import('../model-types.d').GroupV2PendingMemberT
|
||||||
type MediaItemType = import('../components/LightboxGallery').MediaItemType;
|
type MediaItemType = import('../components/LightboxGallery').MediaItemType;
|
||||||
type MessageType = import('../state/ducks/conversations').MessageType;
|
type MessageType = import('../state/ducks/conversations').MessageType;
|
||||||
|
|
||||||
type GetLinkPreviewResult = {
|
type GetLinkPreviewImageResult = {
|
||||||
title: string;
|
|
||||||
url: string;
|
|
||||||
image: {
|
|
||||||
data: ArrayBuffer;
|
data: ArrayBuffer;
|
||||||
size: number;
|
size: number;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
width: number;
|
width?: number;
|
||||||
height: number;
|
height?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GetLinkPreviewResult = {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
image?: GetLinkPreviewImageResult;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
date: number | null;
|
date: number | null;
|
||||||
};
|
};
|
||||||
|
@ -3580,7 +3582,10 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
this.renderLinkPreview();
|
this.renderLinkPreview();
|
||||||
},
|
},
|
||||||
|
|
||||||
async getStickerPackPreview(url: any) {
|
async getStickerPackPreview(
|
||||||
|
url: string,
|
||||||
|
abortSignal: any
|
||||||
|
): Promise<null | GetLinkPreviewResult> {
|
||||||
const isPackDownloaded = (pack: any) =>
|
const isPackDownloaded = (pack: any) =>
|
||||||
pack && (pack.status === 'downloaded' || pack.status === 'installed');
|
pack && (pack.status === 'downloaded' || pack.status === 'installed');
|
||||||
const isPackValid = (pack: any) =>
|
const isPackValid = (pack: any) =>
|
||||||
|
@ -3604,7 +3609,12 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
await window.Signal.Stickers.downloadEphemeralPack(id, keyBase64);
|
await window.Signal.Stickers.downloadEphemeralPack(id, keyBase64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (abortSignal.aborted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const pack = window.Signal.Stickers.getStickerPack(id);
|
const pack = window.Signal.Stickers.getStickerPack(id);
|
||||||
|
|
||||||
if (!isPackValid(pack)) {
|
if (!isPackValid(pack)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -3619,6 +3629,10 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
? await window.Signal.Migrations.readTempData(sticker.path)
|
? await window.Signal.Migrations.readTempData(sticker.path)
|
||||||
: await window.Signal.Migrations.readStickerData(sticker.path);
|
: await window.Signal.Migrations.readStickerData(sticker.path);
|
||||||
|
|
||||||
|
if (abortSignal.aborted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
|
@ -3628,6 +3642,8 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
size: data.byteLength,
|
size: data.byteLength,
|
||||||
contentType: 'image/webp',
|
contentType: 'image/webp',
|
||||||
},
|
},
|
||||||
|
description: null,
|
||||||
|
date: null,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
|
@ -3642,12 +3658,99 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getGroupPreview(
|
||||||
|
url: string,
|
||||||
|
abortSignal: any
|
||||||
|
): Promise<null | GetLinkPreviewResult> {
|
||||||
|
let urlObject;
|
||||||
|
try {
|
||||||
|
urlObject = new URL(url);
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hash } = urlObject;
|
||||||
|
if (!hash) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const groupData = hash.slice(1);
|
||||||
|
|
||||||
|
const {
|
||||||
|
inviteLinkPassword,
|
||||||
|
masterKey,
|
||||||
|
} = window.Signal.Groups.parseGroupLink(groupData);
|
||||||
|
|
||||||
|
const fields = window.Signal.Groups.deriveGroupFields(
|
||||||
|
window.Signal.Crypto.base64ToArrayBuffer(masterKey)
|
||||||
|
);
|
||||||
|
const id = window.Signal.Crypto.arrayBufferToBase64(fields.id);
|
||||||
|
const logId = `groupv2(${id})`;
|
||||||
|
const secretParams = window.Signal.Crypto.arrayBufferToBase64(
|
||||||
|
fields.secretParams
|
||||||
|
);
|
||||||
|
|
||||||
|
window.log.info(`getGroupPreview/${logId}: Fetching pre-join state`);
|
||||||
|
const result = await window.Signal.Groups.getPreJoinGroupInfo(
|
||||||
|
inviteLinkPassword,
|
||||||
|
masterKey
|
||||||
|
);
|
||||||
|
|
||||||
|
if (abortSignal.aborted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title =
|
||||||
|
window.Signal.Groups.decryptGroupTitle(result.title, secretParams) ||
|
||||||
|
window.i18n('unknownGroup');
|
||||||
|
const description =
|
||||||
|
result.memberCount === 1 || result.memberCount === undefined
|
||||||
|
? window.i18n('GroupV2--join--member-count--single')
|
||||||
|
: window.i18n('GroupV2--join--member-count--multiple', {
|
||||||
|
count: result.memberCount.toString(),
|
||||||
|
});
|
||||||
|
let image: undefined | GetLinkPreviewImageResult;
|
||||||
|
|
||||||
|
if (result.avatar) {
|
||||||
|
try {
|
||||||
|
const data = await window.Signal.Groups.decryptGroupAvatar(
|
||||||
|
result.avatar,
|
||||||
|
secretParams
|
||||||
|
);
|
||||||
|
image = {
|
||||||
|
data,
|
||||||
|
size: data.byteLength,
|
||||||
|
contentType: 'image/jpeg',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const errorString = error && error.stack ? error.stack : error;
|
||||||
|
window.log.error(
|
||||||
|
`getGroupPreview/${logId}: Failed to fetch avatar ${errorString}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abortSignal.aborted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
url,
|
||||||
|
image,
|
||||||
|
date: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
async getPreview(
|
async getPreview(
|
||||||
url: string,
|
url: string,
|
||||||
abortSignal: any
|
abortSignal: any
|
||||||
): Promise<null | GetLinkPreviewResult> {
|
): Promise<null | GetLinkPreviewResult> {
|
||||||
if (window.Signal.LinkPreviews.isStickerPack(url)) {
|
if (window.Signal.LinkPreviews.isStickerPack(url)) {
|
||||||
return this.getStickerPackPreview(url);
|
return this.getStickerPackPreview(url, abortSignal);
|
||||||
|
}
|
||||||
|
if (window.Signal.LinkPreviews.isGroupLink(url)) {
|
||||||
|
return this.getGroupPreview(url, abortSignal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is already checked elsewhere, but we want to be extra-careful.
|
// This is already checked elsewhere, but we want to be extra-careful.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue