You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/updater/common.ts

324 lines
7.8 KiB
TypeScript

Session v1.0 changes (#802) * correct typo in readme * include log * decrypt file server response, remove debug, handle crypt before _sendToProxy, improve json parsing failure logging * support file uploads on file proxy, fix _sendToProxy calling * bump form-data to 3.0 * initial refactor of feaure flag detection statements in serverRequest() * fix send-message line-height with multiple lines * fix lint * fix position of delete account modal * Profile picture upload, fixes and copy * Various changes suggested by redesign overview * Scrolling button updated and animations to modals * Display subscriber count for open chats * Prevent illegal username and passwords * Delete channel / group merge * Solidification of minor changes w appview injections * hide description field in group panel for now * fix join publicgroups pulls * increase min height respecting ratio * allow space inside a display name but not at start or end * fix height of leftpane overlay view * add back typing indicator and read receipt setting under privacy * Auto-focus new open chat input box * Password lock screen and delete data screen * touchups * Resolving Bilb revisions * Disable link previews as default per Kee on signup * remove date, we have git * add missing semicolon * _sendToProxy pass headers/handle response refactor, lint * fix my yarn conflict/resolve * include IV in server response * Sealed sender support * Support sealed sender for friend requests * fix lint * Remove unused destinationRegistrationId; lint * Update messages.json * pull RSS through file proxy * fix unit tests: remove not used count in scrolldown view and assert svg present * Disable auto-joining default loki open groups * session-id-editable-textarea * fix the textscramble for sessionID on registration * speed up lint, add lint-full/format-full, make sure use lint-full * add skipToken to establishConnection options, smuggle out secureRpcPubKey * get latest version through snode proxy, remove clearfix from ExpiredAlertBanner * expose semver and LokiAppDotNetServerAPI because we can't get ourKey from storage early enough * update note * fix upgrade link, wrap expiredWarning in span for styling, use br to clear the float, trim trailing whitespace * designalify * designalify * designalify user agent * continue designalification * make expired banner legible * remove ugly TLS hack * disable unauthorization rejection when making https requests limited to lokiRpc * Update main.js Aspect ratio amendment * Constants rework * local commit * event listeners * address missing comma for lint * fix header sessions message section * fix profile image size conversation list with pending friend request * textarea centering * refresh files in group in group panel * Looking into keyboard navigation * Remove P2P * cache eslint on `lint` but not `ready` * Cleanup media view formatting * force locale to be EN until our files are updated and translated * Simplification of keyup * Updated all icon references * SASS fixup * fix disabled state of message input on sent friendrequest * trim pubkey when user can enter one to remove whitespaces * remove lZ in path which fixes errors on svg and does not alter rendering * fix text scramble animation on registration * reload app on ctrl-r or f5 from anywhere * add back file which should have not been deleted * fix lint and clean code * fix lint * add .loki to have a self-signed cert * Remove mixpanel * use local shortcut instead of global shortcut otherwise, ctrl+r is only caught bu the last loaded instance * open the conversation when accepting a friend request also, it does what is needed to show the new friend in the friend list * make sure token comms are done over fileProxy, other notes, logging adjustment * leftpane sections titles are Wasa bold * minor refactor * onboarding messageview * linter * fix padding buttons overlay * do not render session-id-editable border when textarea disabled * textarea sessionID SpaceMono font * various touchups * fix font of description to sfprodisplay * reduce triple dots conversation header icon size * reduce size of conversationHeader title font size * fix font for session-search-input * make conversationlistitem title font wasa * fix green and white border under title in leftpane * fix panel-text-divider font-size and family * disable completely borders for profile images * make profile image which where 48pixels big 36 noew, as no more border * Complete conditional message onboarding * cache file deletions * Link preview warning on setting toggle * Messages.json amendments * Join channel generalisation * Localise global vars * remove eslintcache * rm global launchcount * Remove source field from envelope * Session public chat icon * CLosed groups ui initial listprops * Desktop: enable useSnodeProxy feature flag * file proxy needs to be able to talk to snode - disable TLS check for fileProxy - lokiHttpsAgent => snodeHttpsAgent (since we use for two different things now) * enable useSealedSender too per Maxim * lint * lint * window.extension.expiredPromise version * better error checking * use promise version to see if we're expired * fix typo * lint * put back seemingly now required process.env.NODE_TLS_REJECT_UNAUTHORIZED * fix querystring in file-proxy * lint * fix typo * Remove more references to signal.org * make sure TLS is forced on open groups, improve serverRequest error message * Closed groups UI * function params changes * turn off snode proxy logging * include useful info on error * actually validate URL before starting up a bunch of timers * Closed groups overlay integration * move comments from connecting_to_server_dialog_view * use attempt from window object to reduce code duplication * refactor out validServer() * lint * lint caught typo * Rename BACKGROUND_FRIEND_REQUEST to SESSION_REQUEST. Don't trigger friend request logic if a message is aimed at a group. * Linting * Closed group joining completed w/o backend * Fix friend request messages being sent to users you don't have a session in closed groups. Disable typing messages and read receipts in groups. Send out session request messages if you don't have a session with a member in the group. * Remove unneeded boolean condition. * Closed group update message stylgin * constants renaming * Message deletion fix * gruntify * fix grunt error * expose isRss, don't close uncloseable Rss conversation on deleteMessages * remove copyId and block user on RSS feeds * remove options from RSS feed that don't make any sense and don't work * fix grunt error * squelch RSS duplicate messages * extension.expiredStatus(), adjustable timers, improve guards * allowing sending of messages if we're still waiting to hear back * markRandomNodeUnreachable() refactor, notes/logging * improve logging * improve logging * no need to validate empty token, support lokinet/getession file domains, mark broken snodes as bad, improve logging * try to address travis-osx lint complaints * not designed to have a period at the end of titleIsNow * put period back at the end * Catch a stray loki messenger * fix stray loki messenger * loki messenger isnt a thing * lint * Fix open group joining. * guards incase there are no members yet, fixes dialog not showing up * fixed file server holding up message sender init. fix joining closed groups. * Clean * Don't wait for file server to return tokens when establishing home connection. * Disable join public chat prompt Co-authored-by: Audric Ackermann <audric.bilb@gmail.com> Co-authored-by: Ryan Tharp <neuro@interx.net> Co-authored-by: Vince <vincent@loki.network> Co-authored-by: Maxim Shishmarev <msgmaxim@gmail.com>
5 years ago
import {
createWriteStream,
statSync,
writeFile as writeFileCallback,
} from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
// @ts-ignore
import { createParser } from 'dashdash';
// @ts-ignore
import ProxyAgent from 'proxy-agent';
import { FAILSAFE_SCHEMA, safeLoad } from 'js-yaml';
import { gt } from 'semver';
import { get as getFromConfig } from 'config';
import { get, GotOptions, stream } from 'got';
import { v4 as getGuid } from 'uuid';
import pify from 'pify';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import { app, BrowserWindow, dialog } from 'electron';
// @ts-ignore
import * as packageJson from '../../package.json';
import { getSignatureFileName } from './signature';
export type MessagesType = {
[key: string]: {
message: string;
description?: string;
};
};
type LogFunction = (...args: Array<any>) => void;
export type LoggerType = {
fatal: LogFunction;
error: LogFunction;
warn: LogFunction;
info: LogFunction;
debug: LogFunction;
trace: LogFunction;
};
const writeFile = pify(writeFileCallback);
const mkdirpPromise = pify(mkdirp);
const rimrafPromise = pify(rimraf);
const { platform } = process;
export async function checkForUpdates(
logger: LoggerType
): Promise<{
fileName: string;
version: string;
} | null> {
const yaml = await getUpdateYaml();
const version = getVersion(yaml);
if (!version) {
logger.warn('checkForUpdates: no version extracted from downloaded yaml');
return null;
}
if (isVersionNewer(version)) {
logger.info(`checkForUpdates: found newer version ${version}`);
return {
fileName: getUpdateFileName(yaml),
version,
};
}
logger.info(
`checkForUpdates: ${version} is not newer; no new update available`
);
return null;
}
export async function downloadUpdate(
fileName: string,
logger: LoggerType
): Promise<string> {
const baseUrl = getUpdatesBase();
const updateFileUrl = `${baseUrl}/${fileName}`;
const signatureFileName = getSignatureFileName(fileName);
const signatureUrl = `${baseUrl}/${signatureFileName}`;
let tempDir;
try {
tempDir = await createTempDir();
const targetUpdatePath = join(tempDir, fileName);
const targetSignaturePath = join(tempDir, getSignatureFileName(fileName));
logger.info(`downloadUpdate: Downloading ${signatureUrl}`);
const { body } = await get(signatureUrl, getGotOptions());
await writeFile(targetSignaturePath, body);
logger.info(`downloadUpdate: Downloading ${updateFileUrl}`);
const downloadStream = stream(updateFileUrl, getGotOptions());
const writeStream = createWriteStream(targetUpdatePath);
await new Promise((resolve, reject) => {
downloadStream.on('error', error => {
reject(error);
});
downloadStream.on('end', () => {
resolve();
});
writeStream.on('error', error => {
reject(error);
});
downloadStream.pipe(writeStream);
});
return targetUpdatePath;
} catch (error) {
if (tempDir) {
await deleteTempDir(tempDir);
}
throw error;
}
}
export async function showUpdateDialog(
mainWindow: BrowserWindow,
messages: MessagesType
): Promise<boolean> {
const RESTART_BUTTON = 0;
const LATER_BUTTON = 1;
const options = {
type: 'info',
buttons: [
messages.autoUpdateRestartButtonLabel.message,
messages.autoUpdateLaterButtonLabel.message,
],
title: messages.autoUpdateNewVersionTitle.message,
message: messages.autoUpdateNewVersionMessage.message,
detail: messages.autoUpdateNewVersionInstructions.message,
defaultId: LATER_BUTTON,
cancelId: RESTART_BUTTON,
};
return new Promise(resolve => {
dialog.showMessageBox(mainWindow, options, response => {
if (response === RESTART_BUTTON) {
// It's key to delay any install calls here because they don't seem to work inside this
// callback - but only if the message box has a parent window.
// Fixes this: https://github.com/signalapp/Signal-Desktop/issues/1864
resolve(true);
return;
}
resolve(false);
});
});
}
export async function showCannotUpdateDialog(
mainWindow: BrowserWindow,
messages: MessagesType
): Promise<boolean> {
const options = {
type: 'error',
buttons: [messages.ok.message],
title: messages.cannotUpdate.message,
message: messages.cannotUpdateDetail.message,
};
return new Promise(resolve => {
dialog.showMessageBox(mainWindow, options, () => {
resolve();
});
});
}
// Helper functions
export function getUpdateCheckUrl(): string {
return `${getUpdatesBase()}/${getUpdatesFileName()}`;
}
export function getUpdatesBase(): string {
return getFromConfig('updatesUrl');
}
export function getCertificateAuthority(): string {
return getFromConfig('certificateAuthority');
}
export function getProxyUrl(): string | undefined {
return process.env.HTTPS_PROXY || process.env.https_proxy;
}
export function getUpdatesFileName(): string {
const prefix = isBetaChannel() ? 'beta' : 'latest';
if (platform === 'darwin') {
return `${prefix}-mac.yml`;
} else {
return `${prefix}.yml`;
}
}
const hasBeta = /beta/i;
function isBetaChannel(): boolean {
return hasBeta.test(packageJson.version);
}
function isVersionNewer(newVersion: string): boolean {
const { version } = packageJson;
return gt(newVersion, version);
}
export function getVersion(yaml: string): string | undefined {
const info = parseYaml(yaml);
if (info && info.version) {
return info.version;
}
return;
}
export function getUpdateFileName(yaml: string) {
const info = parseYaml(yaml);
if (info && info.path) {
return info.path;
}
return;
}
function parseYaml(yaml: string): any {
return safeLoad(yaml, { schema: FAILSAFE_SCHEMA, json: true });
}
async function getUpdateYaml(): Promise<string> {
const targetUrl = getUpdateCheckUrl();
const { body } = await get(targetUrl, getGotOptions());
if (!body) {
throw new Error('Got unexpected response back from update check');
}
return body.toString('utf8');
}
function getGotOptions(): GotOptions<null> {
const ca = getCertificateAuthority();
const proxyUrl = getProxyUrl();
const agent = proxyUrl ? new ProxyAgent(proxyUrl) : undefined;
return {
agent,
ca,
headers: {
'Cache-Control': 'no-cache',
'User-Agent': 'Session Desktop (+https://getsession.org)',
},
useElectronNet: false,
};
}
function getBaseTempDir() {
// 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();
}
export async function createTempDir() {
const baseTempDir = getBaseTempDir();
const uniqueName = getGuid();
const targetDir = join(baseTempDir, uniqueName);
await mkdirpPromise(targetDir);
return targetDir;
}
export async function deleteTempDir(targetDir: string) {
const pathInfo = statSync(targetDir);
if (!pathInfo.isDirectory()) {
throw new Error(
`deleteTempDir: Cannot delete path '${targetDir}' because it is not a directory`
);
}
const baseTempDir = getBaseTempDir();
if (!targetDir.startsWith(baseTempDir)) {
throw new Error(
`deleteTempDir: Cannot delete path '${targetDir}' since it is not within base temp dir`
);
}
await rimrafPromise(targetDir);
}
export function getPrintableError(error: 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 {
const parser = createParser({ options });
const cliOptions = parser.parse(process.argv);
if (cliOptions.help) {
const help = parser.help().trimRight();
// tslint:disable-next-line:no-console
console.log(help);
process.exit(0);
}
return cliOptions;
}