mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 05:48:59 +08:00
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:

This commit is contained in:
@@ -96,6 +96,13 @@ export const AFFINE_FLAGS = {
|
|||||||
configurable: isCanaryBuild,
|
configurable: isCanaryBuild,
|
||||||
defaultState: isCanaryBuild,
|
defaultState: isCanaryBuild,
|
||||||
},
|
},
|
||||||
|
enable_offline_mode: {
|
||||||
|
category: 'affine',
|
||||||
|
displayName: 'Offline Mode',
|
||||||
|
description: 'Enables offline mode.',
|
||||||
|
configurable: isDesktopEnvironment,
|
||||||
|
defaultState: false,
|
||||||
|
},
|
||||||
} satisfies { [key in string]: FlagInfo };
|
} satisfies { [key in string]: FlagInfo };
|
||||||
|
|
||||||
export type AFFINE_FLAGS = typeof AFFINE_FLAGS;
|
export type AFFINE_FLAGS = typeof AFFINE_FLAGS;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|||||||
import { net, protocol, session } from 'electron';
|
import { net, protocol, session } from 'electron';
|
||||||
|
|
||||||
import { CLOUD_BASE_URL } from './config';
|
import { CLOUD_BASE_URL } from './config';
|
||||||
|
import { isOfflineModeEnabled } from './utils';
|
||||||
import { getCookies } from './windows-manager';
|
import { getCookies } from './windows-manager';
|
||||||
|
|
||||||
protocol.registerSchemesAsPrivileged([
|
protocol.registerSchemesAsPrivileged([
|
||||||
@@ -105,9 +106,21 @@ export function registerProtocol() {
|
|||||||
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
|
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||||
const url = new URL(details.url);
|
const url = new URL(details.url);
|
||||||
const pathname = url.pathname;
|
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
|
// session cookies are set to file:// on production
|
||||||
// if sending request to the cloud, attach the session cookie (to affine cloud server)
|
// if sending request to the cloud, attach the session cookie (to affine cloud server)
|
||||||
if (isNetworkResource(pathname)) {
|
if (isNetworkResource(pathname) && sameOrigin) {
|
||||||
const cookie = getCookies();
|
const cookie = getCookies();
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
const cookieString = cookie.map(c => `${c.name}=${c.value}`).join('; ');
|
const cookieString = cookie.map(c => `${c.name}=${c.value}`).join('; ');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { autoUpdater as defaultAutoUpdater } from 'electron-updater';
|
|||||||
|
|
||||||
import { buildType } from '../config';
|
import { buildType } from '../config';
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
|
import { isOfflineModeEnabled } from '../utils';
|
||||||
import { AFFiNEUpdateProvider } from './affine-update-provider';
|
import { AFFiNEUpdateProvider } from './affine-update-provider';
|
||||||
import { updaterSubjects } from './event';
|
import { updaterSubjects } from './event';
|
||||||
import { WindowsUpdater } from './windows-updater';
|
import { WindowsUpdater } from './windows-updater';
|
||||||
@@ -54,7 +55,7 @@ export const setConfig = (newConfig: Partial<UpdaterConfig> = {}): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const checkForUpdates = async () => {
|
export const checkForUpdates = async () => {
|
||||||
if (disabled || checkingUpdate) {
|
if (disabled || checkingUpdate || isOfflineModeEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checkingUpdate = true;
|
checkingUpdate = true;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import http from 'node:http';
|
|
||||||
import https from 'node:https';
|
|
||||||
|
|
||||||
import type { CookiesSetDetails } from 'electron';
|
import type { CookiesSetDetails } from 'electron';
|
||||||
|
|
||||||
|
import { logger } from './logger';
|
||||||
|
import { globalStateStorage } from './shared-storage/storage';
|
||||||
|
|
||||||
export function parseCookie(
|
export function parseCookie(
|
||||||
cookieString: string,
|
cookieString: string,
|
||||||
url: string
|
url: string
|
||||||
@@ -56,55 +56,15 @@ export function parseCookie(
|
|||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const isOfflineModeEnabled = () => {
|
||||||
* Send a GET request to a specified URL.
|
try {
|
||||||
* This function uses native http/https modules instead of fetch to
|
return (
|
||||||
* bypassing set-cookies headers
|
// todo(pengx17): better abstraction for syncing flags with electron
|
||||||
*/
|
// packages/common/infra/src/modules/feature-flag/entities/flags.ts
|
||||||
export async function simpleGet(requestUrl: string): Promise<{
|
globalStateStorage.get('affine-flag:enable_offline_mode') ?? false
|
||||||
body: string;
|
);
|
||||||
headers: [string, string][];
|
} catch (error) {
|
||||||
statusCode: number;
|
logger.error('Failed to get offline mode flag', error);
|
||||||
}> {
|
return false;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user