Notification token on Windows
This commit is contained in:
parent
74acb3a2dd
commit
22d30ec4eb
10 changed files with 142 additions and 122 deletions
11
app/main.ts
11
app/main.ts
|
@ -2899,14 +2899,13 @@ function handleSignalRoute(route: ParsedSignalRoute) {
|
||||||
value: route.args.encryptedUsername,
|
value: route.args.encryptedUsername,
|
||||||
});
|
});
|
||||||
} else if (route.key === 'showConversation') {
|
} else if (route.key === 'showConversation') {
|
||||||
mainWindow.webContents.send('show-conversation-via-notification', {
|
mainWindow.webContents.send(
|
||||||
conversationId: route.args.conversationId,
|
'show-conversation-via-token',
|
||||||
messageId: route.args.messageId,
|
route.args.token
|
||||||
storyId: route.args.storyId,
|
);
|
||||||
});
|
|
||||||
} else if (route.key === 'startCallLobby') {
|
} else if (route.key === 'startCallLobby') {
|
||||||
mainWindow.webContents.send('start-call-lobby', {
|
mainWindow.webContents.send('start-call-lobby', {
|
||||||
conversationId: route.args.conversationId,
|
token: route.args.token,
|
||||||
});
|
});
|
||||||
} else if (route.key === 'linkCall') {
|
} else if (route.key === 'linkCall') {
|
||||||
mainWindow.webContents.send('start-call-link', {
|
mainWindow.webContents.send('start-call-link', {
|
||||||
|
|
|
@ -37,10 +37,8 @@ const Image = (props: { id: string; src: string; 'hint-crop': string }) =>
|
||||||
export function renderWindowsToast({
|
export function renderWindowsToast({
|
||||||
avatarPath,
|
avatarPath,
|
||||||
body,
|
body,
|
||||||
conversationId,
|
|
||||||
heading,
|
heading,
|
||||||
messageId,
|
token,
|
||||||
storyId,
|
|
||||||
type,
|
type,
|
||||||
}: WindowsNotificationData): string {
|
}: WindowsNotificationData): string {
|
||||||
// Note: with these templates, the first <text> is one line, bolded
|
// Note: with these templates, the first <text> is one line, bolded
|
||||||
|
@ -58,13 +56,11 @@ export function renderWindowsToast({
|
||||||
// 2) this also maps to the url-handling in main.ts
|
// 2) this also maps to the url-handling in main.ts
|
||||||
if (type === NotificationType.Message || type === NotificationType.Reaction) {
|
if (type === NotificationType.Message || type === NotificationType.Reaction) {
|
||||||
launch = showConversationRoute.toAppUrl({
|
launch = showConversationRoute.toAppUrl({
|
||||||
conversationId,
|
token,
|
||||||
messageId: messageId ?? null,
|
|
||||||
storyId: storyId ?? null,
|
|
||||||
});
|
});
|
||||||
} else if (type === NotificationType.IncomingGroupCall) {
|
} else if (type === NotificationType.IncomingGroupCall) {
|
||||||
launch = startCallLobbyRoute.toAppUrl({
|
launch = startCallLobbyRoute.toAppUrl({
|
||||||
conversationId,
|
token,
|
||||||
});
|
});
|
||||||
} else if (type === NotificationType.IncomingCall) {
|
} else if (type === NotificationType.IncomingCall) {
|
||||||
launch = showWindowRoute.toAppUrl({});
|
launch = showWindowRoute.toAppUrl({});
|
||||||
|
|
16
ts/CI.ts
16
ts/CI.ts
|
@ -10,6 +10,7 @@ import * as log from './logging/log';
|
||||||
import { explodePromise } from './util/explodePromise';
|
import { explodePromise } from './util/explodePromise';
|
||||||
import { AccessType, ipcInvoke } from './sql/channels';
|
import { AccessType, ipcInvoke } from './sql/channels';
|
||||||
import { backupsService } from './services/backups';
|
import { backupsService } from './services/backups';
|
||||||
|
import { notificationService } from './services/notifications';
|
||||||
import { AttachmentBackupManager } from './jobs/AttachmentBackupManager';
|
import { AttachmentBackupManager } from './jobs/AttachmentBackupManager';
|
||||||
import { migrateAllMessages } from './messages/migrateMessageData';
|
import { migrateAllMessages } from './messages/migrateMessageData';
|
||||||
import { SECOND } from './util/durations';
|
import { SECOND } from './util/durations';
|
||||||
|
@ -22,6 +23,7 @@ type ResolveType = (data: unknown) => void;
|
||||||
export type CIType = {
|
export type CIType = {
|
||||||
deviceName: string;
|
deviceName: string;
|
||||||
getConversationId: (address: string | null) => string | null;
|
getConversationId: (address: string | null) => string | null;
|
||||||
|
createNotificationToken: (address: string) => string | undefined;
|
||||||
getMessagesBySentAt(
|
getMessagesBySentAt(
|
||||||
sentAt: number
|
sentAt: number
|
||||||
): Promise<ReadonlyArray<MessageAttributesType>>;
|
): Promise<ReadonlyArray<MessageAttributesType>>;
|
||||||
|
@ -157,6 +159,19 @@ export function getCI({
|
||||||
return window.ConversationController.getConversationId(address);
|
return window.ConversationController.getConversationId(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createNotificationToken(address: string): string | undefined {
|
||||||
|
const id = window.ConversationController.getConversationId(address);
|
||||||
|
if (!id) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return notificationService._createToken({
|
||||||
|
conversationId: id,
|
||||||
|
messageId: undefined,
|
||||||
|
storyId: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function openSignalRoute(url: string) {
|
async function openSignalRoute(url: string) {
|
||||||
strictAssert(
|
strictAssert(
|
||||||
isSignalRoute(url),
|
isSignalRoute(url),
|
||||||
|
@ -201,6 +216,7 @@ export function getCI({
|
||||||
return {
|
return {
|
||||||
deviceName,
|
deviceName,
|
||||||
getConversationId,
|
getConversationId,
|
||||||
|
createNotificationToken,
|
||||||
getMessagesBySentAt,
|
getMessagesBySentAt,
|
||||||
handleEvent,
|
handleEvent,
|
||||||
setProvisioningURL,
|
setProvisioningURL,
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
|
import { v4 as getGuid } from 'uuid';
|
||||||
|
|
||||||
import { Sound, SoundType } from '../util/Sound';
|
import { Sound, SoundType } from '../util/Sound';
|
||||||
import { shouldHideExpiringMessageBody } from '../types/Settings';
|
import { shouldHideExpiringMessageBody } from '../types/Settings';
|
||||||
import OS from '../util/os/osMain';
|
import OS from '../util/os/osMain';
|
||||||
|
@ -36,16 +38,14 @@ type NotificationDataType = Readonly<{
|
||||||
|
|
||||||
export type NotificationClickData = Readonly<{
|
export type NotificationClickData = Readonly<{
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
messageId: string | null;
|
messageId: string | undefined;
|
||||||
storyId: string | null;
|
storyId: string | undefined;
|
||||||
}>;
|
}>;
|
||||||
export type WindowsNotificationData = {
|
export type WindowsNotificationData = {
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
body: string;
|
body: string;
|
||||||
conversationId: string;
|
|
||||||
heading: string;
|
heading: string;
|
||||||
messageId?: string;
|
token: string;
|
||||||
storyId?: string;
|
|
||||||
type: NotificationType;
|
type: NotificationType;
|
||||||
};
|
};
|
||||||
export enum NotificationType {
|
export enum NotificationType {
|
||||||
|
@ -86,6 +86,7 @@ class NotificationService extends EventEmitter {
|
||||||
|
|
||||||
#lastNotification: null | Notification = null;
|
#lastNotification: null | Notification = null;
|
||||||
#notificationData: null | NotificationDataType = null;
|
#notificationData: null | NotificationDataType = null;
|
||||||
|
#tokenData: { token: string; data: NotificationClickData } | undefined;
|
||||||
|
|
||||||
// Testing indicated that trying to create/destroy notifications too quickly
|
// Testing indicated that trying to create/destroy notifications too quickly
|
||||||
// resulted in notifications that stuck around forever, requiring the user
|
// resulted in notifications that stuck around forever, requiring the user
|
||||||
|
@ -175,16 +176,20 @@ class NotificationService extends EventEmitter {
|
||||||
log.info('NotificationService: showing a notification', sentAt);
|
log.info('NotificationService: showing a notification', sentAt);
|
||||||
|
|
||||||
if (OS.isWindows()) {
|
if (OS.isWindows()) {
|
||||||
|
const token = this._createToken({
|
||||||
|
conversationId,
|
||||||
|
messageId,
|
||||||
|
storyId,
|
||||||
|
});
|
||||||
|
|
||||||
// Note: showing a windows notification clears all previous notifications first
|
// Note: showing a windows notification clears all previous notifications first
|
||||||
drop(
|
drop(
|
||||||
window.IPC.showWindowsNotification({
|
window.IPC.showWindowsNotification({
|
||||||
avatarPath: iconPath,
|
avatarPath: iconPath,
|
||||||
body: message,
|
body: message,
|
||||||
conversationId,
|
|
||||||
heading: title,
|
heading: title,
|
||||||
messageId,
|
|
||||||
storyId,
|
|
||||||
type,
|
type,
|
||||||
|
token,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -206,8 +211,8 @@ class NotificationService extends EventEmitter {
|
||||||
window.IPC.showWindow();
|
window.IPC.showWindow();
|
||||||
window.Events.showConversationViaNotification({
|
window.Events.showConversationViaNotification({
|
||||||
conversationId,
|
conversationId,
|
||||||
messageId: messageId ?? null,
|
messageId,
|
||||||
storyId: storyId ?? null,
|
storyId,
|
||||||
});
|
});
|
||||||
} else if (type === NotificationType.IncomingGroupCall) {
|
} else if (type === NotificationType.IncomingGroupCall) {
|
||||||
window.IPC.showWindow();
|
window.IPC.showWindow();
|
||||||
|
@ -304,6 +309,7 @@ class NotificationService extends EventEmitter {
|
||||||
// adding anythhing new; just one notification at a time. Electron forces it, so
|
// adding anythhing new; just one notification at a time. Electron forces it, so
|
||||||
// we replicate it with our Windows notifications.
|
// we replicate it with our Windows notifications.
|
||||||
if (!notificationData) {
|
if (!notificationData) {
|
||||||
|
this.#tokenData = undefined;
|
||||||
drop(window.IPC.clearAllWindowsNotifications());
|
drop(window.IPC.clearAllWindowsNotifications());
|
||||||
}
|
}
|
||||||
} else if (this.#lastNotification) {
|
} else if (this.#lastNotification) {
|
||||||
|
@ -446,6 +452,32 @@ class NotificationService extends EventEmitter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
public _createToken(data: NotificationClickData): string {
|
||||||
|
const token = getGuid();
|
||||||
|
|
||||||
|
this.#tokenData = {
|
||||||
|
token,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolveToken(token: string): NotificationClickData | undefined {
|
||||||
|
if (!this.#tokenData) {
|
||||||
|
log.warn(`NotificationService: no data when looking up ${token}`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#tokenData.token !== token) {
|
||||||
|
log.warn(`NotificationService: token mismatch ${token}`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#tokenData.data;
|
||||||
|
}
|
||||||
|
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
log.info(
|
log.info(
|
||||||
'NotificationService: clearing notification and requesting an update'
|
'NotificationService: clearing notification and requesting an update'
|
||||||
|
|
|
@ -55,18 +55,13 @@ describe('routing', function (this: Mocha.Suite) {
|
||||||
const [friend] = contacts;
|
const [friend] = contacts;
|
||||||
const page = await app.getWindow();
|
const page = await app.getWindow();
|
||||||
await page.locator('#LeftPane').waitFor();
|
await page.locator('#LeftPane').waitFor();
|
||||||
const conversationId = await page.evaluate(
|
const token = await page.evaluate(
|
||||||
serviceId => window.SignalCI?.getConversationId(serviceId),
|
serviceId => window.SignalCI?.createNotificationToken(serviceId),
|
||||||
friend.toContact().aci
|
friend.toContact().aci
|
||||||
);
|
);
|
||||||
strictAssert(
|
strictAssert(typeof token === 'string', 'token must be returned');
|
||||||
typeof conversationId === 'string',
|
|
||||||
'conversationId must exist'
|
|
||||||
);
|
|
||||||
const conversationUrl = showConversationRoute.toAppUrl({
|
const conversationUrl = showConversationRoute.toAppUrl({
|
||||||
conversationId,
|
token,
|
||||||
messageId: null,
|
|
||||||
storyId: null,
|
|
||||||
});
|
});
|
||||||
await app.openSignalRoute(conversationUrl);
|
await app.openSignalRoute(conversationUrl);
|
||||||
const title = page.locator(
|
const title = page.locator(
|
||||||
|
|
|
@ -12,12 +12,12 @@ describe('renderWindowsToast', () => {
|
||||||
avatarPath: 'C:/temp/ab/abcd',
|
avatarPath: 'C:/temp/ab/abcd',
|
||||||
body: 'Hi there!',
|
body: 'Hi there!',
|
||||||
heading: 'Alice',
|
heading: 'Alice',
|
||||||
conversationId: 'conversation5',
|
token: 'token',
|
||||||
type: NotificationType.Message,
|
type: NotificationType.Message,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
'<toast launch="sgnl://show-conversation?conversationId=conversation5" activationType="protocol"><visual><binding template="ToastImageAndText02"><image id="1" src="file:///C:/temp/ab/abcd" hint-crop="circle"></image><text id="1">Alice</text><text id="2">Hi there!</text></binding></visual></toast>';
|
'<toast launch="sgnl://show-conversation?token=token" activationType="protocol"><visual><binding template="ToastImageAndText02"><image id="1" src="file:///C:/temp/ab/abcd" hint-crop="circle"></image><text id="1">Alice</text><text id="2">Hi there!</text></binding></visual></toast>';
|
||||||
|
|
||||||
assert.strictEqual(xml, expected);
|
assert.strictEqual(xml, expected);
|
||||||
});
|
});
|
||||||
|
@ -26,12 +26,12 @@ describe('renderWindowsToast', () => {
|
||||||
const xml = renderWindowsToast({
|
const xml = renderWindowsToast({
|
||||||
body: 'Hi there!',
|
body: 'Hi there!',
|
||||||
heading: 'Alice',
|
heading: 'Alice',
|
||||||
conversationId: 'conversation5',
|
token: 'token',
|
||||||
type: NotificationType.Message,
|
type: NotificationType.Message,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
'<toast launch="sgnl://show-conversation?conversationId=conversation5" activationType="protocol"><visual><binding template="ToastText02"><text id="1">Alice</text><text id="2">Hi there!</text></binding></visual></toast>';
|
'<toast launch="sgnl://show-conversation?token=token" activationType="protocol"><visual><binding template="ToastText02"><text id="1">Alice</text><text id="2">Hi there!</text></binding></visual></toast>';
|
||||||
|
|
||||||
assert.strictEqual(xml, expected);
|
assert.strictEqual(xml, expected);
|
||||||
});
|
});
|
||||||
|
@ -40,14 +40,12 @@ describe('renderWindowsToast', () => {
|
||||||
const xml = renderWindowsToast({
|
const xml = renderWindowsToast({
|
||||||
body: 'Hi there!',
|
body: 'Hi there!',
|
||||||
heading: 'Alice',
|
heading: 'Alice',
|
||||||
conversationId: 'conversation5',
|
token: 'token',
|
||||||
messageId: 'message6',
|
|
||||||
storyId: 'story7',
|
|
||||||
type: NotificationType.Message,
|
type: NotificationType.Message,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
'<toast launch="sgnl://show-conversation?conversationId=conversation5&messageId=message6&storyId=story7" activationType="protocol"><visual><binding template="ToastText02"><text id="1">Alice</text><text id="2">Hi there!</text></binding></visual></toast>';
|
'<toast launch="sgnl://show-conversation?token=token" activationType="protocol"><visual><binding template="ToastText02"><text id="1">Alice</text><text id="2">Hi there!</text></binding></visual></toast>';
|
||||||
|
|
||||||
assert.strictEqual(xml, expected);
|
assert.strictEqual(xml, expected);
|
||||||
});
|
});
|
||||||
|
@ -56,7 +54,7 @@ describe('renderWindowsToast', () => {
|
||||||
const xml = renderWindowsToast({
|
const xml = renderWindowsToast({
|
||||||
body: 'Hi there!',
|
body: 'Hi there!',
|
||||||
heading: 'Alice',
|
heading: 'Alice',
|
||||||
conversationId: 'conversation5',
|
token: 'token',
|
||||||
type: NotificationType.IncomingCall,
|
type: NotificationType.IncomingCall,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,12 +68,12 @@ describe('renderWindowsToast', () => {
|
||||||
const xml = renderWindowsToast({
|
const xml = renderWindowsToast({
|
||||||
body: 'Hi there!',
|
body: 'Hi there!',
|
||||||
heading: 'Alice',
|
heading: 'Alice',
|
||||||
conversationId: 'conversation5',
|
token: 'token',
|
||||||
type: NotificationType.IncomingGroupCall,
|
type: NotificationType.IncomingGroupCall,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
'<toast launch="sgnl://start-call-lobby?conversationId=conversation5" activationType="protocol"><visual><binding template="ToastText02"><text id="1">Alice</text><text id="2">Hi there!</text></binding></visual></toast>';
|
'<toast launch="sgnl://start-call-lobby?token=token" activationType="protocol"><visual><binding template="ToastText02"><text id="1">Alice</text><text id="2">Hi there!</text></binding></visual></toast>';
|
||||||
|
|
||||||
assert.strictEqual(xml, expected);
|
assert.strictEqual(xml, expected);
|
||||||
});
|
});
|
||||||
|
@ -84,7 +82,7 @@ describe('renderWindowsToast', () => {
|
||||||
const xml = renderWindowsToast({
|
const xml = renderWindowsToast({
|
||||||
body: 'Hi there!',
|
body: 'Hi there!',
|
||||||
heading: 'Alice',
|
heading: 'Alice',
|
||||||
conversationId: 'conversation5',
|
token: 'token',
|
||||||
type: NotificationType.IsPresenting,
|
type: NotificationType.IsPresenting,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -186,37 +186,23 @@ describe('signalRoutes', () => {
|
||||||
|
|
||||||
it('showConversation', () => {
|
it('showConversation', () => {
|
||||||
const check = createCheck({ isRoute: true, hasWebUrl: false });
|
const check = createCheck({ isRoute: true, hasWebUrl: false });
|
||||||
const args1 = `conversationId=${foo}`;
|
const args1 = `token=${foo}`;
|
||||||
const args2 = `conversationId=${foo}&messageId=${foo}`;
|
|
||||||
const args3 = `conversationId=${foo}&messageId=${foo}&storyId=${foo}`;
|
|
||||||
const result1: ParsedSignalRoute = {
|
const result1: ParsedSignalRoute = {
|
||||||
key: 'showConversation',
|
key: 'showConversation',
|
||||||
args: { conversationId: foo, messageId: null, storyId: null },
|
args: { token: foo },
|
||||||
};
|
|
||||||
const result2: ParsedSignalRoute = {
|
|
||||||
key: 'showConversation',
|
|
||||||
args: { conversationId: foo, messageId: foo, storyId: null },
|
|
||||||
};
|
|
||||||
const result3: ParsedSignalRoute = {
|
|
||||||
key: 'showConversation',
|
|
||||||
args: { conversationId: foo, messageId: foo, storyId: foo },
|
|
||||||
};
|
};
|
||||||
check(`sgnl://show-conversation/?${args1}`, result1);
|
check(`sgnl://show-conversation/?${args1}`, result1);
|
||||||
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', () => {
|
it('startCallLobby', () => {
|
||||||
const result: ParsedSignalRoute = {
|
const result: ParsedSignalRoute = {
|
||||||
key: 'startCallLobby',
|
key: 'startCallLobby',
|
||||||
args: { conversationId: foo },
|
args: { token: foo },
|
||||||
};
|
};
|
||||||
const check = createCheck({ isRoute: true, hasWebUrl: false });
|
const check = createCheck({ isRoute: true, hasWebUrl: false });
|
||||||
check(`sgnl://start-call-lobby/?conversationId=${foo}`, result);
|
check(`sgnl://start-call-lobby/?token=${foo}`, result);
|
||||||
check(`sgnl://start-call-lobby?conversationId=${foo}`, result);
|
check(`sgnl://start-call-lobby?token=${foo}`, result);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('showWindow', () => {
|
it('showWindow', () => {
|
||||||
|
|
|
@ -35,7 +35,10 @@ import * as Registration from './registration';
|
||||||
import { lookupConversationWithoutServiceId } from './lookupConversationWithoutServiceId';
|
import { lookupConversationWithoutServiceId } from './lookupConversationWithoutServiceId';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { deleteAllMyStories } from './deleteAllMyStories';
|
import { deleteAllMyStories } from './deleteAllMyStories';
|
||||||
import type { NotificationClickData } from '../services/notifications';
|
import {
|
||||||
|
type NotificationClickData,
|
||||||
|
notificationService,
|
||||||
|
} from '../services/notifications';
|
||||||
import { StoryViewModeType, StoryViewTargetType } from '../types/Stories';
|
import { StoryViewModeType, StoryViewTargetType } from '../types/Stories';
|
||||||
import { isValidE164 } from './isValidE164';
|
import { isValidE164 } from './isValidE164';
|
||||||
import { fromWebSafeBase64 } from './webSafeBase64';
|
import { fromWebSafeBase64 } from './webSafeBase64';
|
||||||
|
@ -121,6 +124,7 @@ export type IPCEventsCallbacksType = {
|
||||||
resetDefaultChatColor: () => void;
|
resetDefaultChatColor: () => void;
|
||||||
setMediaPlaybackDisabled: (playbackDisabled: boolean) => void;
|
setMediaPlaybackDisabled: (playbackDisabled: boolean) => void;
|
||||||
showConversationViaNotification: (data: NotificationClickData) => void;
|
showConversationViaNotification: (data: NotificationClickData) => void;
|
||||||
|
showConversationViaToken: (token: string) => void;
|
||||||
showConversationViaSignalDotMe: (
|
showConversationViaSignalDotMe: (
|
||||||
kind: string,
|
kind: string,
|
||||||
value: string
|
value: string
|
||||||
|
@ -129,6 +133,7 @@ export type IPCEventsCallbacksType = {
|
||||||
showGroupViaLink: (value: string) => Promise<void>;
|
showGroupViaLink: (value: string) => Promise<void>;
|
||||||
showReleaseNotes: () => void;
|
showReleaseNotes: () => void;
|
||||||
showStickerPack: (packId: string, key: string) => void;
|
showStickerPack: (packId: string, key: string) => void;
|
||||||
|
startCallingLobbyViaToken: (token: string) => void;
|
||||||
requestCloseConfirmation: () => Promise<boolean>;
|
requestCloseConfirmation: () => Promise<boolean>;
|
||||||
getIsInCall: () => boolean;
|
getIsInCall: () => boolean;
|
||||||
shutdown: () => Promise<void>;
|
shutdown: () => Promise<void>;
|
||||||
|
@ -575,23 +580,31 @@ export function createIPCEvents(
|
||||||
messageId,
|
messageId,
|
||||||
storyId,
|
storyId,
|
||||||
}: NotificationClickData) {
|
}: NotificationClickData) {
|
||||||
if (conversationId) {
|
if (!conversationId) {
|
||||||
if (storyId) {
|
|
||||||
window.reduxActions.stories.viewStory({
|
|
||||||
storyId,
|
|
||||||
storyViewMode: StoryViewModeType.Single,
|
|
||||||
viewTarget: StoryViewTargetType.Replies,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
window.reduxActions.conversations.showConversation({
|
|
||||||
conversationId,
|
|
||||||
messageId: messageId ?? undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.reduxActions.app.openInbox();
|
window.reduxActions.app.openInbox();
|
||||||
|
} else if (storyId) {
|
||||||
|
window.reduxActions.stories.viewStory({
|
||||||
|
storyId,
|
||||||
|
storyViewMode: StoryViewModeType.Single,
|
||||||
|
viewTarget: StoryViewTargetType.Replies,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.reduxActions.conversations.showConversation({
|
||||||
|
conversationId,
|
||||||
|
messageId: messageId ?? undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showConversationViaToken(token: string) {
|
||||||
|
const data = notificationService.resolveToken(token);
|
||||||
|
if (!data) {
|
||||||
|
window.reduxActions.app.openInbox();
|
||||||
|
} else {
|
||||||
|
window.Events.showConversationViaNotification(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async showConversationViaSignalDotMe(kind: string, value: string) {
|
async showConversationViaSignalDotMe(kind: string, value: string) {
|
||||||
if (!Registration.everDone()) {
|
if (!Registration.everDone()) {
|
||||||
log.info(
|
log.info(
|
||||||
|
@ -638,6 +651,17 @@ export function createIPCEvents(
|
||||||
showUnknownSgnlLinkModal();
|
showUnknownSgnlLinkModal();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
startCallingLobbyViaToken(token: string) {
|
||||||
|
const data = notificationService.resolveToken(token);
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.reduxActions?.calling?.startCallingLobby({
|
||||||
|
conversationId: data.conversationId,
|
||||||
|
isVideoCall: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
requestCloseConfirmation: async (): Promise<boolean> => {
|
requestCloseConfirmation: async (): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
|
|
@ -201,7 +201,6 @@ function _route<Key extends string, Args extends object>(
|
||||||
}
|
}
|
||||||
|
|
||||||
const paramSchema = z.string().min(1);
|
const paramSchema = z.string().min(1);
|
||||||
const optionalParamSchema = paramSchema.nullish().default(null);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* signal.me by phone number
|
* signal.me by phone number
|
||||||
|
@ -452,11 +451,9 @@ export const artAddStickersRoute = _route('artAddStickers', {
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* showConversationRoute.toAppUrl({
|
* showConversationRoute.toAppUrl({
|
||||||
* conversationId: "123",
|
* token: 'abc',
|
||||||
* messageId: "abc",
|
|
||||||
* storyId: "def",
|
|
||||||
* })
|
* })
|
||||||
* // URL { "sgnl://show-conversation?conversationId=123&messageId=abc&storyId=def" }
|
* // URL { "sgnl://show-conversation?token=abc" }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const showConversationRoute = _route('showConversation', {
|
export const showConversationRoute = _route('showConversation', {
|
||||||
|
@ -464,28 +461,16 @@ export const showConversationRoute = _route('showConversation', {
|
||||||
_pattern('sgnl:', 'show-conversation', '{/}?', { search: ':params' }),
|
_pattern('sgnl:', 'show-conversation', '{/}?', { search: ':params' }),
|
||||||
],
|
],
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
conversationId: paramSchema,
|
token: paramSchema,
|
||||||
messageId: optionalParamSchema,
|
|
||||||
storyId: optionalParamSchema,
|
|
||||||
}),
|
}),
|
||||||
parse(result) {
|
parse(result) {
|
||||||
const params = new URLSearchParams(result.search.groups.params);
|
const params = new URLSearchParams(result.search.groups.params);
|
||||||
return {
|
return {
|
||||||
conversationId: params.get('conversationId'),
|
token: params.get('token'),
|
||||||
messageId: params.get('messageId'),
|
|
||||||
storyId: params.get('storyId'),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
toAppUrl(args) {
|
toAppUrl(args) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({ token: args.token });
|
||||||
conversationId: args.conversationId,
|
|
||||||
});
|
|
||||||
if (args.messageId != null) {
|
|
||||||
params.set('messageId', args.messageId);
|
|
||||||
}
|
|
||||||
if (args.storyId != null) {
|
|
||||||
params.set('storyId', args.storyId);
|
|
||||||
}
|
|
||||||
return new URL(`sgnl://show-conversation?${params.toString()}`);
|
return new URL(`sgnl://show-conversation?${params.toString()}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -495,9 +480,9 @@ export const showConversationRoute = _route('showConversation', {
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* startCallLobbyRoute.toAppUrl({
|
* startCallLobbyRoute.toAppUrl({
|
||||||
* conversationId: "123",
|
* token: "123",
|
||||||
* })
|
* })
|
||||||
* // URL { "sgnl://start-call-lobby?conversationId=123" }
|
* // URL { "sgnl://start-call-lobby?token=123" }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const startCallLobbyRoute = _route('startCallLobby', {
|
export const startCallLobbyRoute = _route('startCallLobby', {
|
||||||
|
@ -505,18 +490,16 @@ export const startCallLobbyRoute = _route('startCallLobby', {
|
||||||
_pattern('sgnl:', 'start-call-lobby', '{/}?', { search: ':params' }),
|
_pattern('sgnl:', 'start-call-lobby', '{/}?', { search: ':params' }),
|
||||||
],
|
],
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
conversationId: paramSchema,
|
token: paramSchema,
|
||||||
}),
|
}),
|
||||||
parse(result) {
|
parse(result) {
|
||||||
const params = new URLSearchParams(result.search.groups.params);
|
const params = new URLSearchParams(result.search.groups.params);
|
||||||
return {
|
return {
|
||||||
conversationId: params.get('conversationId'),
|
token: params.get('token'),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
toAppUrl(args) {
|
toAppUrl(args) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({ token: args.token });
|
||||||
conversationId: args.conversationId,
|
|
||||||
});
|
|
||||||
return new URL(`sgnl://start-call-lobby?${params.toString()}`);
|
return new URL(`sgnl://start-call-lobby?${params.toString()}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,10 +18,7 @@ import * as Errors from '../../types/errors';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { drop } from '../../util/drop';
|
import { drop } from '../../util/drop';
|
||||||
import { DataReader } from '../../sql/Client';
|
import { DataReader } from '../../sql/Client';
|
||||||
import type {
|
import type { WindowsNotificationData } from '../../services/notifications';
|
||||||
NotificationClickData,
|
|
||||||
WindowsNotificationData,
|
|
||||||
} from '../../services/notifications';
|
|
||||||
import { AggregatedStats } from '../../textsecure/WebsocketResources';
|
import { AggregatedStats } from '../../textsecure/WebsocketResources';
|
||||||
import { UNAUTHENTICATED_CHANNEL_NAME } from '../../textsecure/SocketManager';
|
import { UNAUTHENTICATED_CHANNEL_NAME } from '../../textsecure/SocketManager';
|
||||||
|
|
||||||
|
@ -340,12 +337,9 @@ ipc.on('show-group-via-link', (_event, info) => {
|
||||||
drop(window.Events.showGroupViaLink?.(info.value));
|
drop(window.Events.showGroupViaLink?.(info.value));
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('start-call-lobby', (_event, { conversationId }) => {
|
ipc.on('start-call-lobby', (_event, info) => {
|
||||||
window.IPC.showWindow();
|
window.IPC.showWindow();
|
||||||
window.reduxActions?.calling?.startCallingLobby({
|
window.Events.startCallingLobbyViaToken(info.token);
|
||||||
conversationId,
|
|
||||||
isVideoCall: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('start-call-link', (_event, { key }) => {
|
ipc.on('start-call-link', (_event, { key }) => {
|
||||||
|
@ -362,15 +356,12 @@ ipc.on('cancel-presenting', () => {
|
||||||
window.reduxActions?.calling?.cancelPresenting();
|
window.reduxActions?.calling?.cancelPresenting();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on(
|
ipc.on('show-conversation-via-token', (_event, token: string) => {
|
||||||
'show-conversation-via-notification',
|
const { showConversationViaToken } = window.Events;
|
||||||
(_event, data: NotificationClickData) => {
|
if (showConversationViaToken) {
|
||||||
const { showConversationViaNotification } = window.Events;
|
void showConversationViaToken(token);
|
||||||
if (showConversationViaNotification) {
|
|
||||||
void showConversationViaNotification(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
ipc.on('show-conversation-via-signal.me', (_event, info) => {
|
ipc.on('show-conversation-via-signal.me', (_event, info) => {
|
||||||
const { kind, value } = info;
|
const { kind, value } = info;
|
||||||
strictAssert(typeof kind === 'string', 'Got an invalid kind over IPC');
|
strictAssert(typeof kind === 'string', 'Got an invalid kind over IPC');
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue