New sticker creator button
This commit is contained in:
parent
85adb39d31
commit
fad0529080
25 changed files with 442 additions and 11 deletions
|
@ -2947,6 +2947,18 @@
|
||||||
"message": "Conversation marked unread",
|
"message": "Conversation marked unread",
|
||||||
"description": "A toast that shows up when user marks a conversation as unread"
|
"description": "A toast that shows up when user marks a conversation as unread"
|
||||||
},
|
},
|
||||||
|
"icu:AuthArtCreator--dialog--message": {
|
||||||
|
"messageformat": "Would you like to open Signal Sticker Pack Creator?",
|
||||||
|
"description": "A body of the dialog that is presented when user tries to open Signal Sticker Pack Creator from a link"
|
||||||
|
},
|
||||||
|
"icu:AuthArtCreator--dialog--confirm": {
|
||||||
|
"messageformat": "Confirm",
|
||||||
|
"description": "A buttle title for confirming Signal Sticker Pack Creator dialog"
|
||||||
|
},
|
||||||
|
"icu:AuthArtCreator--dialog--dismiss": {
|
||||||
|
"messageformat": "Dismiss",
|
||||||
|
"description": "A buttle title for dismissing Signal Sticker Pack Creator dialog"
|
||||||
|
},
|
||||||
"StickerCreator--title": {
|
"StickerCreator--title": {
|
||||||
"message": "Sticker pack creator",
|
"message": "Sticker pack creator",
|
||||||
"description": "The title of the Sticker Pack Creator window"
|
"description": "The title of the Sticker Pack Creator window"
|
||||||
|
@ -3147,6 +3159,10 @@
|
||||||
"message": "Please set up Signal on your phone and desktop to use the Sticker Pack Creator",
|
"message": "Please set up Signal on your phone and desktop to use the Sticker Pack Creator",
|
||||||
"description": "The error message which appears when the user has not linked their account and attempts to use the Sticker Creator"
|
"description": "The error message which appears when the user has not linked their account and attempts to use the Sticker Creator"
|
||||||
},
|
},
|
||||||
|
"icu:ArtCreator--Authentication--error": {
|
||||||
|
"messageformat": "Please set up Signal on your phone and desktop to use the Sticker Art Creator",
|
||||||
|
"description": "The error message which appears when the user has not linked their account and attempts to use the Art Creator"
|
||||||
|
},
|
||||||
"Reactions--remove": {
|
"Reactions--remove": {
|
||||||
"message": "Remove reaction",
|
"message": "Remove reaction",
|
||||||
"describe": "Shown when you want to remove a reaction you've made"
|
"describe": "Shown when you want to remove a reaction you've made"
|
||||||
|
|
36
app/main.ts
36
app/main.ts
|
@ -417,6 +417,7 @@ async function prepareUrl(
|
||||||
storageUrl: config.get<string>('storageUrl'),
|
storageUrl: config.get<string>('storageUrl'),
|
||||||
updatesUrl: config.get<string>('updatesUrl'),
|
updatesUrl: config.get<string>('updatesUrl'),
|
||||||
resourcesUrl: config.get<string>('resourcesUrl'),
|
resourcesUrl: config.get<string>('resourcesUrl'),
|
||||||
|
artCreatorUrl: config.get<string>('artCreatorUrl'),
|
||||||
cdnUrl0: config.get<ConfigType>('cdn').get<string>('0'),
|
cdnUrl0: config.get<ConfigType>('cdn').get<string>('0'),
|
||||||
cdnUrl2: config.get<ConfigType>('cdn').get<string>('2'),
|
cdnUrl2: config.get<ConfigType>('cdn').get<string>('2'),
|
||||||
certificateAuthority: config.get<string>('certificateAuthority'),
|
certificateAuthority: config.get<string>('certificateAuthority'),
|
||||||
|
@ -1004,6 +1005,11 @@ ipc.handle('database-ready', async () => {
|
||||||
getLogger().info('sending `database-ready`');
|
getLogger().info('sending `database-ready`');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipc.handle('open-art-creator', (_event, { username, password }) => {
|
||||||
|
const baseUrl = config.get<string>('artCreatorUrl');
|
||||||
|
drop(shell.openExternal(`${baseUrl}/#auth=${username}:${password}`));
|
||||||
|
});
|
||||||
|
|
||||||
ipc.on('show-window', () => {
|
ipc.on('show-window', () => {
|
||||||
showWindow();
|
showWindow();
|
||||||
});
|
});
|
||||||
|
@ -1409,6 +1415,25 @@ async function showStickerCreator() {
|
||||||
await safeLoadURL(stickerCreatorWindow, await appUrl);
|
await safeLoadURL(stickerCreatorWindow, await appUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openArtCreator() {
|
||||||
|
if (!(await getIsLinked())) {
|
||||||
|
const message = getResolvedMessagesLocale().i18n(
|
||||||
|
'icu:ArtCreator--Authentication--error'
|
||||||
|
);
|
||||||
|
|
||||||
|
await dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('open-art-creator');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let debugLogWindow: BrowserWindow | undefined;
|
let debugLogWindow: BrowserWindow | undefined;
|
||||||
async function showDebugLogWindow() {
|
async function showDebugLogWindow() {
|
||||||
if (debugLogWindow) {
|
if (debugLogWindow) {
|
||||||
|
@ -1963,6 +1988,7 @@ function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
forceUpdate,
|
forceUpdate,
|
||||||
|
openArtCreator,
|
||||||
openContactUs,
|
openContactUs,
|
||||||
openForums,
|
openForums,
|
||||||
openJoinTheBeta,
|
openJoinTheBeta,
|
||||||
|
@ -2348,6 +2374,14 @@ function handleSgnlHref(incomingHref: string) {
|
||||||
? Buffer.from(packKeyHex, 'hex').toString('base64')
|
? Buffer.from(packKeyHex, 'hex').toString('base64')
|
||||||
: '';
|
: '';
|
||||||
mainWindow.webContents.send('show-sticker-pack', { packId, packKey });
|
mainWindow.webContents.send('show-sticker-pack', { packId, packKey });
|
||||||
|
} else if (command === 'art-auth') {
|
||||||
|
const token = args?.get('token');
|
||||||
|
const pubKeyBase64 = args?.get('pub_key');
|
||||||
|
|
||||||
|
mainWindow.webContents.send('authorize-art-creator', {
|
||||||
|
token,
|
||||||
|
pubKeyBase64,
|
||||||
|
});
|
||||||
} else if (command === 'signal.group' && hash) {
|
} else if (command === 'signal.group' && hash) {
|
||||||
getLogger().info('Showing group from sgnl protocol link');
|
getLogger().info('Showing group from sgnl protocol link');
|
||||||
mainWindow.webContents.send('show-group-via-link', { hash });
|
mainWindow.webContents.send('show-group-via-link', { hash });
|
||||||
|
@ -2549,6 +2583,8 @@ ipc.handle('getMenuOptions', async () => {
|
||||||
ipc.handle('executeMenuAction', async (_event, action: MenuActionType) => {
|
ipc.handle('executeMenuAction', async (_event, action: MenuActionType) => {
|
||||||
if (action === 'forceUpdate') {
|
if (action === 'forceUpdate') {
|
||||||
drop(forceUpdate());
|
drop(forceUpdate());
|
||||||
|
} else if (action === 'openArtCreator') {
|
||||||
|
drop(openArtCreator());
|
||||||
} else if (action === 'openContactUs') {
|
} else if (action === 'openContactUs') {
|
||||||
openContactUs();
|
openContactUs();
|
||||||
} else if (action === 'openForums') {
|
} else if (action === 'openForums') {
|
||||||
|
|
|
@ -38,6 +38,7 @@ export const createTemplate = (
|
||||||
showKeyboardShortcuts,
|
showKeyboardShortcuts,
|
||||||
showSettings,
|
showSettings,
|
||||||
showStickerCreator,
|
showStickerCreator,
|
||||||
|
openArtCreator,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const template: MenuListType = [
|
const template: MenuListType = [
|
||||||
|
@ -46,7 +47,7 @@ export const createTemplate = (
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: i18n('mainMenuCreateStickers'),
|
label: i18n('mainMenuCreateStickers'),
|
||||||
click: showStickerCreator,
|
click: isProduction ? showStickerCreator : openArtCreator,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n('mainMenuSettings'),
|
label: i18n('mainMenuSettings'),
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"contentProxyUrl": "http://contentproxy.signal.org:443",
|
"contentProxyUrl": "http://contentproxy.signal.org:443",
|
||||||
"updatesUrl": "https://updates2.signal.org/desktop",
|
"updatesUrl": "https://updates2.signal.org/desktop",
|
||||||
"resourcesUrl": "https://updates2.signal.org",
|
"resourcesUrl": "https://updates2.signal.org",
|
||||||
|
"artCreatorUrl": "https://create.staging.signal.art",
|
||||||
"updatesPublicKey": "05fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401",
|
"updatesPublicKey": "05fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401",
|
||||||
"sfuUrl": "https://sfu.voip.signal.org/",
|
"sfuUrl": "https://sfu.voip.signal.org/",
|
||||||
"updatesEnabled": false,
|
"updatesEnabled": false,
|
||||||
|
|
14
protos/ArtCreator.proto
Normal file
14
protos/ArtCreator.proto
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
package signalservice;
|
||||||
|
|
||||||
|
message ArtProvisioningEnvelope {
|
||||||
|
optional bytes publicKey = 1;
|
||||||
|
optional bytes ciphertext = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ArtProvisioningMessage {
|
||||||
|
optional string username = 1;
|
||||||
|
optional string password = 2;
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ const WebAPI = initializeWebAPI({
|
||||||
storageUrl: config.storageUrl,
|
storageUrl: config.storageUrl,
|
||||||
updatesUrl: config.updatesUrl,
|
updatesUrl: config.updatesUrl,
|
||||||
resourcesUrl: config.resourcesUrl,
|
resourcesUrl: config.resourcesUrl,
|
||||||
|
artCreatorUrl: config.artCreatorUrl,
|
||||||
directoryConfig: config.directoryConfig,
|
directoryConfig: config.directoryConfig,
|
||||||
cdnUrlObject: {
|
cdnUrlObject: {
|
||||||
0: config.cdnUrl0,
|
0: config.cdnUrl0,
|
||||||
|
|
|
@ -3522,7 +3522,7 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-spinner__circle--on-captcha {
|
.module-spinner__circle--on-primary-button {
|
||||||
background-color: $color-white-alpha-40;
|
background-color: $color-white-alpha-40;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3540,7 +3540,7 @@ button.module-image__border-overlay:focus {
|
||||||
.module-spinner__arc--on-avatar {
|
.module-spinner__arc--on-avatar {
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
}
|
}
|
||||||
.module-spinner__arc--on-captcha {
|
.module-spinner__arc--on-primary-button {
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ export function CaptchaDialog(props: Readonly<PropsType>): JSX.Element {
|
||||||
variant={ButtonVariant.Primary}
|
variant={ButtonVariant.Primary}
|
||||||
>
|
>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<Spinner size="22px" svgSize="small" direction="on-captcha" />
|
<Spinner size="22px" svgSize="small" direction="on-primary-button" />
|
||||||
) : (
|
) : (
|
||||||
'Continue'
|
'Continue'
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -10,11 +10,13 @@ import { ModalHost } from './ModalHost';
|
||||||
import { ModalPage } from './Modal';
|
import { ModalPage } from './Modal';
|
||||||
import type { Theme } from '../util/theme';
|
import type { Theme } from '../util/theme';
|
||||||
import { useAnimated } from '../hooks/useAnimated';
|
import { useAnimated } from '../hooks/useAnimated';
|
||||||
|
import { Spinner } from './Spinner';
|
||||||
|
|
||||||
export type ActionSpec = {
|
export type ActionSpec = {
|
||||||
text: string;
|
text: string;
|
||||||
action: () => unknown;
|
action: () => unknown;
|
||||||
style?: 'affirmative' | 'negative';
|
style?: 'affirmative' | 'negative';
|
||||||
|
autoClose?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OwnProps = Readonly<{
|
export type OwnProps = Readonly<{
|
||||||
|
@ -22,6 +24,7 @@ export type OwnProps = Readonly<{
|
||||||
dialogName: string;
|
dialogName: string;
|
||||||
cancelButtonVariant?: ButtonVariant;
|
cancelButtonVariant?: ButtonVariant;
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
|
isSpinning?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
hasXButton?: boolean;
|
hasXButton?: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
@ -65,6 +68,7 @@ export const ConfirmationDialog = React.memo(function ConfirmationDialogInner({
|
||||||
children,
|
children,
|
||||||
hasXButton,
|
hasXButton,
|
||||||
i18n,
|
i18n,
|
||||||
|
isSpinning,
|
||||||
moduleClassName,
|
moduleClassName,
|
||||||
noMouseClose,
|
noMouseClose,
|
||||||
noDefaultCancelButton,
|
noDefaultCancelButton,
|
||||||
|
@ -99,7 +103,7 @@ export const ConfirmationDialog = React.memo(function ConfirmationDialogInner({
|
||||||
|
|
||||||
const footer = (
|
const footer = (
|
||||||
<>
|
<>
|
||||||
{!noDefaultCancelButton ? (
|
{!isSpinning && !noDefaultCancelButton ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
ref={focusRef}
|
ref={focusRef}
|
||||||
|
@ -114,14 +118,25 @@ export const ConfirmationDialog = React.memo(function ConfirmationDialogInner({
|
||||||
{actions.map((action, i) => (
|
{actions.map((action, i) => (
|
||||||
<Button
|
<Button
|
||||||
key={action.text}
|
key={action.text}
|
||||||
|
disabled={isSpinning}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
action.action();
|
action.action();
|
||||||
close();
|
if (action.autoClose !== false) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
data-action={i}
|
data-action={i}
|
||||||
variant={getButtonVariant(action.style)}
|
variant={getButtonVariant(action.style)}
|
||||||
>
|
>
|
||||||
{action.text}
|
{isSpinning ? (
|
||||||
|
<Spinner
|
||||||
|
size="22px"
|
||||||
|
svgSize="small"
|
||||||
|
direction="on-primary-button"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
action.text
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import type {
|
||||||
ForwardMessagePropsType,
|
ForwardMessagePropsType,
|
||||||
UserNotFoundModalStateType,
|
UserNotFoundModalStateType,
|
||||||
SafetyNumberChangedBlockingDataType,
|
SafetyNumberChangedBlockingDataType,
|
||||||
|
AuthorizeArtCreatorDataType,
|
||||||
} from '../state/ducks/globalModals';
|
} from '../state/ducks/globalModals';
|
||||||
import type { LocalizerType, ThemeType } from '../types/Util';
|
import type { LocalizerType, ThemeType } from '../types/Util';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
@ -66,6 +67,11 @@ export type PropsType = {
|
||||||
// WhatsNewModal
|
// WhatsNewModal
|
||||||
isWhatsNewVisible: boolean;
|
isWhatsNewVisible: boolean;
|
||||||
hideWhatsNewModal: () => unknown;
|
hideWhatsNewModal: () => unknown;
|
||||||
|
// AuthArtCreatorModal
|
||||||
|
authArtCreatorData?: AuthorizeArtCreatorDataType;
|
||||||
|
isAuthorizingArtCreator?: boolean;
|
||||||
|
cancelAuthorizeArtCreator: () => unknown;
|
||||||
|
confirmAuthorizeArtCreator: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function GlobalModalContainer({
|
export function GlobalModalContainer({
|
||||||
|
@ -110,6 +116,11 @@ export function GlobalModalContainer({
|
||||||
// WhatsNewModal
|
// WhatsNewModal
|
||||||
hideWhatsNewModal,
|
hideWhatsNewModal,
|
||||||
isWhatsNewVisible,
|
isWhatsNewVisible,
|
||||||
|
// AuthArtCreatorModal
|
||||||
|
authArtCreatorData,
|
||||||
|
isAuthorizingArtCreator,
|
||||||
|
cancelAuthorizeArtCreator,
|
||||||
|
confirmAuthorizeArtCreator,
|
||||||
}: PropsType): JSX.Element | null {
|
}: PropsType): JSX.Element | null {
|
||||||
// We want the following dialogs to show in this order:
|
// We want the following dialogs to show in this order:
|
||||||
// 1. Errors
|
// 1. Errors
|
||||||
|
@ -200,5 +211,28 @@ export function GlobalModalContainer({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authArtCreatorData) {
|
||||||
|
return (
|
||||||
|
<ConfirmationDialog
|
||||||
|
dialogName="GlobalModalContainer.authArtCreator"
|
||||||
|
cancelText={i18n('icu:AuthArtCreator--dialog--dismiss')}
|
||||||
|
cancelButtonVariant={ButtonVariant.Secondary}
|
||||||
|
i18n={i18n}
|
||||||
|
isSpinning={isAuthorizingArtCreator}
|
||||||
|
onClose={cancelAuthorizeArtCreator}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
text: i18n('icu:AuthArtCreator--dialog--confirm'),
|
||||||
|
style: 'affirmative',
|
||||||
|
action: confirmAuthorizeArtCreator,
|
||||||
|
autoClose: false,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{i18n('icu:AuthArtCreator--dialog--message')}
|
||||||
|
</ConfirmationDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export const SpinnerDirections = [
|
||||||
'outgoing',
|
'outgoing',
|
||||||
'incoming',
|
'incoming',
|
||||||
'on-background',
|
'on-background',
|
||||||
'on-captcha',
|
'on-primary-button',
|
||||||
'on-progress-dialog',
|
'on-progress-dialog',
|
||||||
'on-avatar',
|
'on-avatar',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
|
@ -226,6 +226,7 @@ export function TitleBarContainer(props: PropsType): JSX.Element {
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
forceUpdate: () => executeMenuAction('forceUpdate'),
|
forceUpdate: () => executeMenuAction('forceUpdate'),
|
||||||
|
openArtCreator: () => executeMenuAction('openArtCreator'),
|
||||||
openContactUs: () => executeMenuAction('openContactUs'),
|
openContactUs: () => executeMenuAction('openContactUs'),
|
||||||
openForums: () => executeMenuAction('openForums'),
|
openForums: () => executeMenuAction('openForums'),
|
||||||
openJoinTheBeta: () => executeMenuAction('openJoinTheBeta'),
|
openJoinTheBeta: () => executeMenuAction('openJoinTheBeta'),
|
||||||
|
|
|
@ -12,13 +12,20 @@ import type { StateType as RootStateType } from '../reducer';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
import * as SingleServePromise from '../../services/singleServePromise';
|
import * as SingleServePromise from '../../services/singleServePromise';
|
||||||
import * as Stickers from '../../types/Stickers';
|
import * as Stickers from '../../types/Stickers';
|
||||||
|
import * as Errors from '../../types/errors';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { getMessageById } from '../../messages/getMessageById';
|
||||||
import { getMessagePropsSelector } from '../selectors/message';
|
import { getMessagePropsSelector } from '../selectors/message';
|
||||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||||
import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper';
|
import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper';
|
||||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
import { isGroupV1 } from '../../util/whatTypeOfConversation';
|
import { isGroupV1 } from '../../util/whatTypeOfConversation';
|
||||||
|
import { authorizeArtCreator } from '../../textsecure/authorizeArtCreator';
|
||||||
|
import type { AuthorizeArtCreatorOptionsType } from '../../textsecure/authorizeArtCreator';
|
||||||
import { getGroupMigrationMembers } from '../../groups';
|
import { getGroupMigrationMembers } from '../../groups';
|
||||||
|
import * as log from '../../logging/log';
|
||||||
|
import { ToastType } from '../../types/Toast';
|
||||||
|
import { SHOW_TOAST } from './toast';
|
||||||
|
import type { ShowToastActionType } from './toast';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -29,6 +36,8 @@ export type SafetyNumberChangedBlockingDataType = ReadonlyDeep<{
|
||||||
promiseUuid: UUIDStringType;
|
promiseUuid: UUIDStringType;
|
||||||
source?: SafetyNumberChangeSource;
|
source?: SafetyNumberChangeSource;
|
||||||
}>;
|
}>;
|
||||||
|
export type AuthorizeArtCreatorDataType =
|
||||||
|
ReadonlyDeep<AuthorizeArtCreatorOptionsType>;
|
||||||
|
|
||||||
type MigrateToGV2PropsType = ReadonlyDeep<{
|
type MigrateToGV2PropsType = ReadonlyDeep<{
|
||||||
areWeInvited: boolean;
|
areWeInvited: boolean;
|
||||||
|
@ -56,6 +65,8 @@ export type GlobalModalsStateType = ReadonlyDeep<{
|
||||||
safetyNumberChangedBlockingData?: SafetyNumberChangedBlockingDataType;
|
safetyNumberChangedBlockingData?: SafetyNumberChangedBlockingDataType;
|
||||||
safetyNumberModalContactId?: string;
|
safetyNumberModalContactId?: string;
|
||||||
stickerPackPreviewId?: string;
|
stickerPackPreviewId?: string;
|
||||||
|
isAuthorizingArtCreator?: boolean;
|
||||||
|
authArtCreatorData?: AuthorizeArtCreatorDataType;
|
||||||
userNotFoundModalState?: UserNotFoundModalStateType;
|
userNotFoundModalState?: UserNotFoundModalStateType;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
@ -89,6 +100,12 @@ const CLOSE_ERROR_MODAL = 'globalModals/CLOSE_ERROR_MODAL';
|
||||||
const SHOW_ERROR_MODAL = 'globalModals/SHOW_ERROR_MODAL';
|
const SHOW_ERROR_MODAL = 'globalModals/SHOW_ERROR_MODAL';
|
||||||
const CLOSE_SHORTCUT_GUIDE_MODAL = 'globalModals/CLOSE_SHORTCUT_GUIDE_MODAL';
|
const CLOSE_SHORTCUT_GUIDE_MODAL = 'globalModals/CLOSE_SHORTCUT_GUIDE_MODAL';
|
||||||
const SHOW_SHORTCUT_GUIDE_MODAL = 'globalModals/SHOW_SHORTCUT_GUIDE_MODAL';
|
const SHOW_SHORTCUT_GUIDE_MODAL = 'globalModals/SHOW_SHORTCUT_GUIDE_MODAL';
|
||||||
|
const SHOW_AUTH_ART_CREATOR = 'globalModals/SHOW_AUTH_ART_CREATOR';
|
||||||
|
const CANCEL_AUTH_ART_CREATOR = 'globalModals/CANCEL_AUTH_ART_CREATOR';
|
||||||
|
const CONFIRM_AUTH_ART_CREATOR_PENDING =
|
||||||
|
'globalModals/CONFIRM_AUTH_ART_CREATOR_PENDING';
|
||||||
|
const CONFIRM_AUTH_ART_CREATOR_FULFILLED =
|
||||||
|
'globalModals/CONFIRM_AUTH_ART_CREATOR_FULFILLED';
|
||||||
|
|
||||||
export type ContactModalStateType = ReadonlyDeep<{
|
export type ContactModalStateType = ReadonlyDeep<{
|
||||||
contactId: string;
|
contactId: string;
|
||||||
|
@ -216,6 +233,23 @@ type ShowShortcutGuideModalActionType = ReadonlyDeep<{
|
||||||
type: typeof SHOW_SHORTCUT_GUIDE_MODAL;
|
type: typeof SHOW_SHORTCUT_GUIDE_MODAL;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type ShowAuthArtCreatorActionType = ReadonlyDeep<{
|
||||||
|
type: typeof SHOW_AUTH_ART_CREATOR;
|
||||||
|
payload: AuthorizeArtCreatorDataType;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type CancelAuthArtCreatorActionType = ReadonlyDeep<{
|
||||||
|
type: typeof CANCEL_AUTH_ART_CREATOR;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type ConfirmAuthArtCreatorPendingActionType = ReadonlyDeep<{
|
||||||
|
type: typeof CONFIRM_AUTH_ART_CREATOR_PENDING;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type ConfirmAuthArtCreatorFulfilledActionType = ReadonlyDeep<{
|
||||||
|
type: typeof CONFIRM_AUTH_ART_CREATOR_FULFILLED;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type GlobalModalsActionType = ReadonlyDeep<
|
export type GlobalModalsActionType = ReadonlyDeep<
|
||||||
| StartMigrationToGV2ActionType
|
| StartMigrationToGV2ActionType
|
||||||
| CloseGV2MigrationDialogActionType
|
| CloseGV2MigrationDialogActionType
|
||||||
|
@ -235,6 +269,10 @@ export type GlobalModalsActionType = ReadonlyDeep<
|
||||||
| ShowErrorModalActionType
|
| ShowErrorModalActionType
|
||||||
| CloseShortcutGuideModalActionType
|
| CloseShortcutGuideModalActionType
|
||||||
| ShowShortcutGuideModalActionType
|
| ShowShortcutGuideModalActionType
|
||||||
|
| CancelAuthArtCreatorActionType
|
||||||
|
| ConfirmAuthArtCreatorPendingActionType
|
||||||
|
| ConfirmAuthArtCreatorFulfilledActionType
|
||||||
|
| ShowAuthArtCreatorActionType
|
||||||
| ToggleForwardMessageModalActionType
|
| ToggleForwardMessageModalActionType
|
||||||
| ToggleProfileEditorActionType
|
| ToggleProfileEditorActionType
|
||||||
| ToggleProfileEditorErrorActionType
|
| ToggleProfileEditorErrorActionType
|
||||||
|
@ -270,6 +308,9 @@ export const actions = {
|
||||||
showErrorModal,
|
showErrorModal,
|
||||||
closeShortcutGuideModal,
|
closeShortcutGuideModal,
|
||||||
showShortcutGuideModal,
|
showShortcutGuideModal,
|
||||||
|
showAuthorizeArtCreator,
|
||||||
|
cancelAuthorizeArtCreator,
|
||||||
|
confirmAuthorizeArtCreator,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGlobalModalActions = (): BoundActionCreatorsMapObject<
|
export const useGlobalModalActions = (): BoundActionCreatorsMapObject<
|
||||||
|
@ -540,6 +581,73 @@ function showShortcutGuideModal(): ShowShortcutGuideModalActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cancelAuthorizeArtCreator(): ThunkAction<
|
||||||
|
void,
|
||||||
|
RootStateType,
|
||||||
|
unknown,
|
||||||
|
CancelAuthArtCreatorActionType
|
||||||
|
> {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const data = getState().globalModals.authArtCreatorData;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: CANCEL_AUTH_ART_CREATOR,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showAuthorizeArtCreator(
|
||||||
|
data: AuthorizeArtCreatorDataType
|
||||||
|
): ShowAuthArtCreatorActionType {
|
||||||
|
return {
|
||||||
|
type: SHOW_AUTH_ART_CREATOR,
|
||||||
|
payload: data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function confirmAuthorizeArtCreator(): ThunkAction<
|
||||||
|
void,
|
||||||
|
RootStateType,
|
||||||
|
unknown,
|
||||||
|
| ConfirmAuthArtCreatorPendingActionType
|
||||||
|
| ConfirmAuthArtCreatorFulfilledActionType
|
||||||
|
| CancelAuthArtCreatorActionType
|
||||||
|
| ShowToastActionType
|
||||||
|
> {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const data = getState().globalModals.authArtCreatorData;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
dispatch({ type: CANCEL_AUTH_ART_CREATOR });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: CONFIRM_AUTH_ART_CREATOR_PENDING,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authorizeArtCreator(data);
|
||||||
|
} catch (err) {
|
||||||
|
log.error('authorizeArtCreator failed', Errors.toLogFormat(err));
|
||||||
|
dispatch({
|
||||||
|
type: SHOW_TOAST,
|
||||||
|
payload: {
|
||||||
|
toastType: ToastType.Error,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: CONFIRM_AUTH_ART_CREATOR_FULFILLED,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Reducer
|
// Reducer
|
||||||
|
|
||||||
export function getEmptyState(): GlobalModalsStateType {
|
export function getEmptyState(): GlobalModalsStateType {
|
||||||
|
@ -718,5 +826,35 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === CANCEL_AUTH_ART_CREATOR) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authArtCreatorData: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === SHOW_AUTH_ART_CREATOR) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isAuthorizingArtCreator: false,
|
||||||
|
authArtCreatorData: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === CONFIRM_AUTH_ART_CREATOR_PENDING) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isAuthorizingArtCreator: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === CONFIRM_AUTH_ART_CREATOR_FULFILLED) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isAuthorizingArtCreator: false,
|
||||||
|
authArtCreatorData: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
safetyNumberModalContactId,
|
safetyNumberModalContactId,
|
||||||
stickerPackPreviewId,
|
stickerPackPreviewId,
|
||||||
userNotFoundModalState,
|
userNotFoundModalState,
|
||||||
|
isAuthorizingArtCreator,
|
||||||
|
authArtCreatorData,
|
||||||
} = useSelector<StateType, GlobalModalsStateType>(
|
} = useSelector<StateType, GlobalModalsStateType>(
|
||||||
state => state.globalModals
|
state => state.globalModals
|
||||||
);
|
);
|
||||||
|
@ -75,6 +77,8 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
hideWhatsNewModal,
|
hideWhatsNewModal,
|
||||||
hideUserNotFoundModal,
|
hideUserNotFoundModal,
|
||||||
toggleSignalConnectionsModal,
|
toggleSignalConnectionsModal,
|
||||||
|
cancelAuthorizeArtCreator,
|
||||||
|
confirmAuthorizeArtCreator,
|
||||||
} = useGlobalModalActions();
|
} = useGlobalModalActions();
|
||||||
|
|
||||||
const renderAddUserToAnotherGroup = useCallback(() => {
|
const renderAddUserToAnotherGroup = useCallback(() => {
|
||||||
|
@ -143,6 +147,10 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
theme={theme}
|
theme={theme}
|
||||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||||
userNotFoundModalState={userNotFoundModalState}
|
userNotFoundModalState={userNotFoundModalState}
|
||||||
|
isAuthorizingArtCreator={isAuthorizingArtCreator}
|
||||||
|
authArtCreatorData={authArtCreatorData}
|
||||||
|
cancelAuthorizeArtCreator={cancelAuthorizeArtCreator}
|
||||||
|
confirmAuthorizeArtCreator={confirmAuthorizeArtCreator}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { load as loadLocale } from '../../../app/locale';
|
||||||
import type { MenuListType } from '../../types/menu';
|
import type { MenuListType } from '../../types/menu';
|
||||||
|
|
||||||
const forceUpdate = stub();
|
const forceUpdate = stub();
|
||||||
|
const openArtCreator = stub();
|
||||||
const openContactUs = stub();
|
const openContactUs = stub();
|
||||||
const openForums = stub();
|
const openForums = stub();
|
||||||
const openJoinTheBeta = stub();
|
const openJoinTheBeta = stub();
|
||||||
|
@ -213,6 +214,7 @@ describe('createTemplate', () => {
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
forceUpdate,
|
forceUpdate,
|
||||||
|
openArtCreator,
|
||||||
openContactUs,
|
openContactUs,
|
||||||
openForums,
|
openForums,
|
||||||
openJoinTheBeta,
|
openJoinTheBeta,
|
||||||
|
|
|
@ -34,6 +34,7 @@ const JITTER = 5 * durations.SECOND;
|
||||||
|
|
||||||
export type SocketManagerOptions = Readonly<{
|
export type SocketManagerOptions = Readonly<{
|
||||||
url: string;
|
url: string;
|
||||||
|
artCreatorUrl: string;
|
||||||
certificateAuthority: string;
|
certificateAuthority: string;
|
||||||
version: string;
|
version: string;
|
||||||
proxyUrl?: string;
|
proxyUrl?: string;
|
||||||
|
@ -276,6 +277,27 @@ export class SocketManager extends EventListener {
|
||||||
}).getResult();
|
}).getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates new WebSocket for Art Creator provisioning
|
||||||
|
public async connectExternalSocket({
|
||||||
|
url,
|
||||||
|
extraHeaders,
|
||||||
|
}: {
|
||||||
|
url: string;
|
||||||
|
extraHeaders?: Record<string, string>;
|
||||||
|
}): Promise<WebSocket> {
|
||||||
|
return connectWebSocket({
|
||||||
|
name: 'art-creator-provisioning',
|
||||||
|
url,
|
||||||
|
version: this.options.version,
|
||||||
|
proxyAgent: this.proxyAgent,
|
||||||
|
extraHeaders,
|
||||||
|
|
||||||
|
createResource(socket: WebSocket): WebSocket {
|
||||||
|
return socket;
|
||||||
|
},
|
||||||
|
}).getResult();
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch-compatible wrapper around underlying unauthenticated/authenticated
|
// Fetch-compatible wrapper around underlying unauthenticated/authenticated
|
||||||
// websocket resources. This wrapper supports only limited number of features
|
// websocket resources. This wrapper supports only limited number of features
|
||||||
// of node-fetch despite being API compatible.
|
// of node-fetch despite being API compatible.
|
||||||
|
|
|
@ -15,6 +15,7 @@ import PQueue from 'p-queue';
|
||||||
import { v4 as getGuid } from 'uuid';
|
import { v4 as getGuid } from 'uuid';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { Readable } from 'stream';
|
import type { Readable } from 'stream';
|
||||||
|
import type { connection as WebSocket } from 'websocket';
|
||||||
|
|
||||||
import { assertDev, strictAssert } from '../util/assert';
|
import { assertDev, strictAssert } from '../util/assert';
|
||||||
import { isRecord } from '../util/isRecord';
|
import { isRecord } from '../util/isRecord';
|
||||||
|
@ -477,7 +478,6 @@ const URL_CALLS = {
|
||||||
config: 'v1/config',
|
config: 'v1/config',
|
||||||
deliveryCert: 'v1/certificate/delivery',
|
deliveryCert: 'v1/certificate/delivery',
|
||||||
devices: 'v1/devices',
|
devices: 'v1/devices',
|
||||||
directoryAuth: 'v1/directory/auth',
|
|
||||||
directoryAuthV2: 'v2/directory/auth',
|
directoryAuthV2: 'v2/directory/auth',
|
||||||
discovery: 'v1/discovery',
|
discovery: 'v1/discovery',
|
||||||
getGroupAvatarUpload: 'v1/groups/avatar/form',
|
getGroupAvatarUpload: 'v1/groups/avatar/form',
|
||||||
|
@ -486,6 +486,7 @@ const URL_CALLS = {
|
||||||
getOnboardingStoryManifest:
|
getOnboardingStoryManifest:
|
||||||
'dynamic/desktop/stories/onboarding/manifest.json',
|
'dynamic/desktop/stories/onboarding/manifest.json',
|
||||||
getStickerPackUpload: 'v1/sticker/pack/form',
|
getStickerPackUpload: 'v1/sticker/pack/form',
|
||||||
|
getArtAuth: 'v1/art/auth',
|
||||||
groupLog: 'v1/groups/logs',
|
groupLog: 'v1/groups/logs',
|
||||||
groupJoinedAtVersion: 'v1/groups/joined_at_version',
|
groupJoinedAtVersion: 'v1/groups/joined_at_version',
|
||||||
groups: 'v1/groups',
|
groups: 'v1/groups',
|
||||||
|
@ -537,7 +538,6 @@ const WEBSOCKET_CALLS = new Set<keyof typeof URL_CALLS>([
|
||||||
'supportUnauthenticatedDelivery',
|
'supportUnauthenticatedDelivery',
|
||||||
|
|
||||||
// Directory
|
// Directory
|
||||||
'directoryAuth',
|
|
||||||
'directoryAuthV2',
|
'directoryAuthV2',
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
|
@ -552,6 +552,7 @@ type InitializeOptionsType = {
|
||||||
storageUrl: string;
|
storageUrl: string;
|
||||||
updatesUrl: string;
|
updatesUrl: string;
|
||||||
resourcesUrl: string;
|
resourcesUrl: string;
|
||||||
|
artCreatorUrl: string;
|
||||||
cdnUrlObject: {
|
cdnUrlObject: {
|
||||||
readonly '0': string;
|
readonly '0': string;
|
||||||
readonly [propName: string]: string;
|
readonly [propName: string]: string;
|
||||||
|
@ -831,6 +832,13 @@ export type ReportMessageOptionsType = Readonly<{
|
||||||
token?: string;
|
token?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
const artAuthZod = z.object({
|
||||||
|
username: z.string(),
|
||||||
|
password: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ArtAuthType = z.infer<typeof artAuthZod>;
|
||||||
|
|
||||||
export type WebAPIType = {
|
export type WebAPIType = {
|
||||||
startRegistration(): unknown;
|
startRegistration(): unknown;
|
||||||
finishRegistration(baton: unknown): void;
|
finishRegistration(baton: unknown): void;
|
||||||
|
@ -848,6 +856,7 @@ export type WebAPIType = {
|
||||||
version: string,
|
version: string,
|
||||||
imageFiles: Array<string>
|
imageFiles: Array<string>
|
||||||
) => Promise<Array<Uint8Array>>;
|
) => Promise<Array<Uint8Array>>;
|
||||||
|
getArtAuth: () => Promise<ArtAuthType>;
|
||||||
getAttachment: (cdnKey: string, cdnNumber?: number) => Promise<Uint8Array>;
|
getAttachment: (cdnKey: string, cdnNumber?: number) => Promise<Uint8Array>;
|
||||||
getAvatar: (path: string) => Promise<Uint8Array>;
|
getAvatar: (path: string) => Promise<Uint8Array>;
|
||||||
getDevices: () => Promise<GetDevicesResultType>;
|
getDevices: () => Promise<GetDevicesResultType>;
|
||||||
|
@ -901,6 +910,7 @@ export type WebAPIType = {
|
||||||
getProvisioningResource: (
|
getProvisioningResource: (
|
||||||
handler: IRequestHandler
|
handler: IRequestHandler
|
||||||
) => Promise<WebSocketResource>;
|
) => Promise<WebSocketResource>;
|
||||||
|
getArtProvisioningSocket: (token: string) => Promise<WebSocket>;
|
||||||
getSenderCertificate: (
|
getSenderCertificate: (
|
||||||
withUuid?: boolean
|
withUuid?: boolean
|
||||||
) => Promise<GetSenderCertificateResultType>;
|
) => Promise<GetSenderCertificateResultType>;
|
||||||
|
@ -1073,6 +1083,7 @@ export function initialize({
|
||||||
storageUrl,
|
storageUrl,
|
||||||
updatesUrl,
|
updatesUrl,
|
||||||
resourcesUrl,
|
resourcesUrl,
|
||||||
|
artCreatorUrl,
|
||||||
directoryConfig,
|
directoryConfig,
|
||||||
cdnUrlObject,
|
cdnUrlObject,
|
||||||
certificateAuthority,
|
certificateAuthority,
|
||||||
|
@ -1092,6 +1103,9 @@ export function initialize({
|
||||||
if (!isString(resourcesUrl)) {
|
if (!isString(resourcesUrl)) {
|
||||||
throw new Error('WebAPI.initialize: Invalid updatesUrl (general)');
|
throw new Error('WebAPI.initialize: Invalid updatesUrl (general)');
|
||||||
}
|
}
|
||||||
|
if (!isString(artCreatorUrl)) {
|
||||||
|
throw new Error('WebAPI.initialize: Invalid artCreatorUrl');
|
||||||
|
}
|
||||||
if (!isObject(cdnUrlObject)) {
|
if (!isObject(cdnUrlObject)) {
|
||||||
throw new Error('WebAPI.initialize: Invalid cdnUrlObject');
|
throw new Error('WebAPI.initialize: Invalid cdnUrlObject');
|
||||||
}
|
}
|
||||||
|
@ -1139,6 +1153,7 @@ export function initialize({
|
||||||
|
|
||||||
const socketManager = new SocketManager({
|
const socketManager = new SocketManager({
|
||||||
url,
|
url,
|
||||||
|
artCreatorUrl,
|
||||||
certificateAuthority,
|
certificateAuthority,
|
||||||
version,
|
version,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
|
@ -1224,6 +1239,8 @@ export function initialize({
|
||||||
fetchLinkPreviewMetadata,
|
fetchLinkPreviewMetadata,
|
||||||
finishRegistration,
|
finishRegistration,
|
||||||
getAccountForUsername,
|
getAccountForUsername,
|
||||||
|
getArtAuth,
|
||||||
|
getArtProvisioningSocket,
|
||||||
getAttachment,
|
getAttachment,
|
||||||
getAvatar,
|
getAvatar,
|
||||||
getBadgeImageFile,
|
getBadgeImageFile,
|
||||||
|
@ -2982,6 +2999,15 @@ export function initialize({
|
||||||
return socketManager.getProvisioningResource(handler);
|
return socketManager.getProvisioningResource(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getArtProvisioningSocket(token: string): Promise<WebSocket> {
|
||||||
|
return socketManager.connectExternalSocket({
|
||||||
|
url: `${artCreatorUrl}/api/socket?token=${token}`,
|
||||||
|
extraHeaders: {
|
||||||
|
origin: artCreatorUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function cdsLookup({
|
async function cdsLookup({
|
||||||
e164s,
|
e164s,
|
||||||
acis = [],
|
acis = [],
|
||||||
|
@ -2995,5 +3021,19 @@ export function initialize({
|
||||||
returnAcisWithoutUaks,
|
returnAcisWithoutUaks,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Art
|
||||||
|
//
|
||||||
|
|
||||||
|
async function getArtAuth(): Promise<ArtAuthType> {
|
||||||
|
const response = await _ajax({
|
||||||
|
call: 'getArtAuth',
|
||||||
|
httpType: 'GET',
|
||||||
|
responseType: 'json',
|
||||||
|
});
|
||||||
|
|
||||||
|
return artAuthZod.parse(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ export type IResource = {
|
||||||
export type ConnectOptionsType<Resource extends IResource> = Readonly<{
|
export type ConnectOptionsType<Resource extends IResource> = Readonly<{
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
certificateAuthority: string;
|
certificateAuthority?: string;
|
||||||
version: string;
|
version: string;
|
||||||
proxyAgent?: ReturnType<typeof ProxyAgent>;
|
proxyAgent?: ReturnType<typeof ProxyAgent>;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
|
59
ts/textsecure/authorizeArtCreator.ts
Normal file
59
ts/textsecure/authorizeArtCreator.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { promisify } from 'util';
|
||||||
|
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
import { calculateAgreement, generateKeyPair } from '../Curve';
|
||||||
|
import { encryptAttachment, deriveSecrets } from '../Crypto';
|
||||||
|
import * as Bytes from '../Bytes';
|
||||||
|
|
||||||
|
const PROVISIONING_INFO = 'Art Service Provisioning Message';
|
||||||
|
|
||||||
|
export type AuthorizeArtCreatorOptionsType = Readonly<{
|
||||||
|
token: string;
|
||||||
|
pubKeyBase64: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export async function authorizeArtCreator({
|
||||||
|
token,
|
||||||
|
pubKeyBase64: theirPubKeyBase64,
|
||||||
|
}: AuthorizeArtCreatorOptionsType): Promise<void> {
|
||||||
|
const { server } = window.textsecure;
|
||||||
|
if (!server) {
|
||||||
|
throw new Error('Server not ready');
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth = await server.getArtAuth();
|
||||||
|
|
||||||
|
const ourKeys = generateKeyPair();
|
||||||
|
const theirPubKey = Bytes.fromBase64(theirPubKeyBase64);
|
||||||
|
|
||||||
|
const secret = calculateAgreement(theirPubKey, ourKeys.privKey);
|
||||||
|
const [aesKey, macKey] = deriveSecrets(
|
||||||
|
secret,
|
||||||
|
new Uint8Array(64),
|
||||||
|
Bytes.fromString(PROVISIONING_INFO)
|
||||||
|
);
|
||||||
|
const keys = Bytes.concatenate([aesKey, macKey]);
|
||||||
|
|
||||||
|
const { ciphertext } = encryptAttachment(
|
||||||
|
Proto.ArtProvisioningMessage.encode({
|
||||||
|
...auth,
|
||||||
|
}).finish(),
|
||||||
|
keys
|
||||||
|
);
|
||||||
|
|
||||||
|
const envelope = Proto.ArtProvisioningEnvelope.encode({
|
||||||
|
publicKey: ourKeys.pubKey,
|
||||||
|
ciphertext,
|
||||||
|
}).finish();
|
||||||
|
|
||||||
|
const socket = await server.getArtProvisioningSocket(token);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await promisify(socket.sendBytes).call(socket, Buffer.from(envelope));
|
||||||
|
} finally {
|
||||||
|
socket.close(1000, 'goodbye');
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ export type DirectoryConfigType = z.infer<typeof directoryConfigSchema>;
|
||||||
export const rendererConfigSchema = z.object({
|
export const rendererConfigSchema = z.object({
|
||||||
appInstance: configOptionalStringSchema,
|
appInstance: configOptionalStringSchema,
|
||||||
appStartInitialSpellcheckSetting: z.boolean(),
|
appStartInitialSpellcheckSetting: z.boolean(),
|
||||||
|
artCreatorUrl: configRequiredStringSchema,
|
||||||
buildCreation: z.number(),
|
buildCreation: z.number(),
|
||||||
buildExpiration: z.number(),
|
buildExpiration: z.number(),
|
||||||
cdnUrl0: configRequiredStringSchema,
|
cdnUrl0: configRequiredStringSchema,
|
||||||
|
|
|
@ -15,6 +15,7 @@ export type MenuOptionsType = Readonly<{
|
||||||
|
|
||||||
export type MenuActionsType = Readonly<{
|
export type MenuActionsType = Readonly<{
|
||||||
forceUpdate: () => unknown;
|
forceUpdate: () => unknown;
|
||||||
|
openArtCreator: () => unknown;
|
||||||
openContactUs: () => unknown;
|
openContactUs: () => unknown;
|
||||||
openForums: () => unknown;
|
openForums: () => unknown;
|
||||||
openJoinTheBeta: () => unknown;
|
openJoinTheBeta: () => unknown;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import type { SystemTraySetting } from '../types/SystemTraySetting';
|
||||||
import { parseSystemTraySetting } from '../types/SystemTraySetting';
|
import { parseSystemTraySetting } from '../types/SystemTraySetting';
|
||||||
|
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
|
import type { AuthorizeArtCreatorDataType } from '../state/ducks/globalModals';
|
||||||
import { calling } from '../services/calling';
|
import { calling } from '../services/calling';
|
||||||
import { getConversationsWithCustomColorSelector } from '../state/selectors/conversations';
|
import { getConversationsWithCustomColorSelector } from '../state/selectors/conversations';
|
||||||
import { getCustomColors } from '../state/selectors/items';
|
import { getCustomColors } from '../state/selectors/items';
|
||||||
|
@ -85,6 +86,7 @@ export type IPCEventsValuesType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IPCEventsCallbacksType = {
|
export type IPCEventsCallbacksType = {
|
||||||
|
openArtCreator(): Promise<void>;
|
||||||
getAvailableIODevices(): Promise<{
|
getAvailableIODevices(): Promise<{
|
||||||
availableCameras: Array<
|
availableCameras: Array<
|
||||||
Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'>
|
Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'>
|
||||||
|
@ -94,6 +96,7 @@ export type IPCEventsCallbacksType = {
|
||||||
}>;
|
}>;
|
||||||
addCustomColor: (customColor: CustomColorType) => void;
|
addCustomColor: (customColor: CustomColorType) => void;
|
||||||
addDarkOverlay: () => void;
|
addDarkOverlay: () => void;
|
||||||
|
authorizeArtCreator: (data: AuthorizeArtCreatorDataType) => void;
|
||||||
deleteAllData: () => Promise<void>;
|
deleteAllData: () => Promise<void>;
|
||||||
deleteAllMyStories: () => Promise<void>;
|
deleteAllMyStories: () => Promise<void>;
|
||||||
closeDB: () => Promise<void>;
|
closeDB: () => Promise<void>;
|
||||||
|
@ -188,6 +191,15 @@ export function createIPCEvents(
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
openArtCreator: async () => {
|
||||||
|
const auth = await window.textsecure.server?.getArtAuth();
|
||||||
|
if (!auth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.openArtCreator(auth);
|
||||||
|
},
|
||||||
|
|
||||||
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
|
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
|
||||||
|
|
||||||
getZoomFactor: () => window.storage.get('zoomFactor', 1),
|
getZoomFactor: () => window.storage.get('zoomFactor', 1),
|
||||||
|
@ -439,6 +451,14 @@ export function createIPCEvents(
|
||||||
});
|
});
|
||||||
document.body.prepend(newOverlay);
|
document.body.prepend(newOverlay);
|
||||||
},
|
},
|
||||||
|
authorizeArtCreator: (data: AuthorizeArtCreatorDataType) => {
|
||||||
|
// We can get these events even if the user has never linked this instance.
|
||||||
|
if (!window.Signal.Util.Registration.everDone()) {
|
||||||
|
log.warn('authorizeArtCreator: Not registered, returning early');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.reduxActions.globalModals.showAuthorizeArtCreator(data);
|
||||||
|
},
|
||||||
removeDarkOverlay: () => {
|
removeDarkOverlay: () => {
|
||||||
const elems = document.querySelectorAll('.dark-overlay');
|
const elems = document.querySelectorAll('.dark-overlay');
|
||||||
|
|
||||||
|
|
1
ts/window.d.ts
vendored
1
ts/window.d.ts
vendored
|
@ -148,6 +148,7 @@ declare global {
|
||||||
localeMessages: { [key: string]: { message: string } };
|
localeMessages: { [key: string]: { message: string } };
|
||||||
|
|
||||||
isBehindProxy: () => boolean;
|
isBehindProxy: () => boolean;
|
||||||
|
openArtCreator: (opts: { username: string; password: string }) => void;
|
||||||
|
|
||||||
enterKeyboardMode: () => void;
|
enterKeyboardMode: () => void;
|
||||||
enterMouseMode: () => void;
|
enterMouseMode: () => void;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import * as log from '../../logging/log';
|
||||||
import * as Errors from '../../types/errors';
|
import * as Errors from '../../types/errors';
|
||||||
|
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
|
import { drop } from '../../util/drop';
|
||||||
|
|
||||||
// It is important to call this as early as possible
|
// It is important to call this as early as possible
|
||||||
window.i18n = SignalContext.i18n;
|
window.i18n = SignalContext.i18n;
|
||||||
|
@ -297,6 +298,24 @@ ipc.on('show-group-via-link', (_event, info) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipc.on('open-art-creator', () => {
|
||||||
|
drop(window.Events.openArtCreator());
|
||||||
|
});
|
||||||
|
window.openArtCreator = ({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
}: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}) => {
|
||||||
|
return ipc.invoke('open-art-creator', { username, password });
|
||||||
|
};
|
||||||
|
|
||||||
|
ipc.on('authorize-art-creator', (_event, info) => {
|
||||||
|
const { token, pubKeyBase64 } = info;
|
||||||
|
window.Events.authorizeArtCreator?.({ token, pubKeyBase64 });
|
||||||
|
});
|
||||||
|
|
||||||
ipc.on('show-conversation-via-signal.me', (_event, info) => {
|
ipc.on('show-conversation-via-signal.me', (_event, info) => {
|
||||||
const { hash } = info;
|
const { hash } = info;
|
||||||
strictAssert(typeof hash === 'string', 'Got an invalid hash over IPC');
|
strictAssert(typeof hash === 'string', 'Got an invalid hash over IPC');
|
||||||
|
|
|
@ -26,6 +26,7 @@ window.WebAPI = window.textsecure.WebAPI.initialize({
|
||||||
storageUrl: config.storageUrl,
|
storageUrl: config.storageUrl,
|
||||||
updatesUrl: config.updatesUrl,
|
updatesUrl: config.updatesUrl,
|
||||||
resourcesUrl: config.resourcesUrl,
|
resourcesUrl: config.resourcesUrl,
|
||||||
|
artCreatorUrl: config.artCreatorUrl,
|
||||||
directoryConfig: config.directoryConfig,
|
directoryConfig: config.directoryConfig,
|
||||||
cdnUrlObject: {
|
cdnUrlObject: {
|
||||||
0: config.cdnUrl0,
|
0: config.cdnUrl0,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue