feat(electron): add offline mode (#8086)

fix AF-1334

It seems `session.enableNetworkEmulation({ offline: true });` does not work - https://github.com/electron/electron/issues/21250

implemented using an in-house solution.

When turned on:

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/T2klNLEk0wxLh4NRDzhk/6805735b-1006-4e51-be46-c047b0f1a82c.png)
This commit is contained in:
pengx17 2024-09-04 10:46:43 +00:00
parent 51bc40d2a8
commit 0ae5673aaa
No known key found for this signature in database
GPG Key ID: 23F23D9E8B3971ED
4 changed files with 37 additions and 56 deletions

View File

@ -96,6 +96,13 @@ export const AFFINE_FLAGS = {
configurable: isCanaryBuild,
defaultState: isCanaryBuild,
},
enable_offline_mode: {
category: 'affine',
displayName: 'Offline Mode',
description: 'Enables offline mode.',
configurable: isDesktopEnvironment,
defaultState: false,
},
} satisfies { [key in string]: FlagInfo };
export type AFFINE_FLAGS = typeof AFFINE_FLAGS;

View File

@ -3,6 +3,7 @@ import { join } from 'node:path';
import { net, protocol, session } from 'electron';
import { CLOUD_BASE_URL } from './config';
import { isOfflineModeEnabled } from './utils';
import { getCookies } from './windows-manager';
protocol.registerSchemesAsPrivileged([
@ -105,9 +106,21 @@ export function registerProtocol() {
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
const url = new URL(details.url);
const pathname = url.pathname;
const protocol = url.protocol;
const origin = url.origin;
const sameOrigin = origin === CLOUD_BASE_URL || protocol === 'file:';
if (isOfflineModeEnabled() && (sameOrigin || 'devtools:' !== protocol)) {
callback({
cancel: true,
});
return;
}
// session cookies are set to file:// on production
// if sending request to the cloud, attach the session cookie (to affine cloud server)
if (isNetworkResource(pathname)) {
if (isNetworkResource(pathname) && sameOrigin) {
const cookie = getCookies();
if (cookie) {
const cookieString = cookie.map(c => `${c.name}=${c.value}`).join('; ');

View File

@ -3,6 +3,7 @@ import { autoUpdater as defaultAutoUpdater } from 'electron-updater';
import { buildType } from '../config';
import { logger } from '../logger';
import { isOfflineModeEnabled } from '../utils';
import { AFFiNEUpdateProvider } from './affine-update-provider';
import { updaterSubjects } from './event';
import { WindowsUpdater } from './windows-updater';
@ -54,7 +55,7 @@ export const setConfig = (newConfig: Partial<UpdaterConfig> = {}): void => {
};
export const checkForUpdates = async () => {
if (disabled || checkingUpdate) {
if (disabled || checkingUpdate || isOfflineModeEnabled()) {
return;
}
checkingUpdate = true;

View File

@ -1,8 +1,8 @@
import http from 'node:http';
import https from 'node:https';
import type { CookiesSetDetails } from 'electron';
import { logger } from './logger';
import { globalStateStorage } from './shared-storage/storage';
export function parseCookie(
cookieString: string,
url: string
@ -56,55 +56,15 @@ export function parseCookie(
return details;
}
/**
* Send a GET request to a specified URL.
* This function uses native http/https modules instead of fetch to
* bypassing set-cookies headers
*/
export async function simpleGet(requestUrl: string): Promise<{
body: string;
headers: [string, string][];
statusCode: number;
}> {
return new Promise((resolve, reject) => {
const parsedUrl = new URL(requestUrl);
const protocol = parsedUrl.protocol === 'https:' ? https : http;
const options = {
hostname: parsedUrl.hostname,
port: parsedUrl.port,
path: parsedUrl.pathname + parsedUrl.search,
method: 'GET',
};
const req = protocol.request(options, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
resolve({
body: data,
headers: toStandardHeaders(res.headers),
statusCode: res.statusCode || 200,
});
});
});
req.on('error', error => {
reject(error);
});
req.end();
});
function toStandardHeaders(headers: http.IncomingHttpHeaders) {
const result: [string, string][] = [];
for (const [key, value] of Object.entries(headers)) {
if (Array.isArray(value)) {
value.forEach(v => {
result.push([key, v]);
});
} else {
result.push([key, value || '']);
}
}
return result;
export const isOfflineModeEnabled = () => {
try {
return (
// todo(pengx17): better abstraction for syncing flags with electron
// packages/common/infra/src/modules/feature-flag/entities/flags.ts
globalStateStorage.get('affine-flag:enable_offline_mode') ?? false
);
} catch (error) {
logger.error('Failed to get offline mode flag', error);
return false;
}
}
};