Skip to content

Commit f69d84e

Browse files
committed
A couple minor fixes
1 parent f0b6f6b commit f69d84e

File tree

8 files changed

+142
-22
lines changed

8 files changed

+142
-22
lines changed

packages/altair-api/src/ai/prompt.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ You are an expert in GraphQL and Altair GraphQL Client (https://altairgraphql.de
2727
* If the user asks for troubleshooting help, use the provided SDL, query, variables, and response to diagnose the issue and suggest solutions.
2828
* Keep your answers as concise as possible while still providing enough detail to be helpful. Avoid unnecessary jargon or overly complex explanations.
2929
* When appropriate, provide examples to illustrate your points. This can help clarify complex concepts or demonstrate how to use Altair effectively.
30+
* When appropriate, suggest recommendations for improving the security, performance, or usability of the GraphQL queries.
3031
* Maintain a professional tone in all responses. Avoid slang or overly casual language.
3132
* Write your responses in markdown format.
3233
* Always wrap GraphQL queries in \`\`\`graphql\`\`\` code blocks.

packages/altair-app/src/app/modules/altair/effects/query.effect.ts

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -206,19 +206,14 @@ export class QueryEffects {
206206
);
207207
}
208208

209-
// Store the current query into the history if it does not already exist in the history
210-
if (
211-
!response.data.history.list.filter(
212-
(item) => item.query && item.query.trim() === query.trim()
213-
).length
214-
) {
215-
this.store.dispatch(
216-
new historyActions.AddHistoryAction(response.windowId, {
217-
query,
218-
limit: response.state.settings.historyDepth,
219-
})
220-
);
221-
}
209+
// Try to store the current query into the history if it does not already exist in the history
210+
this.store.dispatch(
211+
new historyActions.TryAddHistoryAction({
212+
windowId: response.windowId,
213+
query,
214+
limit: response.state.settings.historyDepth,
215+
})
216+
);
222217

223218
// perform some cleanup of previous state
224219
this.store.dispatch(
@@ -1434,6 +1429,51 @@ export class QueryEffects {
14341429
{ dispatch: false }
14351430
);
14361431

1432+
tryAddHistory$ = createEffect(
1433+
() => {
1434+
return this.actions$.pipe(
1435+
ofType(historyActions.TRY_ADD_HISTORY),
1436+
withLatestFrom(
1437+
this.store,
1438+
(action: historyActions.TryAddHistoryAction, state: RootState) => {
1439+
return {
1440+
data: state.windows[action.payload.windowId],
1441+
windowId: action.payload.windowId,
1442+
action,
1443+
};
1444+
}
1445+
),
1446+
switchMap((res) => {
1447+
if (!res.data) {
1448+
return EMPTY;
1449+
}
1450+
1451+
const matchesPromise = res.data.history.list.map(async (item) => {
1452+
return (
1453+
(await this.gqlService.compress(item.query)) ===
1454+
(await this.gqlService.compress(res.action.payload.query))
1455+
);
1456+
});
1457+
1458+
return from(Promise.all(matchesPromise)).pipe(
1459+
map((matches) => {
1460+
if (!matches.includes(true)) {
1461+
// If the query is not in history, add it
1462+
this.store.dispatch(
1463+
new historyActions.AddHistoryAction(res.windowId, {
1464+
query: res.action.payload.query,
1465+
limit: res.action.payload.limit,
1466+
})
1467+
);
1468+
}
1469+
})
1470+
);
1471+
})
1472+
);
1473+
},
1474+
{ dispatch: false }
1475+
);
1476+
14371477
// Get the introspection after setting the URL
14381478
constructor(
14391479
private actions$: Actions,

packages/altair-app/src/app/modules/altair/store/history/history.action.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Action as NGRXAction } from '@ngrx/store';
22

33
export const ADD_HISTORY = 'ADD_HISTORY';
44
export const CLEAR_HISTORY = 'CLEAR_HISTORY';
5+
export const TRY_ADD_HISTORY = 'TRY_ADD_HISTORY';
56

67
export class AddHistoryAction implements NGRXAction {
78
readonly type = ADD_HISTORY;
@@ -18,4 +19,10 @@ export class ClearHistoryAction implements NGRXAction {
1819
constructor(public windowId: string) {}
1920
}
2021

21-
export type Action = AddHistoryAction | ClearHistoryAction;
22+
export class TryAddHistoryAction implements NGRXAction {
23+
readonly type = TRY_ADD_HISTORY;
24+
25+
constructor(public payload: { windowId: string; query: string; limit?: number }) {}
26+
}
27+
28+
export type Action = AddHistoryAction | ClearHistoryAction | TryAddHistoryAction;

packages/altair-electron/src/app/window.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import {
4141
getPersisedSettingsFromFile,
4242
updateAltairSettingsOnFile,
4343
} from '../settings/main/store';
44+
import { cspAsString } from '../utils/csp';
45+
import { SENTRY_CSP_REPORT_URI } from '../constants';
4446

4547
export class WindowManager {
4648
private instance?: BrowserWindow;
@@ -306,19 +308,37 @@ export class WindowManager {
306308
`'sha256-1Sj1x3xsk3UVwnakQHbO0yQ3Xm904avQIfGThrdrjcc='`,
307309
`'${createSha256CspHash(renderInitSnippet(this.getRenderOptions()))}'`,
308310
`https://cdn.jsdelivr.net`,
309-
`https://apis.google.com`,
310311
`localhost:*`,
311312
`file:`,
312313
];
313314

315+
const additionalHeaders = {
316+
// TODO: Figure out why an error from this breaks devtools
317+
'Content-Security-Policy': [
318+
cspAsString({
319+
'script-src': scriptSrc,
320+
'object-src': ["'self'"],
321+
'report-uri': [SENTRY_CSP_REPORT_URI],
322+
'report-to': ['csp-endpoint'],
323+
}),
324+
],
325+
'Report-To': JSON.stringify({
326+
group: 'csp-endpoint',
327+
max_age: 10886400, // 3 months
328+
endpoints: [
329+
{
330+
url: SENTRY_CSP_REPORT_URI,
331+
},
332+
],
333+
include_subdomains: true,
334+
}),
335+
'Reporting-Endpoints': `csp-endpoint="${SENTRY_CSP_REPORT_URI}"`,
336+
};
337+
314338
return callback({
315339
responseHeaders: {
316-
...details.responseHeaders, // Setting CSP
317-
// TODO: Figure out why an error from this breaks devtools
318-
'Content-Security-Policy': [
319-
`script-src ${scriptSrc.join(' ')}; object-src 'self';`,
320-
// `script-src 'self' 'sha256-1Sj1x3xsk3UVwnakQHbO0yQ3Xm904avQIfGThrdrjcc=' '${createSha256CspHash(renderInitSnippet())}' https://cdn.jsdelivr.net localhost:*; object-src 'self';`
321-
],
340+
...details.responseHeaders,
341+
...additionalHeaders, // Additional headers
322342
},
323343
});
324344
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const SENTRY_DSN =
2+
'https://1b08762f991476e3115e1ab7d12e6682@o4506180788879360.ingest.sentry.io/4506198594813952';
3+
export const SENTRY_CSP_REPORT_URI = `https://o4506180788879360.ingest.us.sentry.io/api/4506198594813952/security/?sentry_key=1b08762f991476e3115e1ab7d12e6682`;

packages/altair-electron/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import * as Sentry from '@sentry/electron';
33
import { ElectronApp } from './app';
44
import { app } from 'electron';
55
import { configureAppOnStartup } from './utils/startup';
6+
import { SENTRY_DSN } from './constants';
67

78
Sentry.init({
8-
dsn: 'https://1b08762f991476e3115e1ab7d12e6682@o4506180788879360.ingest.sentry.io/4506198594813952',
9+
dsn: SENTRY_DSN,
910
release: app.getVersion(),
1011
});
1112
configureAppOnStartup(app);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { cspAsString } from './csp';
2+
3+
describe('cspAsString', () => {
4+
it('should return a valid CSP string', () => {
5+
const csp = {
6+
'default-src': ["'self'", "'unsafe-inline'"],
7+
'script-src': [
8+
"'self'",
9+
'sha256-abc123',
10+
'https://example.com',
11+
"'nonce-xyz789'",
12+
'unsafe-eval',
13+
],
14+
'style-src': ["'self'", "'nonce-xyz789'"],
15+
};
16+
17+
const result = cspAsString(csp);
18+
expect(result).toBe(
19+
"default-src 'self' 'unsafe-inline'; script-src 'self' 'sha256-abc123' https://example.com 'nonce-xyz789' 'unsafe-eval'; style-src 'self' 'nonce-xyz789'"
20+
);
21+
});
22+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const QUOTED_VALUES = [
2+
'self',
3+
'unsafe-inline',
4+
'unsafe-eval',
5+
'none',
6+
'strict-dynamic',
7+
'report-sample',
8+
'wasm-unsafe-eval',
9+
];
10+
const QUOTED_PREFIXES = ['sha256-', 'sha384-', 'sha512-', 'nonce-'];
11+
export const cspAsString = (csp: Record<string, string[]>): string => {
12+
return Object.entries(csp)
13+
.map(([key, values]) => {
14+
const mappedValues = values.map((value) => {
15+
if (QUOTED_VALUES.includes(value)) {
16+
return `'${value}'`;
17+
}
18+
if (QUOTED_PREFIXES.some((prefix) => value.startsWith(prefix))) {
19+
return `'${value}'`;
20+
}
21+
return value;
22+
});
23+
return `${key} ${mappedValues.join(' ')}`;
24+
})
25+
.join('; ');
26+
};

0 commit comments

Comments
 (0)