Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix syncing electron headers #2318

Merged
merged 2 commits into from
Nov 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { SelectedOperation } from 'altair-graphql-core/build/types/state/query.i
import { prettify } from './prettifier';
import { Position } from '../../utils/editor/helpers';
import { ElectronAppService } from '../electron-app/electron-app.service';
import { ELECTRON_ALLOWED_FORBIDDEN_HEADERS } from '@altairgraphql/electron-interop/build/constants';

interface SendRequestOptions {
query: string;
Expand Down Expand Up @@ -156,12 +157,15 @@ export class GqlService {
newHeaders = new HttpHeaders(this.defaultHeaders);
}

const forbiddenHeaders = ['Origin'];
if (headers?.length) {
// For electron app, send the instruction to set headers
this.electronAppService.setHeaders(headers);

if (headers && headers.length) {
headers.forEach((header) => {
if (
!forbiddenHeaders.includes(header.key) &&
!ELECTRON_ALLOWED_FORBIDDEN_HEADERS.includes(
header.key.toLowerCase()
) &&
header.enabled &&
header.key &&
header.value
Expand All @@ -176,18 +180,14 @@ export class GqlService {
}

getParamsFromData(data: IDictionary) {
return Object.keys(data).reduce(
(params, key) =>
data[key]
? params.set(
key,
typeof data[key] === 'object'
? JSON.stringify(data[key])
: data[key]
)
: params,
new HttpParams()
);
return Object.keys(data).reduce((params, key) => {
let value = data[key];
if (value) {
value = typeof value === 'object' ? JSON.stringify(value) : value;
params = params.set(key, value);
}
return params;
}, new HttpParams());
}

setUrl(url: string) {
Expand Down Expand Up @@ -244,7 +244,7 @@ export class GqlService {
getIntrospectionSchema(
introspection?: IntrospectionQuery
): GraphQLSchema | null {
if (!introspection || !introspection.__schema) {
if (!introspection?.__schema) {
return null;
}

Expand Down Expand Up @@ -373,7 +373,7 @@ export class GqlService {
const operation = this.getOperationAtIndex(query, index);

if (operation) {
return operation.name && operation.name.value ? operation.name.value : '';
return operation?.name?.value ?? '';
}
return '';
}
Expand Down Expand Up @@ -639,13 +639,6 @@ export class GqlService {
let params: HttpParams | undefined;
const headers = this.headers;

// For electron app, send the instruction to set headers
this.electronAppService.setHeaders(
headers
.keys()
.map((k) => ({ key: k, value: headers.get(k) ?? '', enabled: true }))
);

if (selectedOperation) {
data.operationName = selectedOperation;
}
Expand Down
7 changes: 7 additions & 0 deletions packages/altair-electron-interop/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,10 @@ export const STORE_EVENTS = {
};

export const electronApiKey = 'electronApi';

// https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
export const ELECTRON_ALLOWED_FORBIDDEN_HEADERS = [
'origin',
'cookie',
'referer',
].map(_ => _.toLowerCase());
4 changes: 4 additions & 0 deletions packages/altair-electron/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['altair'],
};
3 changes: 3 additions & 0 deletions packages/altair-electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@playwright/test": "1.31.2",
"@types/jest": "^28.1.7",
"@types/mime-types": "^2.1.1",
"altair-graphql-core": "^5.2.5",
"devtron": "^1.4.0",
"dotenv": "^8.1.0",
"electron": "^26.2.2",
Expand All @@ -43,6 +44,8 @@
"electron-chromedriver": "^14.0.0",
"electron-reloader": "^1.2.1",
"eslint": "^7.3.1",
"eslint-config-altair": "^5.0.22",
"eslint-config-prettier": "8.5.0",
"jest": "29.4.1",
"jest-circus": "28.0.0",
"playwright": "^1.18.1",
Expand Down
67 changes: 30 additions & 37 deletions packages/altair-electron/src/app/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,14 @@ import { ActionManager } from './actions';
import { TouchbarManager } from './touchbar';
import { handleWithCustomErrors } from '../utils/index';
import { AuthServer } from '../auth/server/index';
import ElectronStore from 'electron-store';
import { getAutobackup, setAutobackup } from '../utils/backup';
import { IPC_EVENT_NAMES } from '@altairgraphql/electron-interop';
import {
IPC_EVENT_NAMES,
ELECTRON_ALLOWED_FORBIDDEN_HEADERS,
} from '@altairgraphql/electron-interop';
import { HeaderState } from 'altair-graphql-core/build/types/state/header.interfaces';
import { log } from '../utils/log';

// https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
const HEADERS_TO_SET = ['Origin', 'Cookie', 'Referer'].map(_ =>
_.toLowerCase()
);

export class WindowManager {
instance?: BrowserWindow;

Expand Down Expand Up @@ -110,7 +108,6 @@ export class WindowManager {
slashes: true,
})
);
// instance.loadURL('http://localhost:4200/');

this.manageEvents();
}
Expand All @@ -126,11 +123,7 @@ export class WindowManager {
initSettingsStoreEvents();
initUpdateAvailableEvent(this.instance.webContents);
// Prevent the app from navigating away from the app
this.instance.webContents.on('will-navigate', e => e.preventDefault());

// instance.webContents.once('dom-ready', () => {
// instance.webContents.openDevTools();
// });
this.instance.webContents.on('will-navigate', (e) => e.preventDefault());

// Emitted when the window is closed.
this.instance.on('closed', () => {
Expand All @@ -153,7 +146,7 @@ export class WindowManager {
session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
log('Before request:', details);
if (details.uploadData) {
details.uploadData.forEach(uploadData => {
details.uploadData.forEach((uploadData) => {
log('Data sent:', uploadData.bytes.toString());
});
}
Expand Down Expand Up @@ -181,9 +174,9 @@ export class WindowManager {
);

if (process.env.NODE_ENV /* === 'test'*/) {
session.defaultSession.webRequest.onSendHeaders(details => {
session.defaultSession.webRequest.onSendHeaders((details) => {
if (details.requestHeaders) {
Object.keys(details.requestHeaders).forEach(headerKey => {
Object.keys(details.requestHeaders).forEach((headerKey) => {
log('Header sent:', headerKey, details.requestHeaders[headerKey]);
});
}
Expand All @@ -195,8 +188,6 @@ export class WindowManager {
details.resourceType === 'mainFrame' ||
details.resourceType === 'subFrame'
) {
// log('received headers..', details.responseHeaders);

// Set the CSP
const scriptSrc = [
`'self'`,
Expand All @@ -209,14 +200,14 @@ export class WindowManager {
];

return callback({
responseHeaders: Object.assign({}, details.responseHeaders, {
// Setting CSP
responseHeaders: {
...details.responseHeaders, // Setting CSP
// TODO: Figure out why an error from this breaks devtools
'Content-Security-Policy': [
`script-src ${scriptSrc.join(' ')}; object-src 'self';`,
// `script-src 'self' 'sha256-1Sj1x3xsk3UVwnakQHbO0yQ3Xm904avQIfGThrdrjcc=' '${createSha256CspHash(renderInitialOptions())}' https://cdn.jsdelivr.net localhost:*; object-src 'self';`
],
}),
},
});
}

Expand All @@ -228,17 +219,16 @@ export class WindowManager {
app.exit();
});

// TODO: Get type from altair-app as a devDependency
// Get 'set headers' instruction from app
ipcMain.on(
IPC_EVENT_NAMES.RENDERER_SET_HEADERS_SYNC,
(e, headers: { key: string; value: string; enabled?: boolean }[]) => {
(e, headers: HeaderState) => {
this.requestHeaders = {};

headers.forEach(header => {
headers.forEach((header) => {
const normalizedKey = header.key.toLowerCase();
if (
HEADERS_TO_SET.includes(normalizedKey) &&
ELECTRON_ALLOWED_FORBIDDEN_HEADERS.includes(normalizedKey) &&
header.key &&
header.value &&
header.enabled
Expand Down Expand Up @@ -272,7 +262,7 @@ export class WindowManager {
this.electronApp.store.delete('opened-file-data');
});

ipcMain.handle('reload-window', e => {
ipcMain.handle('reload-window', (e) => {
e.sender.reload();
});

Expand All @@ -284,18 +274,21 @@ export class WindowManager {
);

// TODO: Create an electron-interop package and move this there
handleWithCustomErrors(IPC_EVENT_NAMES.RENDERER_GET_AUTH_TOKEN, async e => {
if (!e.sender || e.sender !== this.instance?.webContents) {
throw new Error('untrusted source trying to get auth token');
}
handleWithCustomErrors(
IPC_EVENT_NAMES.RENDERER_GET_AUTH_TOKEN,
async (e) => {
if (!e.sender || e.sender !== this.instance?.webContents) {
throw new Error('untrusted source trying to get auth token');
}

const authServer = new AuthServer();
return authServer.getCustomToken();
});
const authServer = new AuthServer();
return authServer.getCustomToken();
}
);

handleWithCustomErrors(
IPC_EVENT_NAMES.RENDERER_GET_AUTOBACKUP_DATA,
async e => {
async (e) => {
if (!e.sender || e.sender !== this.instance?.webContents) {
throw new Error('untrusted source');
}
Expand All @@ -309,7 +302,7 @@ export class WindowManager {
/**
* Using a custom buffer protocol, instead of a file protocol because of restrictions with the file protocol.
*/
protocol.handle('altair', async request => {
protocol.handle('altair', async (request) => {
const requestDirectory = getDistDirectory();
const originalFilePath = path.join(
requestDirectory,
Expand Down Expand Up @@ -360,7 +353,7 @@ export class WindowManager {
if (!filePath) {
filePath = fallbackPath;
}
if (filePath && filePath.endsWith('.map')) {
if (filePath?.endsWith('.map')) {
return {
mimeType: 'text/plain',
data: Buffer.from(
Expand All @@ -372,7 +365,7 @@ export class WindowManager {
// some files are binary files, eg. font, so don't encode utf8
let data = await fs.readFile(filePath);

if (filePath && filePath.includes('index.html')) {
if (filePath?.includes('index.html')) {
data = Buffer.from(renderAltair(), 'utf-8');
}

Expand Down