Support additional sticker states

Co-authored-by: scott@signal.org
Co-authored-by: ken@signal.org
This commit is contained in:
Ken Powers 2019-05-23 18:27:42 -07:00 committed by Scott Nonnenberg
parent 41880cfe66
commit be5d0837f8
35 changed files with 925 additions and 249 deletions

View file

@ -1835,6 +1835,12 @@
"message": "Sticker Pack", "message": "Sticker Pack",
"description": "The title that appears in the sticker pack preview modal." "description": "The title that appears in the sticker pack preview modal."
}, },
"stickers--StickerPreview--Error": {
"message":
"Error opening sticker pack. Check your internet connection and try again.",
"description":
"The message that appears in the sticker preview modal when there is an error."
},
"EmojiPicker--empty": { "EmojiPicker--empty": {
"message": "No emoji found", "message": "No emoji found",
"description": "Shown in the emoji picker when a search yields 0 results." "description": "Shown in the emoji picker when a search yields 0 results."

View file

@ -12,6 +12,7 @@ let initialized = false;
const ERASE_ATTACHMENTS_KEY = 'erase-attachments'; const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
const ERASE_STICKERS_KEY = 'erase-stickers'; const ERASE_STICKERS_KEY = 'erase-stickers';
const ERASE_TEMP_KEY = 'erase-temp';
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments'; const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
async function initialize({ configDir, cleanupOrphanedAttachments }) { async function initialize({ configDir, cleanupOrphanedAttachments }) {
@ -22,6 +23,18 @@ async function initialize({ configDir, cleanupOrphanedAttachments }) {
const attachmentsDir = Attachments.getPath(configDir); const attachmentsDir = Attachments.getPath(configDir);
const stickersDir = Attachments.getStickersPath(configDir); const stickersDir = Attachments.getStickersPath(configDir);
const tempDir = Attachments.getTempPath(configDir);
ipcMain.on(ERASE_TEMP_KEY, event => {
try {
rimraf.sync(tempDir);
event.sender.send(`${ERASE_TEMP_KEY}-done`);
} catch (error) {
const errorForDisplay = error && error.stack ? error.stack : error;
console.log(`erase temp error: ${errorForDisplay}`);
event.sender.send(`${ERASE_TEMP_KEY}-done`, error);
}
});
ipcMain.on(ERASE_ATTACHMENTS_KEY, event => { ipcMain.on(ERASE_ATTACHMENTS_KEY, event => {
try { try {

1
app/attachments.d.ts vendored Normal file
View file

@ -0,0 +1 @@
export function getTempPath(userDataPath: string): string;

View file

@ -9,6 +9,7 @@ const { map, isArrayBuffer, isString } = require('lodash');
const PATH = 'attachments.noindex'; const PATH = 'attachments.noindex';
const STICKER_PATH = 'stickers.noindex'; const STICKER_PATH = 'stickers.noindex';
const TEMP_PATH = 'temp';
exports.getAllAttachments = async userDataPath => { exports.getAllAttachments = async userDataPath => {
const dir = exports.getPath(userDataPath); const dir = exports.getPath(userDataPath);
@ -42,6 +43,20 @@ exports.getStickersPath = userDataPath => {
return path.join(userDataPath, STICKER_PATH); return path.join(userDataPath, STICKER_PATH);
}; };
// getTempPath :: AbsolutePath -> AbsolutePath
exports.getTempPath = userDataPath => {
if (!isString(userDataPath)) {
throw new TypeError("'userDataPath' must be a string");
}
return path.join(userDataPath, TEMP_PATH);
};
// clearTempPath :: AbsolutePath -> AbsolutePath
exports.clearTempPath = userDataPath => {
const tempPath = exports.getTempPath(userDataPath);
return fse.emptyDir(tempPath);
};
// createReader :: AttachmentsPath -> // createReader :: AttachmentsPath ->
// RelativePath -> // RelativePath ->
// IO (Promise ArrayBuffer) // IO (Promise ArrayBuffer)

View file

@ -1193,14 +1193,14 @@ async function updateConversation(data) {
await db.run( await db.run(
`UPDATE conversations SET `UPDATE conversations SET
json = $json, json = $json,
active_at = $active_at, active_at = $active_at,
type = $type, type = $type,
members = $members, members = $members,
name = $name, name = $name,
profileName = $profileName profileName = $profileName
WHERE id = $id;`, WHERE id = $id;`,
{ {
$id: id, $id: id,
$json: objectToJSON(data), $json: objectToJSON(data),
@ -1879,8 +1879,46 @@ async function createOrUpdateStickerPack(pack) {
); );
} }
const rows = await db.all('SELECT id FROM sticker_packs WHERE id = $id;', {
$id: id,
});
const payload = {
$attemptedStatus: attemptedStatus,
$author: author,
$coverStickerId: coverStickerId,
$createdAt: createdAt || Date.now(),
$downloadAttempts: downloadAttempts || 1,
$id: id,
$installedAt: installedAt,
$key: key,
$lastUsed: lastUsed || null,
$status: status,
$stickerCount: stickerCount,
$title: title,
};
if (rows && rows.length) {
await db.run(
`UPDATE sticker_packs SET
attemptedStatus = $attemptedStatus,
author = $author,
coverStickerId = $coverStickerId,
createdAt = $createdAt,
downloadAttempts = $downloadAttempts,
installedAt = $installedAt,
key = $key,
lastUsed = $lastUsed,
status = $status,
stickerCount = $stickerCount,
title = $title
WHERE id = $id;`,
payload
);
return;
}
await db.run( await db.run(
`INSERT OR REPLACE INTO sticker_packs ( `INSERT INTO sticker_packs (
attemptedStatus, attemptedStatus,
author, author,
coverStickerId, coverStickerId,
@ -1907,20 +1945,7 @@ async function createOrUpdateStickerPack(pack) {
$stickerCount, $stickerCount,
$title $title
)`, )`,
{ payload
$attemptedStatus: attemptedStatus,
$author: author,
$coverStickerId: coverStickerId,
$createdAt: createdAt || Date.now(),
$downloadAttempts: downloadAttempts || 1,
$id: id,
$installedAt: installedAt,
$key: key,
$lastUsed: lastUsed || null,
$status: status,
$stickerCount: stickerCount,
$title: title,
}
); );
} }
async function updateStickerPackStatus(id, status, options) { async function updateStickerPackStatus(id, status, options) {

View file

@ -302,22 +302,25 @@
await window.Signal.Data.shutdown(); await window.Signal.Data.shutdown();
}, },
installStickerPack: async (id, key) => { showStickerPack: async (packId, key) => {
const status = window.Signal.Stickers.getStickerPackStatus(id); // Kick off the download
window.Signal.Stickers.downloadEphemeralPack(packId, key);
if (status === 'installed') { const props = {
return; packId,
} onClose: async () => {
stickerPreviewModalView.remove();
await window.Signal.Stickers.removeEphemeralPack(packId);
},
};
if (status === 'advertised') { const stickerPreviewModalView = new Whisper.ReactWrapperView({
await window.reduxActions.stickers.installStickerPack(id, key, { className: 'sticker-preview-modal-wrapper',
fromSync: true, JSX: Signal.State.Roots.createStickerPreviewModal(
}); window.reduxStore,
} else { props
await window.Signal.Stickers.downloadStickerPack(id, key, { ),
finalStatus: 'installed', });
});
}
}, },
}; };
@ -464,6 +467,7 @@
user: { user: {
attachmentsPath: window.baseAttachmentsPath, attachmentsPath: window.baseAttachmentsPath,
stickersPath: window.baseStickersPath, stickersPath: window.baseStickersPath,
tempPath: window.baseTempPath,
regionCode: window.storage.get('regionCode'), regionCode: window.storage.get('regionCode'),
ourNumber: textsecure.storage.user.getNumber(), ourNumber: textsecure.storage.user.getNumber(),
i18n: window.i18n, i18n: window.i18n,
@ -1056,7 +1060,7 @@
fromSync: true, fromSync: true,
}); });
} else if (isInstall) { } else if (isInstall) {
if (status === 'advertised') { if (status === 'downloaded') {
window.reduxActions.stickers.installStickerPack(id, key, { window.reduxActions.stickers.installStickerPack(id, key, {
fromSync: true, fromSync: true,
}); });

View file

@ -33,7 +33,7 @@
const { const {
copyStickerToAttachments, copyStickerToAttachments,
deletePackReference, deletePackReference,
downloadStickerPack, savePackMetadata,
getStickerPackStatus, getStickerPackStatus,
} = window.Signal.Stickers; } = window.Signal.Stickers;
const { addStickerPackReference } = window.Signal.Data; const { addStickerPackReference } = window.Signal.Data;
@ -1467,7 +1467,7 @@
const status = getStickerPackStatus(packId); const status = getStickerPackStatus(packId);
let data; let data;
if (status && status !== 'pending' && status !== 'error') { if (status && (status === 'downloaded' || status === 'installed')) {
try { try {
const copiedSticker = await copyStickerToAttachments( const copiedSticker = await copyStickerToAttachments(
packId, packId,
@ -1492,8 +1492,8 @@
}); });
} }
if (!status) { if (!status) {
// kick off the download without waiting // Save the packId/packKey for future download/install
downloadStickerPack(packId, packKey, { messageId }); savePackMetadata(packId, packKey, { messageId });
} else { } else {
await addStickerPackReference(messageId, packId); await addStickerPackReference(messageId, packId);
} }

View file

@ -8,7 +8,7 @@ export function updateStickerLastUsed(
): Promise<void>; ): Promise<void>;
export function updateStickerPackStatus( export function updateStickerPackStatus(
packId: string, packId: string,
status: 'advertised' | 'installed' | 'error' | 'pending', status: 'known' | 'downloaded' | 'installed' | 'error' | 'pending',
options?: { timestamp: number } options?: { timestamp: number }
): Promise<void>; ): Promise<void>;

View file

@ -28,6 +28,7 @@ const SQL_CHANNEL_KEY = 'sql-channel';
const ERASE_SQL_KEY = 'erase-sql-key'; const ERASE_SQL_KEY = 'erase-sql-key';
const ERASE_ATTACHMENTS_KEY = 'erase-attachments'; const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
const ERASE_STICKERS_KEY = 'erase-stickers'; const ERASE_STICKERS_KEY = 'erase-stickers';
const ERASE_TEMP_KEY = 'erase-temp';
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments'; const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
const _jobs = Object.create(null); const _jobs = Object.create(null);
@ -965,6 +966,7 @@ async function removeOtherData() {
callChannel(ERASE_SQL_KEY), callChannel(ERASE_SQL_KEY),
callChannel(ERASE_ATTACHMENTS_KEY), callChannel(ERASE_ATTACHMENTS_KEY),
callChannel(ERASE_STICKERS_KEY), callChannel(ERASE_STICKERS_KEY),
callChannel(ERASE_TEMP_KEY),
]); ]);
} }

View file

@ -129,6 +129,7 @@ function initializeMigrations({
const { const {
getPath, getPath,
getStickersPath, getStickersPath,
getTempPath,
createReader, createReader,
createAbsolutePathGetter, createAbsolutePathGetter,
createWriterForNew, createWriterForNew,
@ -161,6 +162,12 @@ function initializeMigrations({
const deleteSticker = Attachments.createDeleter(stickersPath); const deleteSticker = Attachments.createDeleter(stickersPath);
const readStickerData = createReader(stickersPath); const readStickerData = createReader(stickersPath);
const tempPath = getTempPath(userDataPath);
const getAbsoluteTempPath = createAbsolutePathGetter(tempPath);
const writeNewTempData = createWriterForNew(tempPath);
const deleteTempFile = Attachments.createDeleter(tempPath);
const readTempData = createReader(tempPath);
return { return {
attachmentsPath, attachmentsPath,
copyIntoAttachmentsDirectory, copyIntoAttachmentsDirectory,
@ -170,6 +177,7 @@ function initializeMigrations({
deleteOnDisk, deleteOnDisk,
}), }),
deleteSticker, deleteSticker,
deleteTempFile,
getAbsoluteAttachmentPath, getAbsoluteAttachmentPath,
getAbsoluteStickerPath, getAbsoluteStickerPath,
getPlaceholderMigrations, getPlaceholderMigrations,
@ -181,6 +189,7 @@ function initializeMigrations({
loadStickerData, loadStickerData,
readAttachmentData, readAttachmentData,
readStickerData, readStickerData,
readTempData,
run, run,
processNewAttachment: attachment => processNewAttachment: attachment =>
MessageType.processNewAttachment(attachment, { MessageType.processNewAttachment(attachment, {
@ -200,6 +209,13 @@ function initializeMigrations({
getImageDimensions, getImageDimensions,
logger, logger,
}), }),
processNewEphemeralSticker: stickerData =>
MessageType.processNewSticker(stickerData, {
writeNewStickerData: writeNewTempData,
getAbsoluteStickerPath: getAbsoluteTempPath,
getImageDimensions,
logger,
}),
upgradeMessageSchema: (message, options = {}) => { upgradeMessageSchema: (message, options = {}) => {
const { maxVersion } = options; const { maxVersion } = options;

View file

@ -1 +1,11 @@
export function maybeDeletePack(packId: string): Promise<void>; export function maybeDeletePack(packId: string): Promise<void>;
export function downloadStickerPack(
packId: string,
packKey: string,
options?: {
finalStatus?: 'installed' | 'downloaded';
messageId?: string;
fromSync?: boolean;
}
): Promise<void>;

View file

@ -2,6 +2,7 @@
textsecure, textsecure,
Signal, Signal,
log, log,
navigator,
reduxStore, reduxStore,
reduxActions, reduxActions,
URL URL
@ -9,7 +10,7 @@
const BLESSED_PACKS = {}; const BLESSED_PACKS = {};
const { isNumber, pick, reject, groupBy } = require('lodash'); const { isNumber, pick, reject, groupBy, values } = require('lodash');
const pMap = require('p-map'); const pMap = require('p-map');
const Queue = require('p-queue'); const Queue = require('p-queue');
const qs = require('qs'); const qs = require('qs');
@ -34,6 +35,7 @@ module.exports = {
deletePack, deletePack,
deletePackReference, deletePackReference,
downloadStickerPack, downloadStickerPack,
downloadEphemeralPack,
getDataFromLink, getDataFromLink,
getInitialState, getInitialState,
getInstalledStickerPacks, getInstalledStickerPacks,
@ -44,6 +46,8 @@ module.exports = {
maybeDeletePack, maybeDeletePack,
downloadQueuedPacks, downloadQueuedPacks,
redactPackId, redactPackId,
removeEphemeralPack,
savePackMetadata,
}; };
let initialState = null; let initialState = null;
@ -88,8 +92,8 @@ function getInstalledStickerPacks() {
return []; return [];
} }
const values = Object.values(packs); const items = Object.values(packs);
return values.filter(pack => pack.status === 'installed'); return items.filter(pack => pack.status === 'installed');
} }
function downloadQueuedPacks() { function downloadQueuedPacks() {
@ -113,7 +117,7 @@ function capturePacksToDownload(existingPackLookup) {
const existing = existingPackLookup[id]; const existing = existingPackLookup[id];
if ( if (
!existing || !existing ||
(existing.status !== 'advertised' && existing.status !== 'installed') (existing.status !== 'downloaded' && existing.status !== 'installed')
) { ) {
toDownload[id] = { toDownload[id] = {
id, id,
@ -130,6 +134,18 @@ function capturePacksToDownload(existingPackLookup) {
} }
const existing = existingPackLookup[id]; const existing = existingPackLookup[id];
// These packs should never end up in the database, but if they do we'll delete them
if (existing.status === 'ephemeral') {
deletePack(id);
return;
}
// We don't automatically download these; not until a user action kicks it off
if (existing.status === 'known') {
return;
}
if (doesPackNeedDownload(existing)) { if (doesPackNeedDownload(existing)) {
toDownload[id] = { toDownload[id] = {
id, id,
@ -147,14 +163,23 @@ function doesPackNeedDownload(pack) {
return true; return true;
} }
const stickerCount = Object.keys(pack.stickers || {}).length; const { status, stickerCount } = pack;
return ( const stickersDownloaded = Object.keys(pack.stickers || {}).length;
!pack.status ||
pack.status === 'error' || if (
pack.status === 'pending' || (status === 'installed' || status === 'downloaded') &&
!pack.stickerCount || stickerCount > 0 &&
stickerCount < pack.stickerCount stickersDownloaded >= stickerCount
); ) {
return false;
}
// If we don't understand a pack's status, we'll download it
// If a pack has any other status, we'll download it
// If a pack has zero stickers in it, we'll download it
// If a pack doesn't have enough downloaded stickers, we'll download it
return true;
} }
async function getPacksForRedux() { async function getPacksForRedux() {
@ -209,10 +234,15 @@ async function decryptSticker(packKey, ciphertext) {
return plaintext; return plaintext;
} }
async function downloadSticker(packId, packKey, proto) { async function downloadSticker(packId, packKey, proto, options) {
const { ephemeral } = options || {};
const ciphertext = await textsecure.messaging.getSticker(packId, proto.id); const ciphertext = await textsecure.messaging.getSticker(packId, proto.id);
const plaintext = await decryptSticker(packKey, ciphertext); const plaintext = await decryptSticker(packKey, ciphertext);
const sticker = await Signal.Migrations.processNewSticker(plaintext);
const sticker = ephemeral
? await Signal.Migrations.processNewEphemeralSticker(plaintext, options)
: await Signal.Migrations.processNewSticker(plaintext, options);
return { return {
...pick(proto, ['id', 'emoji']), ...pick(proto, ['id', 'emoji']),
@ -221,6 +251,156 @@ async function downloadSticker(packId, packKey, proto) {
}; };
} }
async function savePackMetadata(packId, packKey, options = {}) {
const { messageId } = options;
const existing = getStickerPack(packId);
if (existing) {
return;
}
const { stickerPackAdded } = getReduxStickerActions();
const pack = {
id: packId,
key: packKey,
status: 'known',
};
stickerPackAdded(pack);
await createOrUpdateStickerPack(pack);
if (messageId) {
await addStickerPackReference(messageId, packId);
}
}
async function removeEphemeralPack(packId) {
const existing = getStickerPack(packId);
if (
existing.status !== 'ephemeral' &&
!(existing.status === 'error' && existing.attemptedStatus === 'ephemeral')
) {
return;
}
const { removeStickerPack } = getReduxStickerActions();
removeStickerPack(packId);
const stickers = values(existing.stickers);
const paths = stickers.map(sticker => sticker.path);
await pMap(paths, Signal.Migrations.deleteTempFile, {
concurrency: 3,
});
// Remove it from database in case it made it there
await deleteStickerPack(packId);
}
async function downloadEphemeralPack(packId, packKey) {
const {
stickerAdded,
stickerPackAdded,
stickerPackUpdated,
} = getReduxStickerActions();
const existingPack = getStickerPack(packId);
if (existingPack) {
log.warn(
`Ephemeral download for pack ${redactPackId(
packId
)} requested, we already know about it. Skipping.`
);
return;
}
try {
// Synchronous placeholder to help with race conditions
const placeholder = {
id: packId,
key: packKey,
status: 'ephemeral',
};
stickerPackAdded(placeholder);
const ciphertext = await textsecure.messaging.getStickerPackManifest(
packId
);
const plaintext = await decryptSticker(packKey, ciphertext);
const proto = textsecure.protobuf.StickerPack.decode(plaintext);
const firstStickerProto = proto.stickers ? proto.stickers[0] : null;
const stickerCount = proto.stickers.length;
const coverProto = proto.cover || firstStickerProto;
const coverStickerId = coverProto ? coverProto.id : null;
if (!coverProto || !isNumber(coverStickerId)) {
throw new Error(
`Sticker pack ${redactPackId(
packId
)} is malformed - it has no cover, and no stickers`
);
}
const nonCoverStickers = reject(
proto.stickers,
sticker => !isNumber(sticker.id) || sticker.id === coverStickerId
);
const coverIncludedInList = nonCoverStickers.length < stickerCount;
const pack = {
id: packId,
key: packKey,
coverStickerId,
stickerCount,
status: 'ephemeral',
...pick(proto, ['title', 'author']),
};
stickerPackAdded(pack);
const downloadStickerJob = async stickerProto => {
const stickerInfo = await downloadSticker(packId, packKey, stickerProto, {
ephemeral: true,
});
const sticker = {
...stickerInfo,
isCoverOnly: !coverIncludedInList && stickerInfo.id === coverStickerId,
};
const statusCheck = getStickerPackStatus(packId);
if (statusCheck !== 'ephemeral') {
throw new Error(
`Ephemeral download for pack ${redactPackId(
packId
)} interrupted by status change. Status is now ${statusCheck}.`
);
}
stickerAdded(sticker);
};
// Download the cover first
await downloadStickerJob(coverProto);
// Then the rest
await pMap(nonCoverStickers, downloadStickerJob, { concurrency: 3 });
} catch (error) {
// Because the user could install this pack while we are still downloading this
// ephemeral pack, we don't want to go change its status unless we're still in
// ephemeral mode.
const statusCheck = getStickerPackStatus(packId);
if (statusCheck === 'ephemeral') {
stickerPackUpdated(packId, {
attemptedStatus: 'ephemeral',
status: 'error',
});
}
log.error(
`Ephemeral download error for sticker pack ${redactPackId(packId)}:`,
error && error.stack ? error.stack : error
);
}
}
async function downloadStickerPack(packId, packKey, options = {}) { async function downloadStickerPack(packId, packKey, options = {}) {
// This will ensure that only one download process is in progress at any given time // This will ensure that only one download process is in progress at any given time
return downloadQueue.add(async () => { return downloadQueue.add(async () => {
@ -244,7 +424,12 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
installStickerPack, installStickerPack,
} = getReduxStickerActions(); } = getReduxStickerActions();
const finalStatus = options.finalStatus || 'advertised'; const finalStatus = options.finalStatus || 'downloaded';
if (finalStatus !== 'downloaded' && finalStatus !== 'installed') {
throw new Error(
`doDownloadStickerPack: invalid finalStatus of ${finalStatus} requested.`
);
}
const existing = getStickerPack(packId); const existing = getStickerPack(packId);
if (!doesPackNeedDownload(existing)) { if (!doesPackNeedDownload(existing)) {
@ -256,7 +441,10 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
return; return;
} }
const downloadAttempts = (existing ? existing.downloadAttempts || 0 : 0) + 1; // We don't count this as an attempt if we're offline
const attemptIncrement = navigator.onLine ? 1 : 0;
const downloadAttempts =
(existing ? existing.downloadAttempts || 0 : 0) + attemptIncrement;
if (downloadAttempts > 3) { if (downloadAttempts > 3) {
log.warn( log.warn(
`Refusing to attempt another download for pack ${redactPackId( `Refusing to attempt another download for pack ${redactPackId(
@ -280,6 +468,16 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
let nonCoverStickers; let nonCoverStickers;
try { try {
// Synchronous placeholder to help with race conditions
const placeholder = {
id: packId,
key: packKey,
attemptedStatus: finalStatus,
downloadAttempts,
status: 'pending',
};
stickerPackAdded(placeholder);
const ciphertext = await textsecure.messaging.getStickerPackManifest( const ciphertext = await textsecure.messaging.getStickerPackManifest(
packId packId
); );
@ -307,8 +505,10 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
coverIncludedInList = nonCoverStickers.length < stickerCount; coverIncludedInList = nonCoverStickers.length < stickerCount;
// status can be: // status can be:
// - 'known'
// - 'ephemeral' (should not hit database)
// - 'pending' // - 'pending'
// - 'advertised' // - 'downloaded'
// - 'error' // - 'error'
// - 'installed' // - 'installed'
const pack = { const pack = {
@ -365,6 +565,13 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
// Then the rest // Then the rest
await pMap(nonCoverStickers, downloadStickerJob, { concurrency: 3 }); await pMap(nonCoverStickers, downloadStickerJob, { concurrency: 3 });
// Allow for the user marking this pack as installed in the middle of our download;
// don't overwrite that status.
const existingStatus = getStickerPackStatus(packId);
if (existingStatus === 'installed') {
return;
}
if (finalStatus === 'installed') { if (finalStatus === 'installed') {
await installStickerPack(packId, packKey, { fromSync }); await installStickerPack(packId, packKey, { fromSync });
} else { } else {
@ -380,11 +587,12 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );
const errorState = 'error'; const errorStatus = 'error';
await updateStickerPackStatus(packId, errorState); await updateStickerPackStatus(packId, errorStatus);
if (stickerPackUpdated) { if (stickerPackUpdated) {
stickerPackUpdated(packId, { stickerPackUpdated(packId, {
state: errorState, attemptedStatus: finalStatus,
status: errorStatus,
}); });
} }
} }

View file

@ -1331,15 +1331,19 @@
dialog.focusCancel(); dialog.focusCancel();
}, },
showStickerPackPreview(packId) { showStickerPackPreview(packId, packKey) {
if (!window.ENABLE_STICKER_SEND) { if (!window.ENABLE_STICKER_SEND) {
return; return;
} }
window.Signal.Stickers.downloadEphemeralPack(packId, packKey);
const props = { const props = {
packId, packId,
onClose: () => { onClose: async () => {
this.stickerPreviewModalView.remove(); this.stickerPreviewModalView.remove();
this.stickerPreviewModalView = null;
await window.Signal.Stickers.removeEphemeralPack(packId);
}, },
}; };
@ -1349,9 +1353,6 @@
window.reduxStore, window.reduxStore,
props props
), ),
onClose: () => {
this.stickerPreviewModalView = null;
},
}); });
}, },
@ -1364,8 +1365,8 @@
} }
const sticker = message.get('sticker'); const sticker = message.get('sticker');
if (sticker) { if (sticker) {
const { packId } = sticker; const { packId, packKey } = sticker;
this.showStickerPackPreview(packId); this.showStickerPackPreview(packId, packKey);
return; return;
} }
@ -1992,17 +1993,25 @@
}, },
async getStickerPackPreview(url) { async getStickerPackPreview(url) {
const isPackDownloaded = pack =>
pack && (pack.status === 'downloaded' || pack.status === 'installed');
const isPackValid = pack => const isPackValid = pack =>
pack && (pack.status === 'advertised' || pack.status === 'installed'); pack &&
(pack.status === 'ephemeral' ||
pack.status === 'downloaded' ||
pack.status === 'installed');
let id;
let key;
try { try {
const { id, key } = window.Signal.Stickers.getDataFromLink(url); ({ id, key } = window.Signal.Stickers.getDataFromLink(url));
const keyBytes = window.Signal.Crypto.bytesFromHexString(key); const keyBytes = window.Signal.Crypto.bytesFromHexString(key);
const keyBase64 = window.Signal.Crypto.arrayBufferToBase64(keyBytes); const keyBase64 = window.Signal.Crypto.arrayBufferToBase64(keyBytes);
const existing = window.Signal.Stickers.getStickerPack(id); const existing = window.Signal.Stickers.getStickerPack(id);
if (!isPackValid(existing)) { if (!isPackDownloaded(existing)) {
await window.Signal.Stickers.downloadStickerPack(id, keyBase64); await window.Signal.Stickers.downloadEphemeralPack(id, keyBase64);
} }
const pack = window.Signal.Stickers.getStickerPack(id); const pack = window.Signal.Stickers.getStickerPack(id);
@ -2015,9 +2024,10 @@
const { title, coverStickerId } = pack; const { title, coverStickerId } = pack;
const sticker = pack.stickers[coverStickerId]; const sticker = pack.stickers[coverStickerId];
const data = await window.Signal.Migrations.readStickerData( const data =
sticker.path pack.status === 'ephemeral'
); ? await window.Signal.Migrations.readTempData(sticker.path)
: await window.Signal.Migrations.readStickerData(sticker.path);
return { return {
title, title,
@ -2035,6 +2045,10 @@
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );
return null; return null;
} finally {
if (id) {
await window.Signal.Stickers.removeEphemeralPack(id);
}
} }
}, },

10
main.js
View file

@ -726,6 +726,14 @@ app.on('ready', async () => {
}); });
} }
try {
await attachments.clearTempPath(userDataPath);
} catch (error) {
logger.error(
'main/ready: Error deleting temp dir:',
error && error.stack ? error.stack : error
);
}
await attachmentChannel.initialize({ await attachmentChannel.initialize({
configDir: userDataPath, configDir: userDataPath,
cleanupOrphanedAttachments, cleanupOrphanedAttachments,
@ -1034,7 +1042,7 @@ function handleSgnlLink(incomingUrl) {
if (command === 'addstickers' && mainWindow && mainWindow.webContents) { if (command === 'addstickers' && mainWindow && mainWindow.webContents) {
const { pack_id: packId, pack_key: packKeyHex } = args; const { pack_id: packId, pack_key: packKeyHex } = args;
const packKey = Buffer.from(packKeyHex, 'hex').toString('base64'); const packKey = Buffer.from(packKeyHex, 'hex').toString('base64');
mainWindow.webContents.send('add-sticker-pack', { packId, packKey }); mainWindow.webContents.send('show-sticker-pack', { packId, packKey });
} else { } else {
console.error('Unhandled sgnl link'); console.error('Unhandled sgnl link');
} }

View file

@ -9,7 +9,7 @@ const { app } = electron.remote;
const { systemPreferences } = electron.remote.require('electron'); const { systemPreferences } = electron.remote.require('electron');
// Waiting for clients to implement changes on receive side // Waiting for clients to implement changes on receive side
window.ENABLE_STICKER_SEND = false; window.ENABLE_STICKER_SEND = true;
window.TIMESTAMP_VALIDATION = false; window.TIMESTAMP_VALIDATION = false;
window.PAD_ALL_ATTACHMENTS = false; window.PAD_ALL_ATTACHMENTS = false;
window.SEND_RECIPIENT_UPDATES = false; window.SEND_RECIPIENT_UPDATES = false;
@ -175,11 +175,11 @@ ipc.on('delete-all-data', () => {
} }
}); });
ipc.on('add-sticker-pack', (_event, info) => { ipc.on('show-sticker-pack', (_event, info) => {
const { packId, packKey } = info; const { packId, packKey } = info;
const { installStickerPack } = window.Events; const { showStickerPack } = window.Events;
if (installStickerPack) { if (showStickerPack) {
installStickerPack(packId, packKey); showStickerPack(packId, packKey);
} }
}); });
@ -306,6 +306,7 @@ window.moment.locale(locale);
const userDataPath = app.getPath('userData'); const userDataPath = app.getPath('userData');
window.baseAttachmentsPath = Attachments.getPath(userDataPath); window.baseAttachmentsPath = Attachments.getPath(userDataPath);
window.baseStickersPath = Attachments.getStickersPath(userDataPath); window.baseStickersPath = Attachments.getStickersPath(userDataPath);
window.baseTempPath = Attachments.getTempPath(userDataPath);
window.Signal = Signal.setup({ window.Signal = Signal.setup({
Attachments, Attachments,
userDataPath, userDataPath,

View file

@ -3,7 +3,11 @@
.inbox, .inbox,
.gutter { .gutter {
height: 100%; height: 100%;
overflow: hidden; }
.inbox {
display: flex;
flex-direction: row;
} }
.expired { .expired {
@ -82,6 +86,7 @@
} }
.conversation-stack { .conversation-stack {
flex-grow: 1;
.conversation { .conversation {
display: none; display: none;
} }

View file

@ -3376,6 +3376,14 @@
max-height: 20px; max-height: 20px;
} }
.module-sticker-picker__header__button__image--placeholder {
min-width: 20px;
min-height: 20px;
max-width: 20px;
max-height: 20px;
background-color: $color-gray-10;
}
.module-sticker-picker__body { .module-sticker-picker__body {
position: relative; position: relative;
@ -3553,6 +3561,11 @@
width: 48px; width: 48px;
height: 48px; height: 48px;
} }
&__cover-placeholder {
width: 48px;
height: 48px;
background: $color-gray-10;
}
&__meta { &__meta {
flex-grow: 1; flex-grow: 1;
@ -3681,9 +3694,23 @@
background: $color-gray-75; background: $color-gray-75;
} }
&__error {
color: $color-core-red;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
width: 100%;
height: 100%;
padding: 0 80px 30px 80px;
font-family: Roboto;
font-weight: 300;
}
&__header { &__header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-shrink: 0;
height: 36px; height: 36px;
padding: 0 8px 0 16px; padding: 0 8px 0 16px;
justify-content: space-between; justify-content: space-between;
@ -3727,6 +3754,18 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
&--placeholder {
border-radius: 4px;
@include light-theme() {
background: $color-gray-05;
}
@include dark-theme() {
background: $color-gray-60;
}
}
} }
} }
@ -3913,6 +3952,11 @@
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
&__image-placeholder {
width: 20px;
height: 20px;
background-color: $color-gray-10;
}
&__text { &__text {
margin-left: 4px; margin-left: 4px;
@ -3938,11 +3982,11 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
&__image { // &__image {
width: 52px; // width: 52px;
height: 52px; // height: 52px;
background: #eaeaea; // background: $color-gray-10;
} // }
&__meta { &__meta {
flex-grow: 1; flex-grow: 1;

View file

@ -43,6 +43,8 @@ const packs = [
i18n={util.i18n} i18n={util.i18n}
receivedPacks={[]} receivedPacks={[]}
installedPacks={packs} installedPacks={packs}
blessedPacks={[]}
knownPacks={[]}
onPickSticker={(packId, stickerId) => onPickSticker={(packId, stickerId) =>
console.log('onPickSticker', { packId, stickerId }) console.log('onPickSticker', { packId, stickerId })
} }
@ -100,8 +102,10 @@ const packs = [
> >
<StickerButton <StickerButton
i18n={util.i18n} i18n={util.i18n}
receivedPacks={[]} receivedPacks={packs}
installedPacks={packs} installedPacks={[]}
blessedPacks={[]}
knownPacks={[]}
onPickSticker={(packId, stickerId) => onPickSticker={(packId, stickerId) =>
console.log('onPickSticker', { packId, stickerId }) console.log('onPickSticker', { packId, stickerId })
} }
@ -113,7 +117,75 @@ const packs = [
</util.ConversationContext>; </util.ConversationContext>;
``` ```
#### No Advertised Packs and No Installed Packs #### Just known packs
Even with just known packs, the button should render.
```jsx
const sticker1 = { id: 1, url: util.kitten164ObjectUrl, packId: 'foo' };
const packs = [
{
id: 'foo',
cover: sticker1,
stickers: Array(101)
.fill(0)
.map((n, id) => ({ ...sticker1, id })),
},
];
<util.ConversationContext theme={util.theme}>
<StickerButton
i18n={util.i18n}
receivedPacks={[]}
installedPacks={[]}
knownPacks={packs}
blessedPacks={[]}
onPickSticker={(packId, stickerId) =>
console.log('onPickSticker', { packId, stickerId })
}
clearInstalledStickerPack={() => console.log('clearInstalledStickerPack')}
onClickAddPack={() => console.log('onClickAddPack')}
recentStickers={[]}
/>
</util.ConversationContext>;
```
#### Just blessed packs
Even with just blessed packs, the button should render.
```jsx
const sticker1 = { id: 1, url: util.kitten164ObjectUrl, packId: 'foo' };
const packs = [
{
id: 'foo',
cover: sticker1,
stickers: Array(101)
.fill(0)
.map((n, id) => ({ ...sticker1, id })),
},
];
<util.ConversationContext theme={util.theme}>
<StickerButton
i18n={util.i18n}
receivedPacks={[]}
installedPacks={[]}
blessedPacks={packs}
knownPacks={[]}
onPickSticker={(packId, stickerId) =>
console.log('onPickSticker', { packId, stickerId })
}
clearInstalledStickerPack={() => console.log('clearInstalledStickerPack')}
onClickAddPack={() => console.log('onClickAddPack')}
recentStickers={[]}
/>
</util.ConversationContext>;
```
#### No packs at all
When there are no advertised packs and no installed packs the button should not render anything. When there are no advertised packs and no installed packs the button should not render anything.
@ -123,6 +195,8 @@ When there are no advertised packs and no installed packs the button should not
i18n={util.i18n} i18n={util.i18n}
receivedPacks={[]} receivedPacks={[]}
installedPacks={[]} installedPacks={[]}
blessedPacks={[]}
knownPacks={[]}
onPickSticker={(packId, stickerId) => onPickSticker={(packId, stickerId) =>
console.log('onPickSticker', { packId, stickerId }) console.log('onPickSticker', { packId, stickerId })
} }
@ -188,6 +262,8 @@ const packs = [
i18n={util.i18n} i18n={util.i18n}
receivedPacks={[]} receivedPacks={[]}
installedPacks={packs} installedPacks={packs}
blessedPacks={[]}
knownPacks={[]}
installedPack={packs[0]} installedPack={packs[0]}
onPickSticker={(packId, stickerId) => onPickSticker={(packId, stickerId) =>
console.log('onPickSticker', { packId, stickerId }) console.log('onPickSticker', { packId, stickerId })
@ -257,6 +333,8 @@ const packs = [
i18n={util.i18n} i18n={util.i18n}
receivedPacks={[]} receivedPacks={[]}
installedPacks={packs} installedPacks={packs}
blessedPacks={[]}
knownPacks={[]}
onPickSticker={(packId, stickerId) => onPickSticker={(packId, stickerId) =>
console.log('onPickSticker', { packId, stickerId }) console.log('onPickSticker', { packId, stickerId })
} }

View file

@ -11,6 +11,8 @@ export type OwnProps = {
readonly i18n: LocalizerType; readonly i18n: LocalizerType;
readonly receivedPacks: ReadonlyArray<StickerPackType>; readonly receivedPacks: ReadonlyArray<StickerPackType>;
readonly installedPacks: ReadonlyArray<StickerPackType>; readonly installedPacks: ReadonlyArray<StickerPackType>;
readonly blessedPacks: ReadonlyArray<StickerPackType>;
readonly knownPacks: ReadonlyArray<StickerPackType>;
readonly installedPack?: StickerPackType | null; readonly installedPack?: StickerPackType | null;
readonly recentStickers: ReadonlyArray<StickerType>; readonly recentStickers: ReadonlyArray<StickerType>;
readonly clearInstalledStickerPack: () => unknown; readonly clearInstalledStickerPack: () => unknown;
@ -35,6 +37,8 @@ export const StickerButton = React.memo(
receivedPacks, receivedPacks,
installedPack, installedPack,
installedPacks, installedPacks,
blessedPacks,
knownPacks,
showIntroduction, showIntroduction,
clearShowIntroduction, clearShowIntroduction,
showPickerHint, showPickerHint,
@ -138,7 +142,12 @@ export const StickerButton = React.memo(
[installedPack, clearInstalledStickerPack] [installedPack, clearInstalledStickerPack]
); );
if (installedPacks.length + receivedPacks.length === 0) { const totalPacks =
knownPacks.length +
blessedPacks.length +
installedPacks.length +
receivedPacks.length;
if (totalPacks === 0) {
return null; return null;
} }
@ -166,11 +175,15 @@ export const StickerButton = React.memo(
role="button" role="button"
onClick={clearInstalledStickerPack} onClick={clearInstalledStickerPack}
> >
<img {installedPack.cover ? (
className="module-sticker-button__tooltip__image" <img
src={installedPack.cover.url} className="module-sticker-button__tooltip__image"
alt={installedPack.title} src={installedPack.cover.url}
/> alt={installedPack.title}
/>
) : (
<div className="module-sticker-button__tooltip__image-placeholder" />
)}
<span className="module-sticker-button__tooltip__text"> <span className="module-sticker-button__tooltip__text">
<span className="module-sticker-button__tooltip__text__title"> <span className="module-sticker-button__tooltip__text__title">
{installedPack.title} {installedPack.title}
@ -202,7 +215,7 @@ export const StickerButton = React.memo(
role="button" role="button"
onClick={handleClearIntroduction} onClick={handleClearIntroduction}
> >
<div className="module-sticker-button__tooltip--introduction__image" /> {/* <div className="module-sticker-button__tooltip--introduction__image" /> */}
<div className="module-sticker-button__tooltip--introduction__meta"> <div className="module-sticker-button__tooltip--introduction__meta">
<div className="module-sticker-button__tooltip--introduction__meta__title"> <div className="module-sticker-button__tooltip--introduction__meta__title">
{i18n('stickers--StickerManager--Introduction--Title')} {i18n('stickers--StickerManager--Introduction--Title')}

View file

@ -35,11 +35,11 @@ const packs = [
}, },
]; ];
const receivedPacks = packs.map(p => ({ ...p, status: 'advertised' })); const receivedPacks = packs.map(p => ({ ...p, status: 'downloaded' }));
const installedPacks = packs.map(p => ({ ...p, status: 'installed' })); const installedPacks = packs.map(p => ({ ...p, status: 'installed' }));
const blessedPacks = packs.map(p => ({ const blessedPacks = packs.map(p => ({
...p, ...p,
status: 'advertised', status: 'downloaded',
isBlessed: true, isBlessed: true,
})); }));
@ -158,6 +158,64 @@ const noPacks = [];
</util.ConversationContext>; </util.ConversationContext>;
``` ```
#### No with 'known'
```jsx
const sticker1 = { id: 1, url: util.kitten164ObjectUrl, packId: 'foo' };
const sticker2 = { id: 2, url: util.kitten264ObjectUrl, packId: 'bar' };
const sticker3 = { id: 3, url: util.kitten364ObjectUrl, packId: 'baz' };
const installedPacks = [
{
id: 'foo',
cover: sticker1,
title: 'Foo',
status: 'installed',
author: 'Foo McBarrington',
stickers: Array(101)
.fill(0)
.map((n, id) => ({ ...sticker1, id })),
},
];
const knownPacks = [
{
id: 'foo',
key: 'key1',
stickers: [],
state: 'known',
},
{
id: 'bar',
key: 'key2',
stickers: [],
state: 'known',
},
{
id: 'baz',
key: 'key3',
stickers: [],
state: 'known',
},
];
const noPacks = [];
<util.ConversationContext theme={util.theme}>
<StickerManager
i18n={util.i18n}
installedPacks={installedPacks}
receivedPacks={noPacks}
blessedPacks={noPacks}
knownPacks={knownPacks}
installStickerPack={id => console.log('installStickerPack', id)}
downloadStickerPack={(packId, packKey, options) =>
console.log('downloadStickerPack', { packId, packKey, options })
}
/>
</util.ConversationContext>;
```
#### No Packs at All #### No Packs at All
```jsx ```jsx

View file

@ -9,6 +9,8 @@ export type OwnProps = {
readonly installedPacks: ReadonlyArray<StickerPackType>; readonly installedPacks: ReadonlyArray<StickerPackType>;
readonly receivedPacks: ReadonlyArray<StickerPackType>; readonly receivedPacks: ReadonlyArray<StickerPackType>;
readonly blessedPacks: ReadonlyArray<StickerPackType>; readonly blessedPacks: ReadonlyArray<StickerPackType>;
readonly knownPacks?: ReadonlyArray<StickerPackType>;
readonly downloadStickerPack: (packId: string, packKey: string) => unknown;
readonly installStickerPack: (packId: string, packKey: string) => unknown; readonly installStickerPack: (packId: string, packKey: string) => unknown;
readonly uninstallStickerPack: (packId: string, packKey: string) => unknown; readonly uninstallStickerPack: (packId: string, packKey: string) => unknown;
readonly i18n: LocalizerType; readonly i18n: LocalizerType;
@ -20,7 +22,9 @@ export const StickerManager = React.memo(
({ ({
installedPacks, installedPacks,
receivedPacks, receivedPacks,
knownPacks,
blessedPacks, blessedPacks,
downloadStickerPack,
installStickerPack, installStickerPack,
uninstallStickerPack, uninstallStickerPack,
i18n, i18n,
@ -30,6 +34,15 @@ export const StickerManager = React.memo(
setPackToPreview, setPackToPreview,
] = React.useState<StickerPackType | null>(null); ] = React.useState<StickerPackType | null>(null);
React.useEffect(() => {
if (!knownPacks) {
return;
}
knownPacks.forEach(pack => {
downloadStickerPack(pack.id, pack.key);
});
}, []);
const clearPackToPreview = React.useCallback( const clearPackToPreview = React.useCallback(
() => { () => {
setPackToPreview(null); setPackToPreview(null);
@ -51,6 +64,7 @@ export const StickerManager = React.memo(
i18n={i18n} i18n={i18n}
pack={packToPreview} pack={packToPreview}
onClose={clearPackToPreview} onClose={clearPackToPreview}
downloadStickerPack={downloadStickerPack}
installStickerPack={installStickerPack} installStickerPack={installStickerPack}
uninstallStickerPack={uninstallStickerPack} uninstallStickerPack={uninstallStickerPack}
/> />

View file

@ -91,11 +91,15 @@ export const StickerManagerPackRow = React.memo(
onClick={handleClickPreview} onClick={handleClickPreview}
className="module-sticker-manager__pack-row" className="module-sticker-manager__pack-row"
> >
<img {pack.cover ? (
src={pack.cover.url} <img
alt={pack.title} src={pack.cover.url}
className="module-sticker-manager__pack-row__cover" alt={pack.title}
/> className="module-sticker-manager__pack-row__cover"
/>
) : (
<div className="module-sticker-manager__pack-row__cover-placeholder" />
)}
<div className="module-sticker-manager__pack-row__meta"> <div className="module-sticker-manager__pack-row__meta">
<div className="module-sticker-manager__pack-row__meta__title"> <div className="module-sticker-manager__pack-row__meta__title">
{pack.title} {pack.title}
@ -108,18 +112,18 @@ export const StickerManagerPackRow = React.memo(
</div> </div>
</div> </div>
<div className="module-sticker-manager__pack-row__controls"> <div className="module-sticker-manager__pack-row__controls">
{pack.status === 'advertised' ? ( {pack.status === 'installed' ? (
<StickerPackInstallButton
installed={false}
i18n={i18n}
onClick={handleInstall}
/>
) : (
<StickerPackInstallButton <StickerPackInstallButton
installed={true} installed={true}
i18n={i18n} i18n={i18n}
onClick={handleUninstall} onClick={handleUninstall}
/> />
) : (
<StickerPackInstallButton
installed={false}
i18n={i18n}
onClick={handleInstall}
/>
)} )}
</div> </div>
</div> </div>

View file

@ -153,12 +153,16 @@ export const StickerPicker = React.memo(
} }
)} )}
> >
<img {pack.cover ? (
className="module-sticker-picker__header__button__image" <img
src={pack.cover.url} className="module-sticker-picker__header__button__image"
alt={pack.title} src={pack.cover.url}
title={pack.title} alt={pack.title}
/> title={pack.title}
/>
) : (
<div className="module-sticker-picker__header__button__image-placeholder" />
)}
</button> </button>
))} ))}
</div> </div>

View file

@ -9,7 +9,7 @@ const pack = {
title: 'Foo', title: 'Foo',
isBlessed: true, isBlessed: true,
author: 'Foo McBarrington', author: 'Foo McBarrington',
status: 'advertised', status: 'downloaded',
stickers: Array(101) stickers: Array(101)
.fill(0) .fill(0)
.map((n, id) => ({ ...abeSticker, id })), .map((n, id) => ({ ...abeSticker, id })),

View file

@ -1,15 +1,23 @@
import * as React from 'react'; import * as React from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { isNumber, range } from 'lodash';
import classNames from 'classnames';
import { StickerPackInstallButton } from './StickerPackInstallButton'; import { StickerPackInstallButton } from './StickerPackInstallButton';
import { ConfirmationDialog } from '../ConfirmationDialog'; import { ConfirmationDialog } from '../ConfirmationDialog';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
import { StickerPackType } from '../../state/ducks/stickers'; import { StickerPackType } from '../../state/ducks/stickers';
import { Spinner } from '../Spinner';
export type OwnProps = { export type OwnProps = {
readonly onClose: () => unknown; readonly onClose: () => unknown;
readonly downloadStickerPack: (
packId: string,
packKey: string,
options?: { finalStatus?: 'installed' | 'downloaded' }
) => unknown;
readonly installStickerPack: (packId: string, packKey: string) => unknown; readonly installStickerPack: (packId: string, packKey: string) => unknown;
readonly uninstallStickerPack: (packId: string, packKey: string) => unknown; readonly uninstallStickerPack: (packId: string, packKey: string) => unknown;
readonly pack: StickerPackType; readonly pack?: StickerPackType;
readonly i18n: LocalizerType; readonly i18n: LocalizerType;
}; };
@ -21,15 +29,57 @@ function focusRef(el: HTMLElement | null) {
} }
} }
function renderBody({ pack, i18n }: Props) {
if (pack && pack.status === 'error') {
return (
<div className="module-sticker-manager__preview-modal__container__error">
{i18n('stickers--StickerPreview--Error')}
</div>
);
}
if (!pack || pack.stickerCount === 0 || !isNumber(pack.stickerCount)) {
return <Spinner size="normal" />;
}
return (
<div className="module-sticker-manager__preview-modal__container__sticker-grid">
{pack.stickers.map(({ id, url }) => (
<div
key={id}
className="module-sticker-manager__preview-modal__container__sticker-grid__cell"
>
<img
className="module-sticker-manager__preview-modal__container__sticker-grid__cell__image"
src={url}
alt={pack.title}
/>
</div>
))}
{range(pack.stickerCount - pack.stickers.length).map(i => (
<div
key={`placeholder-${i}`}
className={classNames(
'module-sticker-manager__preview-modal__container__sticker-grid__cell',
'module-sticker-manager__preview-modal__container__sticker-grid__cell--placeholder'
)}
/>
))}
</div>
);
}
export const StickerPreviewModal = React.memo( export const StickerPreviewModal = React.memo(
// tslint:disable-next-line max-func-body-length // tslint:disable-next-line max-func-body-length
({ (props: Props) => {
onClose, const {
pack, onClose,
i18n, pack,
installStickerPack, i18n,
uninstallStickerPack, downloadStickerPack,
}: Props) => { installStickerPack,
uninstallStickerPack,
} = props;
const [root, setRoot] = React.useState<HTMLElement | null>(null); const [root, setRoot] = React.useState<HTMLElement | null>(null);
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false); const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
@ -40,15 +90,36 @@ export const StickerPreviewModal = React.memo(
return () => { return () => {
document.body.removeChild(div); document.body.removeChild(div);
setRoot(null);
}; };
}, []); }, []);
const isInstalled = pack.status === 'installed'; React.useEffect(() => {
if (pack && pack.status === 'known') {
downloadStickerPack(pack.id, pack.key);
}
if (
pack &&
pack.status === 'error' &&
(pack.attemptedStatus === 'downloaded' ||
pack.attemptedStatus === 'installed')
) {
downloadStickerPack(pack.id, pack.key, {
finalStatus: pack.attemptedStatus,
});
}
}, []);
const isInstalled = Boolean(pack && pack.status === 'installed');
const handleToggleInstall = React.useCallback( const handleToggleInstall = React.useCallback(
() => { () => {
if (!pack) {
return;
}
if (isInstalled) { if (isInstalled) {
setConfirmingUninstall(true); setConfirmingUninstall(true);
} else if (pack.status === 'ephemeral') {
downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' });
onClose();
} else { } else {
installStickerPack(pack.id, pack.key); installStickerPack(pack.id, pack.key);
onClose(); onClose();
@ -59,6 +130,9 @@ export const StickerPreviewModal = React.memo(
const handleUninstall = React.useCallback( const handleUninstall = React.useCallback(
() => { () => {
if (!pack) {
return;
}
uninstallStickerPack(pack.id, pack.key); uninstallStickerPack(pack.id, pack.key);
setConfirmingUninstall(false); setConfirmingUninstall(false);
// onClose is called by the confirmation modal // onClose is called by the confirmation modal
@ -119,42 +193,35 @@ export const StickerPreviewModal = React.memo(
className="module-sticker-manager__preview-modal__container__header__close-button" className="module-sticker-manager__preview-modal__container__header__close-button"
/> />
</header> </header>
<div className="module-sticker-manager__preview-modal__container__sticker-grid"> {renderBody(props)}
{pack.stickers.map(({ id, url }) => ( {pack && pack.status !== 'error' ? (
<div <div className="module-sticker-manager__preview-modal__container__meta-overlay">
key={id} <div className="module-sticker-manager__preview-modal__container__meta-overlay__info">
className="module-sticker-manager__preview-modal__container__sticker-grid__cell" <h3 className="module-sticker-manager__preview-modal__container__meta-overlay__info__title">
> {pack.title}
<img {pack.isBlessed ? (
className="module-sticker-manager__preview-modal__container__sticker-grid__cell__image" <span className="module-sticker-manager__preview-modal__container__meta-overlay__info__blessed-icon" />
src={url} ) : null}
alt={pack.title} </h3>
/> <h4 className="module-sticker-manager__preview-modal__container__meta-overlay__info__author">
{pack.author}
</h4>
</div>
<div className="module-sticker-manager__preview-modal__container__meta-overlay__install">
{pack.status === 'pending' ? (
<Spinner size="mini" />
) : (
<StickerPackInstallButton
ref={focusRef}
installed={isInstalled}
i18n={i18n}
onClick={handleToggleInstall}
blue={true}
/>
)}
</div> </div>
))}
</div>
<div className="module-sticker-manager__preview-modal__container__meta-overlay">
<div className="module-sticker-manager__preview-modal__container__meta-overlay__info">
<h3 className="module-sticker-manager__preview-modal__container__meta-overlay__info__title">
{pack.title}
{pack.isBlessed ? (
<span className="module-sticker-manager__preview-modal__container__meta-overlay__info__blessed-icon" />
) : null}
</h3>
<h4 className="module-sticker-manager__preview-modal__container__meta-overlay__info__author">
{pack.author}
</h4>
</div> </div>
<div className="module-sticker-manager__preview-modal__container__meta-overlay__install"> ) : null}
<StickerPackInstallButton
ref={focusRef}
installed={isInstalled}
i18n={i18n}
onClick={handleToggleInstall}
blue={true}
/>
</div>
</div>
</div> </div>
)} )}
</div>, </div>,

View file

@ -4,10 +4,15 @@ import {
updateStickerLastUsed, updateStickerLastUsed,
updateStickerPackStatus, updateStickerPackStatus,
} from '../../../js/modules/data'; } from '../../../js/modules/data';
import { maybeDeletePack } from '../../../js/modules/stickers'; import {
downloadStickerPack as externalDownloadStickerPack,
maybeDeletePack,
} from '../../../js/modules/stickers';
import { sendStickerPackSync } from '../../shims/textsecure'; import { sendStickerPackSync } from '../../shims/textsecure';
import { trigger } from '../../shims/events'; import { trigger } from '../../shims/events';
import { NoopActionType } from './noop';
// State // State
export type StickerDBType = { export type StickerDBType = {
@ -24,14 +29,20 @@ export type StickerPackDBType = {
readonly id: string; readonly id: string;
readonly key: string; readonly key: string;
readonly attemptedStatus: string; readonly attemptedStatus: 'downloaded' | 'installed' | 'ephemeral';
readonly author: string; readonly author: string;
readonly coverStickerId: number; readonly coverStickerId: number;
readonly createdAt: number; readonly createdAt: number;
readonly downloadAttempts: number; readonly downloadAttempts: number;
readonly installedAt: number | null; readonly installedAt: number | null;
readonly lastUsed: number; readonly lastUsed: number;
readonly status: 'advertised' | 'installed' | 'pending' | 'error'; readonly status:
| 'known'
| 'ephemeral'
| 'downloaded'
| 'installed'
| 'pending'
| 'error';
readonly stickerCount: number; readonly stickerCount: number;
readonly stickers: Dictionary<StickerDBType>; readonly stickers: Dictionary<StickerDBType>;
readonly title: string; readonly title: string;
@ -64,9 +75,16 @@ export type StickerPackType = {
readonly title: string; readonly title: string;
readonly author: string; readonly author: string;
readonly isBlessed: boolean; readonly isBlessed: boolean;
readonly cover: StickerType; readonly cover?: StickerType;
readonly lastUsed: number; readonly lastUsed: number;
readonly status: 'advertised' | 'installed' | 'pending' | 'error'; readonly attemptedStatus?: 'downloaded' | 'installed' | 'ephemeral';
readonly status:
| 'known'
| 'ephemeral'
| 'downloaded'
| 'installed'
| 'pending'
| 'error';
readonly stickers: Array<StickerType>; readonly stickers: Array<StickerType>;
readonly stickerCount: number; readonly stickerCount: number;
}; };
@ -103,7 +121,7 @@ type ClearInstalledStickerPackAction = {
type UninstallStickerPackPayloadType = { type UninstallStickerPackPayloadType = {
packId: string; packId: string;
status: 'advertised'; status: 'downloaded';
installedAt: null; installedAt: null;
recentStickers: Array<RecentStickerType>; recentStickers: Array<RecentStickerType>;
}; };
@ -148,11 +166,13 @@ export type StickersActionType =
| UninstallStickerPackFulfilledAction | UninstallStickerPackFulfilledAction
| StickerPackUpdatedAction | StickerPackUpdatedAction
| StickerPackRemovedAction | StickerPackRemovedAction
| UseStickerFulfilledAction; | UseStickerFulfilledAction
| NoopActionType;
// Action Creators // Action Creators
export const actions = { export const actions = {
downloadStickerPack,
clearInstalledStickerPack, clearInstalledStickerPack,
removeStickerPack, removeStickerPack,
stickerAdded, stickerAdded,
@ -191,6 +211,23 @@ function stickerPackAdded(payload: StickerPackDBType): StickerPackAddedAction {
}; };
} }
function downloadStickerPack(
packId: string,
packKey: string,
options?: { finalStatus?: 'installed' | 'downloaded' }
): NoopActionType {
const { finalStatus } = options || { finalStatus: undefined };
// We're just kicking this off, since it will generate more redux events
// tslint:disable-next-line:no-floating-promises
externalDownloadStickerPack(packId, packKey, { finalStatus });
return {
type: 'NOOP',
payload: null,
};
}
function installStickerPack( function installStickerPack(
packId: string, packId: string,
packKey: string, packKey: string,
@ -246,7 +283,7 @@ async function doUninstallStickerPack(
): Promise<UninstallStickerPackPayloadType> { ): Promise<UninstallStickerPackPayloadType> {
const { fromSync } = options || { fromSync: false }; const { fromSync } = options || { fromSync: false };
const status = 'advertised'; const status = 'downloaded';
await updateStickerPackStatus(packId, status); await updateStickerPackStatus(packId, status);
// If there are no more references, it should be removed // If there are no more references, it should be removed
@ -277,6 +314,13 @@ function stickerPackUpdated(
packId: string, packId: string,
patch: Partial<StickerPackDBType> patch: Partial<StickerPackDBType>
): StickerPackUpdatedAction { ): StickerPackUpdatedAction {
const { status, attemptedStatus } = patch;
// We do this to trigger a toast, which is still done via Backbone
if (status === 'error' && attemptedStatus === 'installed') {
trigger('pack-install-failed');
}
return { return {
type: 'stickers/STICKER_PACK_UPDATED', type: 'stickers/STICKER_PACK_UPDATED',
payload: { payload: {

View file

@ -6,6 +6,7 @@ import { LocalizerType } from '../../types/Util';
export type UserStateType = { export type UserStateType = {
attachmentsPath: string; attachmentsPath: string;
stickersPath: string; stickersPath: string;
tempPath: string;
ourNumber: string; ourNumber: string;
regionCode: string; regionCode: string;
i18n: LocalizerType; i18n: LocalizerType;
@ -45,6 +46,7 @@ function getEmptyState(): UserStateType {
return { return {
attachmentsPath: 'missing', attachmentsPath: 'missing',
stickersPath: 'missing', stickersPath: 'missing',
tempPath: 'missing',
ourNumber: 'missing', ourNumber: 'missing',
regionCode: 'missing', regionCode: 'missing',
i18n: () => 'missing', i18n: () => 'missing',

View file

@ -20,13 +20,14 @@ import {
StickersStateType, StickersStateType,
StickerType, StickerType,
} from '../ducks/stickers'; } from '../ducks/stickers';
import { getStickersPath } from './user'; import { getStickersPath, getTempPath } from './user';
const getSticker = ( const getSticker = (
packs: Dictionary<StickerPackDBType>, packs: Dictionary<StickerPackDBType>,
packId: string, packId: string,
stickerId: number, stickerId: number,
stickerPath: string stickerPath: string,
tempPath: string
): StickerType | undefined => { ): StickerType | undefined => {
const pack = packs[packId]; const pack = packs[packId];
if (!pack) { if (!pack) {
@ -38,20 +39,25 @@ const getSticker = (
return; return;
} }
return translateStickerFromDB(sticker, stickerPath); const isEphemeral = pack.status === 'ephemeral';
return translateStickerFromDB(sticker, stickerPath, tempPath, isEphemeral);
}; };
const translateStickerFromDB = ( const translateStickerFromDB = (
sticker: StickerDBType, sticker: StickerDBType,
stickerPath: string stickerPath: string,
tempPath: string,
isEphemeral: boolean
): StickerType => { ): StickerType => {
const { id, packId, emoji, path } = sticker; const { id, packId, emoji, path } = sticker;
const prefix = isEphemeral ? tempPath : stickerPath;
return { return {
id, id,
packId, packId,
emoji, emoji,
url: join(stickerPath, path), url: join(prefix, path),
}; };
}; };
@ -59,9 +65,11 @@ export const translatePackFromDB = (
pack: StickerPackDBType, pack: StickerPackDBType,
packs: Dictionary<StickerPackDBType>, packs: Dictionary<StickerPackDBType>,
blessedPacks: Dictionary<boolean>, blessedPacks: Dictionary<boolean>,
stickersPath: string stickersPath: string,
tempPath: string
) => { ) => {
const { id, stickers, coverStickerId } = pack; const { id, stickers, status, coverStickerId } = pack;
const isEphemeral = status === 'ephemeral';
// Sometimes sticker packs have a cover which isn't included in their set of stickers. // Sometimes sticker packs have a cover which isn't included in their set of stickers.
// We don't want to show cover-only images when previewing or picking from a pack. // We don't want to show cover-only images when previewing or picking from a pack.
@ -70,13 +78,13 @@ export const translatePackFromDB = (
sticker => sticker.isCoverOnly sticker => sticker.isCoverOnly
); );
const translatedStickers = map(filteredStickers, sticker => const translatedStickers = map(filteredStickers, sticker =>
translateStickerFromDB(sticker, stickersPath) translateStickerFromDB(sticker, stickersPath, tempPath, isEphemeral)
); );
return { return {
...pack, ...pack,
isBlessed: Boolean(blessedPacks[id]), isBlessed: Boolean(blessedPacks[id]),
cover: getSticker(packs, id, coverStickerId, stickersPath), cover: getSticker(packs, id, coverStickerId, stickersPath, tempPath),
stickers: sortBy(translatedStickers, sticker => sticker.id), stickers: sortBy(translatedStickers, sticker => sticker.id),
}; };
}; };
@ -86,18 +94,15 @@ const filterAndTransformPacks = (
packFilter: (sticker: StickerPackDBType) => boolean, packFilter: (sticker: StickerPackDBType) => boolean,
packSort: (sticker: StickerPackDBType) => any, packSort: (sticker: StickerPackDBType) => any,
blessedPacks: Dictionary<boolean>, blessedPacks: Dictionary<boolean>,
stickersPath: string stickersPath: string,
tempPath: string
): Array<StickerPackType> => { ): Array<StickerPackType> => {
const list = filter(packs, packFilter); const list = filter(packs, packFilter);
const sorted = orderBy<StickerPackDBType>(list, packSort, ['desc']); const sorted = orderBy<StickerPackDBType>(list, packSort, ['desc']);
const ready = sorted.map(pack => return sorted.map(pack =>
translatePackFromDB(pack, packs, blessedPacks, stickersPath) translatePackFromDB(pack, packs, blessedPacks, stickersPath, tempPath)
); );
// We're explicitly forcing pack.cover to be truthy here, but TypeScript doesn't
// understand that.
return ready.filter(pack => Boolean(pack.cover)) as Array<StickerPackType>;
}; };
const getStickers = (state: StateType) => state.stickers; const getStickers = (state: StateType) => state.stickers;
@ -121,14 +126,16 @@ export const getRecentStickers = createSelector(
getRecents, getRecents,
getPacks, getPacks,
getStickersPath, getStickersPath,
getTempPath,
( (
recents: Array<RecentStickerType>, recents: Array<RecentStickerType>,
packs: Dictionary<StickerPackDBType>, packs: Dictionary<StickerPackDBType>,
stickersPath: string stickersPath: string,
tempPath: string
) => { ) => {
return compact( return compact(
recents.map(({ packId, stickerId }) => { recents.map(({ packId, stickerId }) => {
return getSticker(packs, packId, stickerId, stickersPath); return getSticker(packs, packId, stickerId, stickersPath, tempPath);
}) })
); );
} }
@ -138,17 +145,20 @@ export const getInstalledStickerPacks = createSelector(
getPacks, getPacks,
getBlessedPacks, getBlessedPacks,
getStickersPath, getStickersPath,
getTempPath,
( (
packs: Dictionary<StickerPackDBType>, packs: Dictionary<StickerPackDBType>,
blessedPacks: Dictionary<boolean>, blessedPacks: Dictionary<boolean>,
stickersPath: string stickersPath: string,
tempPath: string
): Array<StickerPackType> => { ): Array<StickerPackType> => {
return filterAndTransformPacks( return filterAndTransformPacks(
packs, packs,
pack => pack.status === 'installed', pack => pack.status === 'installed',
pack => pack.installedAt, pack => pack.installedAt,
blessedPacks, blessedPacks,
stickersPath stickersPath,
tempPath
); );
} }
); );
@ -169,19 +179,22 @@ export const getReceivedStickerPacks = createSelector(
getPacks, getPacks,
getBlessedPacks, getBlessedPacks,
getStickersPath, getStickersPath,
getTempPath,
( (
packs: Dictionary<StickerPackDBType>, packs: Dictionary<StickerPackDBType>,
blessedPacks: Dictionary<boolean>, blessedPacks: Dictionary<boolean>,
stickersPath: string stickersPath: string,
tempPath: string
): Array<StickerPackType> => { ): Array<StickerPackType> => {
return filterAndTransformPacks( return filterAndTransformPacks(
packs, packs,
pack => pack =>
(pack.status === 'advertised' || pack.status === 'pending') && (pack.status === 'downloaded' || pack.status === 'pending') &&
!blessedPacks[pack.id], !blessedPacks[pack.id],
pack => pack.createdAt, pack => pack.createdAt,
blessedPacks, blessedPacks,
stickersPath stickersPath,
tempPath
); );
} }
); );
@ -190,17 +203,42 @@ export const getBlessedStickerPacks = createSelector(
getPacks, getPacks,
getBlessedPacks, getBlessedPacks,
getStickersPath, getStickersPath,
getTempPath,
( (
packs: Dictionary<StickerPackDBType>, packs: Dictionary<StickerPackDBType>,
blessedPacks: Dictionary<boolean>, blessedPacks: Dictionary<boolean>,
stickersPath: string stickersPath: string,
tempPath: string
): Array<StickerPackType> => { ): Array<StickerPackType> => {
return filterAndTransformPacks( return filterAndTransformPacks(
packs, packs,
pack => blessedPacks[pack.id] && pack.status !== 'installed', pack => blessedPacks[pack.id] && pack.status !== 'installed',
pack => pack.createdAt, pack => pack.createdAt,
blessedPacks, blessedPacks,
stickersPath stickersPath,
tempPath
);
}
);
export const getKnownStickerPacks = createSelector(
getPacks,
getBlessedPacks,
getStickersPath,
getTempPath,
(
packs: Dictionary<StickerPackDBType>,
blessedPacks: Dictionary<boolean>,
stickersPath: string,
tempPath: string
): Array<StickerPackType> => {
return filterAndTransformPacks(
packs,
pack => !blessedPacks[pack.id] && pack.status === 'known',
pack => pack.createdAt,
blessedPacks,
stickersPath,
tempPath
); );
} }
); );

View file

@ -31,3 +31,8 @@ export const getStickersPath = createSelector(
getUser, getUser,
(state: UserStateType): string => state.stickersPath (state: UserStateType): string => state.stickersPath
); );
export const getTempPath = createSelector(
getUser,
(state: UserStateType): string => state.tempPath
);

View file

@ -6,7 +6,9 @@ import { StateType } from '../reducer';
import { getIntl } from '../selectors/user'; import { getIntl } from '../selectors/user';
import { import {
getBlessedStickerPacks,
getInstalledStickerPacks, getInstalledStickerPacks,
getKnownStickerPacks,
getReceivedStickerPacks, getReceivedStickerPacks,
getRecentlyInstalledStickerPack, getRecentlyInstalledStickerPack,
getRecentStickers, getRecentStickers,
@ -15,6 +17,9 @@ import {
const mapStateToProps = (state: StateType) => { const mapStateToProps = (state: StateType) => {
const receivedPacks = getReceivedStickerPacks(state); const receivedPacks = getReceivedStickerPacks(state);
const installedPacks = getInstalledStickerPacks(state); const installedPacks = getInstalledStickerPacks(state);
const blessedPacks = getBlessedStickerPacks(state);
const knownPacks = getKnownStickerPacks(state);
const recentStickers = getRecentStickers(state); const recentStickers = getRecentStickers(state);
const installedPack = getRecentlyInstalledStickerPack(state); const installedPack = getRecentlyInstalledStickerPack(state);
const showIntroduction = get( const showIntroduction = get(
@ -29,6 +34,8 @@ const mapStateToProps = (state: StateType) => {
return { return {
receivedPacks, receivedPacks,
installedPack, installedPack,
blessedPacks,
knownPacks,
installedPacks, installedPacks,
recentStickers, recentStickers,
showIntroduction, showIntroduction,

View file

@ -7,6 +7,7 @@ import { getIntl } from '../selectors/user';
import { import {
getBlessedStickerPacks, getBlessedStickerPacks,
getInstalledStickerPacks, getInstalledStickerPacks,
getKnownStickerPacks,
getReceivedStickerPacks, getReceivedStickerPacks,
} from '../selectors/stickers'; } from '../selectors/stickers';
@ -14,11 +15,13 @@ const mapStateToProps = (state: StateType) => {
const blessedPacks = getBlessedStickerPacks(state); const blessedPacks = getBlessedStickerPacks(state);
const receivedPacks = getReceivedStickerPacks(state); const receivedPacks = getReceivedStickerPacks(state);
const installedPacks = getInstalledStickerPacks(state); const installedPacks = getInstalledStickerPacks(state);
const knownPacks = getKnownStickerPacks(state);
return { return {
blessedPacks, blessedPacks,
receivedPacks, receivedPacks,
installedPacks, installedPacks,
knownPacks,
i18n: getIntl(state), i18n: getIntl(state),
}; };
}; };

View file

@ -3,7 +3,7 @@ import { mapDispatchToProps } from '../actions';
import { StickerPreviewModal } from '../../components/stickers/StickerPreviewModal'; import { StickerPreviewModal } from '../../components/stickers/StickerPreviewModal';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { getIntl, getStickersPath } from '../selectors/user'; import { getIntl, getStickersPath, getTempPath } from '../selectors/user';
import { import {
getBlessedPacks, getBlessedPacks,
getPacks, getPacks,
@ -18,33 +18,17 @@ type ExternalProps = {
const mapStateToProps = (state: StateType, props: ExternalProps) => { const mapStateToProps = (state: StateType, props: ExternalProps) => {
const { packId } = props; const { packId } = props;
const stickersPath = getStickersPath(state); const stickersPath = getStickersPath(state);
const tempPath = getTempPath(state);
const packs = getPacks(state); const packs = getPacks(state);
const blessedPacks = getBlessedPacks(state); const blessedPacks = getBlessedPacks(state);
const pack = packs[packId]; const pack = packs[packId];
if (!pack) {
throw new Error(`Cannot find pack ${packId}`);
}
const translated = translatePackFromDB(
pack,
packs,
blessedPacks,
stickersPath
);
return { return {
...props, ...props,
pack: { pack: pack
...translated, ? translatePackFromDB(pack, packs, blessedPacks, stickersPath, tempPath)
cover: translated.cover : undefined,
? translated.cover
: {
id: 0,
url: 'nonexistent',
packId,
emoji: 'WTF',
},
},
i18n: getIntl(state), i18n: getIntl(state),
}; };
}; };

View file

@ -20,6 +20,8 @@ import mkdirp from 'mkdirp';
import rimraf from 'rimraf'; import rimraf from 'rimraf';
import { app, BrowserWindow, dialog } from 'electron'; import { app, BrowserWindow, dialog } from 'electron';
import { getTempPath } from '../../app/attachments';
// @ts-ignore // @ts-ignore
import * as packageJson from '../../package.json'; import * as packageJson from '../../package.json';
import { getSignatureFileName } from './signature'; import { getSignatureFileName } from './signature';
@ -269,7 +271,7 @@ function getGotOptions(): GotOptions<null> {
function getBaseTempDir() { function getBaseTempDir() {
// We only use tmpdir() when this code is run outside of an Electron app (as in: tests) // We only use tmpdir() when this code is run outside of an Electron app (as in: tests)
return app ? join(app.getPath('userData'), 'temp') : tmpdir(); return app ? getTempPath(app.getPath('userData')) : tmpdir();
} }
export async function createTempDir() { export async function createTempDir() {
@ -303,11 +305,6 @@ export function getPrintableError(error: Error) {
return error && error.stack ? error.stack : error; return error && error.stack ? error.stack : error;
} }
export async function deleteBaseTempDir() {
const baseTempDir = getBaseTempDir();
await rimrafPromise(baseTempDir);
}
export function getCliOptions<T>(options: any): T { export function getCliOptions<T>(options: any): T {
const parser = createParser({ options }); const parser = createParser({ options });
const cliOptions = parser.parse(process.argv); const cliOptions = parser.parse(process.argv);

View file

@ -3,12 +3,7 @@ import { BrowserWindow } from 'electron';
import { start as startMacOS } from './macos'; import { start as startMacOS } from './macos';
import { start as startWindows } from './windows'; import { start as startWindows } from './windows';
import { import { LoggerType, MessagesType } from './common';
deleteBaseTempDir,
getPrintableError,
LoggerType,
MessagesType,
} from './common';
let initialized = false; let initialized = false;
@ -39,15 +34,6 @@ export async function start(
return; return;
} }
try {
await deleteBaseTempDir();
} catch (error) {
logger.error(
'updater/start: Error deleting temp dir:',
getPrintableError(error)
);
}
if (platform === 'win32') { if (platform === 'win32') {
await startWindows(getMainWindow, messages, logger); await startWindows(getMainWindow, messages, logger);
} else if (platform === 'darwin') { } else if (platform === 'darwin') {

View file

@ -227,11 +227,19 @@
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2018-09-19T18:13:29.628Z" "updated": "2018-09-19T18:13:29.628Z"
}, },
{
"rule": "jQuery-load(",
"path": "js/modules/emojis.js",
"line": "async function load() {",
"lineNumber": 13,
"reasonCategory": "falseMatch",
"updated": "2019-05-23T22:27:53.554Z"
},
{ {
"rule": "jQuery-load(", "rule": "jQuery-load(",
"path": "js/modules/stickers.js", "path": "js/modules/stickers.js",
"line": "async function load() {", "line": "async function load() {",
"lineNumber": 53, "lineNumber": 57,
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2019-04-26T17:48:30.675Z" "updated": "2019-04-26T17:48:30.675Z"
}, },
@ -6066,13 +6074,5 @@
"lineNumber": 60, "lineNumber": 60,
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2019-05-02T20:44:56.470Z" "updated": "2019-05-02T20:44:56.470Z"
},
{
"rule": "jQuery-load(",
"path": "js/modules/emojis.js",
"line": "async function load() {",
"lineNumber": 13,
"reasonCategory": "falseMatch",
"updated": "2019-05-23T22:27:53.554Z"
} }
] ]