Skip to content

Commit

Permalink
refactor: init all from index, move http clients and messaging modules (
Browse files Browse the repository at this point in the history
  • Loading branch information
MikkCZ authored Oct 6, 2024
1 parent 9ce0af3 commit adf0cf0
Show file tree
Hide file tree
Showing 34 changed files with 292 additions and 280 deletions.
12 changes: 5 additions & 7 deletions src/background/RemotePontoon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ import {
pontoonUserData,
bugzillaTeamComponents,
} from './apiEndpoints';
import type { GetProjectsInfoResponse } from './httpClients';
import {
pontoonHttpClient,
httpClient,
pontoonGraphqlClient,
} from './httpClients';
import { httpClient } from './httpClients/httpClient';
import type { GetProjectsInfoResponse } from './httpClients/pontoonGraphqlClient';
import { pontoonGraphqlClient } from './httpClients/pontoonGraphqlClient';
import { pontoonHttpClient } from './httpClients/pontoonHttpClient';
import { projectsListData } from './data/projectsListData';

type GetProjectsInfoProject = GetProjectsInfoResponse['projects'][number];
Expand Down Expand Up @@ -59,7 +57,7 @@ function parseDOM(pageContent: string) {
return new DOMParser().parseFromString(pageContent, 'text/html');
}

export function listenToMessagesFromClients() {
export function initMessageListeners() {
listenToMessages<'PAGE_LOADED'>('pontoon-page-loaded', ({ documentHTML }) =>
updateNotificationsIfThereAreNew(documentHTML),
);
Expand Down
2 changes: 1 addition & 1 deletion src/background/addressBarIcon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {

import { getPontoonProjectForPageUrl } from './RemotePontoon';

export function setupAddressBarIcon() {
export function init() {
listenToStorageChange('projectsList', async () => {
updatePageActions(await getAllTabs());
});
Expand Down
2 changes: 1 addition & 1 deletion src/background/contextButtons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { getOneOption, getOptions } from '@commons/options';
import { openNewPontoonTab } from '@commons/utils';

export function setupPageContextButtons() {
export function init() {
listenToMessagesFromContentScript();
}

Expand Down
2 changes: 1 addition & 1 deletion src/background/contextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const NON_SELECTION_CONTEXTS: Menus.ContextType[] = [
];
const SELECTION_CONTEXTS: Menus.ContextType[] = ['selection'];

export function setupPageContextMenus() {
export function init() {
listenToStorageChange('projectsList', () => createContextMenuItems());
listenToStorageChange('teamsList', () => createContextMenuItems());
listenToOptionChange('pontoon_base_url', () => createContextMenuItems());
Expand Down
6 changes: 3 additions & 3 deletions src/background/dataRefresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import {
getOptions,
listenToOptionChange,
} from '@commons/options';
import type { BackgroundClientMessageWithoutResponse } from '@commons/BackgroundClientMessageType';
import type { BackgroundMessagesWithoutResponse } from '@commons/backgroundMessaging';

import { refreshData } from './RemotePontoon';

export function setupDataRefresh() {
export function init() {
listenToOptionChange(
'data_update_interval',
({ newValue: periodInMinutes }) => {
Expand Down Expand Up @@ -87,7 +87,7 @@ function registerLiveDataProvider() {
contextualIdentity !== tab.cookieStoreId &&
typeof tab.id !== 'undefined'
) {
const message: BackgroundClientMessageWithoutResponse['DISABLE_NOTIFICATIONS_BELL_SCRIPT']['message'] =
const message: BackgroundMessagesWithoutResponse['DISABLE_NOTIFICATIONS_BELL_SCRIPT']['message'] =
{
type: 'disable-notifications-bell-script',
};
Expand Down
5 changes: 5 additions & 0 deletions src/background/httpClients/httpClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const httpClient = {
fetch: async (url: string): Promise<Response> => {
return await fetch(url, { credentials: 'omit' });
},
};
68 changes: 68 additions & 0 deletions src/background/httpClients/pontoonGraphqlClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { gql } from 'graphql-tag';
import { GraphQLClient } from 'graphql-request';

import { getOneOption } from '@commons/options';

import type { DeepNonNullable, DeepRequired } from '../typeUtils';
import { pontoonGraphQL } from '../apiEndpoints';
import type {
GetProjectsInfoQuery,
GetTeamsInfoQuery,
} from '../../generated/pontoon.graphql';
import { getSdk } from '../../generated/pontoon.graphql';

const _getTeamsInfoQuery = gql`
query getTeamsInfo {
locales {
code
name
approvedStrings
pretranslatedStrings
stringsWithWarnings
stringsWithErrors
missingStrings
unreviewedStrings
totalStrings
}
}
`;

interface GetTeamsInfoResponse {
locales: DeepRequired<DeepNonNullable<GetTeamsInfoQuery['locales']>>;
}

const _getProjectsInfoQuery = gql`
query getProjectsInfo {
projects {
slug
name
}
}
`;

export interface GetProjectsInfoResponse {
projects: DeepRequired<DeepNonNullable<GetProjectsInfoQuery['projects']>>;
}

function getGraphQLClient(pontoonBaseUrl: string) {
return getSdk(
new GraphQLClient(pontoonGraphQL(pontoonBaseUrl), {
method: 'GET',
}),
);
}

async function getPontoonBaseUrl(): Promise<string> {
return await getOneOption('pontoon_base_url');
}

export const pontoonGraphqlClient = {
getTeamsInfo: async (): Promise<GetTeamsInfoResponse> => {
const client = getGraphQLClient(await getPontoonBaseUrl());
return (await client.getTeamsInfo()) as GetTeamsInfoResponse;
},
getProjectsInfo: async (): Promise<GetProjectsInfoResponse> => {
const client = getGraphQLClient(await getPontoonBaseUrl());
return (await client.getProjectsInfo()) as GetProjectsInfoResponse;
},
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { WebRequest } from 'webextension-polyfill';
import { v4 as uuidv4 } from 'uuid';
import { gql } from 'graphql-tag';
import { GraphQLClient } from 'graphql-request';

import { browser } from '@commons/webExtensionsApi';
import {
Expand All @@ -10,30 +8,34 @@ import {
listenToOptionChange,
} from '@commons/options';

import type {
GetProjectsInfoQuery,
GetTeamsInfoQuery,
} from '../generated/pontoon.graphql';
import { getSdk } from '../generated/pontoon.graphql';

import { pontoonGraphQL } from './apiEndpoints';

const PONTOON_REQUEST_TOKEN_HEADER = 'pontoon-addon-token';
const PONTOON_REQUEST_TOKEN_STORAGE_KEY_PREFIX = 'pontoon_req_token_';
const PONTOON_REQUEST_TOKEN_VALIDITY_SECONDS = 60;

interface TokenInfo {
issued: string; // ISO date
}

async function listenForRequestsToPontoon(pontoonBaseUrl?: string) {
export async function init() {
await listenToOptionChange(
'pontoon_base_url',
async ({ newValue: pontoonBaseUrl }) => {
await listenForRequestsToPontoon(pontoonBaseUrl);
},
);
await listenForRequestsToPontoon(await getPontoonBaseUrl());
}

async function getPontoonBaseUrl(): Promise<string> {
return await getOneOption('pontoon_base_url');
}

async function listenForRequestsToPontoon(pontoonBaseUrl: string) {
if (browser.webRequest) {
if (typeof pontoonBaseUrl === 'undefined') {
pontoonBaseUrl = await getOneOption('pontoon_base_url');
}
browser.webRequest.onBeforeSendHeaders.removeListener(
await browser.webRequest.onBeforeSendHeaders.removeListener(
setSessionCookieForPontoonRequest,
);
browser.webRequest.onBeforeSendHeaders.addListener(
await browser.webRequest.onBeforeSendHeaders.addListener(
setSessionCookieForPontoonRequest,
{ urls: [`${pontoonBaseUrl}/*`] },
['blocking', 'requestHeaders'],
Expand All @@ -42,7 +44,7 @@ async function listenForRequestsToPontoon(pontoonBaseUrl?: string) {
}

async function fetchFromPontoonSession(url: string): Promise<Response> {
const pontoonBaseUrl = await getOneOption('pontoon_base_url');
const pontoonBaseUrl = await getPontoonBaseUrl();
if (!url.startsWith(`${pontoonBaseUrl}/`)) {
throw new Error(
`Attempted to fetch '${url}' with Pontoon session for '${pontoonBaseUrl}'.`,
Expand All @@ -54,11 +56,14 @@ async function fetchFromPontoonSession(url: string): Promise<Response> {
if (browser.webRequest) {
browser.webRequest.onBeforeSendHeaders.hasListener(
setSessionCookieForPontoonRequest,
) || (await listenForRequestsToPontoon());
headers.append('pontoon-addon-token', await issueNewPontoonRequestToken());
return fetch(url, { credentials: 'omit', headers: headers });
) || (await listenForRequestsToPontoon(pontoonBaseUrl));
headers.append(
PONTOON_REQUEST_TOKEN_HEADER,
await issueNewPontoonRequestToken(),
);
return fetch(url, { credentials: 'omit', headers });
} else {
return fetch(url, { credentials: 'include', headers: headers });
return fetch(url, { credentials: 'include', headers });
}
}

Expand Down Expand Up @@ -102,7 +107,7 @@ async function verifyPontoonRequestToken(
async function setSessionCookieForPontoonRequest(
details: WebRequest.OnBeforeSendHeadersDetailsType,
): Promise<WebRequest.BlockingResponse> {
const pontoonBaseUrl = await getOneOption('pontoon_base_url');
const pontoonBaseUrl = await getPontoonBaseUrl();
if (!details.url.startsWith(`${pontoonBaseUrl}/`)) {
console.warn(
`Observed a request to '${details.url}', but Pontoon is at '${pontoonBaseUrl}'. Request passed unchanged.`,
Expand All @@ -111,7 +116,9 @@ async function setSessionCookieForPontoonRequest(
}

const tokens = (details.requestHeaders ?? [])
.filter((header) => header.name.toLowerCase() === 'pontoon-addon-token')
.filter(
(header) => header.name.toLowerCase() === PONTOON_REQUEST_TOKEN_HEADER,
)
.map((header) => header.value);
const isMarked =
tokens.length > 0 &&
Expand All @@ -134,7 +141,9 @@ async function setSessionCookieForPontoonRequest(
storeId: contextualIdentity,
});
const requestHeaders = (details.requestHeaders ?? [])
.filter((header) => header.name.toLowerCase() !== 'pontoon-addon-token')
.filter(
(header) => header.name.toLowerCase() !== PONTOON_REQUEST_TOKEN_HEADER,
)
.filter((header) => header.name.toLowerCase() !== 'cookie')
.concat(
...(cookie
Expand All @@ -153,85 +162,6 @@ async function setSessionCookieForPontoonRequest(
}
}

listenToOptionChange('pontoon_base_url', ({ newValue: pontoonBaseUrl }) => {
listenForRequestsToPontoon(pontoonBaseUrl);
});
listenForRequestsToPontoon();

export const pontoonHttpClient = {
fetchFromPontoonSession,
};

export const httpClient = {
fetch: async (url: string): Promise<Response> => {
return await fetch(url, { credentials: 'omit' });
},
};

type DeepRequired<T> = T extends object
? Required<{
[P in keyof T]: DeepRequired<T[P]>;
}>
: Required<T>;

type DeepNonNullable<T> = T extends object
? NonNullable<{
[P in keyof T]: DeepNonNullable<T[P]>;
}>
: NonNullable<T>;

const _getTeamsInfoQuery = gql`
query getTeamsInfo {
locales {
code
name
approvedStrings
pretranslatedStrings
stringsWithWarnings
stringsWithErrors
missingStrings
unreviewedStrings
totalStrings
}
}
`;

interface GetTeamsInfoResponse {
locales: DeepRequired<DeepNonNullable<GetTeamsInfoQuery['locales']>>;
}

const _getProjectsInfoQuery = gql`
query getProjectsInfo {
projects {
slug
name
}
}
`;

export interface GetProjectsInfoResponse {
projects: DeepRequired<DeepNonNullable<GetProjectsInfoQuery['projects']>>;
}

function getGraphQLClient(pontoonBaseUrl: string) {
return getSdk(
new GraphQLClient(pontoonGraphQL(pontoonBaseUrl), {
method: 'GET',
}),
);
}

export const pontoonGraphqlClient = {
getTeamsInfo: async (): Promise<GetTeamsInfoResponse> => {
const client = getGraphQLClient(await getPontoonBaseUrl());
return (await client.getTeamsInfo()) as GetTeamsInfoResponse;
},
getProjectsInfo: async (): Promise<GetProjectsInfoResponse> => {
const client = getGraphQLClient(await getPontoonBaseUrl());
return (await client.getProjectsInfo()) as GetProjectsInfoResponse;
},
};

async function getPontoonBaseUrl(): Promise<string> {
return await getOneOption('pontoon_base_url');
}
Loading

0 comments on commit adf0cf0

Please sign in to comment.