Skip to content

Commit

Permalink
refactor: extract origin config fetching logic
Browse files Browse the repository at this point in the history
  • Loading branch information
f1ames committed Nov 17, 2023
1 parent e9eb045 commit c559c45
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 95 deletions.
4 changes: 2 additions & 2 deletions packages/synchronizer/src/__tests__/fetcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import sinon from 'sinon';
import {assert} from 'chai';
import express from 'express';
import {createDefaultMonokleFetcher} from '../createDefaultMonokleFetcher.js';
import {Fetcher} from '../utils/fetcher.js';
import {fetchOriginConfig} from '../handlers/configHandler.js';

const TEST_QUERY = `
query getCluster($id: ID) {
Expand Down Expand Up @@ -141,7 +141,7 @@ describe('Fetcher Tests', () => {

const server = app.listen(13000, async () => {
try {
const originData = await Fetcher.getOriginConfig('localhost:13000');
const originData = await fetchOriginConfig('localhost:13000');

assert.equal(originData?.origin, 'http://localhost:13000');
assert.equal(originData?.apiOrigin, 'https://api.monokle.local');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Authenticator} from './utils/authenticator.js';
/**
* Creates default Monokle Authenticator instance.
*
* @deprecated Use createMonokleAuthenticatorFromOrigin instead which does not rely on hardcoded config.
* @deprecated Use createMonokleAuthenticatorFromOrigin or createMonokleAuthenticatorFromConfig instead which does not rely on hardcoded config.
*
* @param storageHandler
* @param apiHandler
Expand Down
8 changes: 8 additions & 0 deletions packages/synchronizer/src/createDefaultMonokleFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import {ApiHandler} from './handlers/apiHandler.js';
import {Fetcher} from './utils/fetcher.js';

/**
* Creates default Monokle Fetcher instance.
*
* @deprecated Use createMonokleFetcherFromOrigin or createMonokleFetcherFromConfig instead which does not rely on hardcoded config.
*
* @param apiHandler
* @returns Fetcher instance
*/
export function createDefaultMonokleFetcher(apiHandler: ApiHandler = new ApiHandler()) {
return new Fetcher(apiHandler);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Synchronizer} from './utils/synchronizer.js';
/**
* Creates default Monokle Synchronizer instance.
*
* @deprecated Use createMonokleSynchronizerFromOrigin instead which does not rely on hardcoded config.
* @deprecated Use createMonokleSynchronizerFromOrigin or createMonokleSynchronizerFromConfig instead which does not rely on hardcoded config.
*
* @param storageHandler
* @param apiHandler
Expand Down
58 changes: 35 additions & 23 deletions packages/synchronizer/src/createMonokleAuthenticatorFromOrigin.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
import {ApiHandler} from './handlers/apiHandler.js';
import {DeviceFlowHandler} from './handlers/deviceFlowHandler.js';
import {StorageHandlerAuth} from './handlers/storageHandlerAuth.js';
import {Fetcher} from './utils/fetcher.js';
import {Authenticator} from './utils/authenticator.js';
import {DEFAULT_DEVICE_FLOW_ALG, DEFAULT_DEVICE_FLOW_CLIENT_SECRET, DEFAULT_ORIGIN} from './constants.js';
import {OriginConfig, fetchOriginConfig} from './handlers/configHandler.js';

export async function createMonokleAuthenticatorFromOrigin(authClientId: string, origin: string = DEFAULT_ORIGIN) {
export async function createMonokleAuthenticatorFromOrigin(
authClientId: string,
origin: string = DEFAULT_ORIGIN,
storageHandler: StorageHandlerAuth = new StorageHandlerAuth()
) {
try {
const originConfig = await Fetcher.getOriginConfig(origin);
const originConfig = await fetchOriginConfig(origin);

if (!authClientId) {
throw new Error(`No auth clientId provided.`);
}
return createMonokleAuthenticatorFromConfig(authClientId, originConfig, storageHandler);
} catch (err: any) {
throw err;
}
}

if (!originConfig?.apiOrigin) {
throw new Error(`No api origin found in origin config from ${origin}.`);
}
export function createMonokleAuthenticatorFromConfig(
authClientId: string,
config: OriginConfig,
storageHandler: StorageHandlerAuth = new StorageHandlerAuth()
) {
if (!authClientId) {
throw new Error(`No auth clientId provided.`);
}

if (!originConfig?.authOrigin) {
throw new Error(`No auth origin found in origin config from ${origin}.`);
}
if (!config?.apiOrigin) {
throw new Error(`No api origin found in origin config from ${origin}.`);
}

return new Authenticator(
new StorageHandlerAuth(),
new ApiHandler(originConfig.apiOrigin),
new DeviceFlowHandler(originConfig.authOrigin, {
client_id: authClientId,
client_secret: DEFAULT_DEVICE_FLOW_CLIENT_SECRET,
id_token_signed_response_alg: DEFAULT_DEVICE_FLOW_ALG,
})
);
} catch (err: any) {
throw err;
if (!config?.authOrigin) {
throw new Error(`No auth origin found in origin config from ${origin}.`);
}

return new Authenticator(
storageHandler,
new ApiHandler(config),
new DeviceFlowHandler(config.authOrigin, {
client_id: authClientId,
client_secret: DEFAULT_DEVICE_FLOW_CLIENT_SECRET,
id_token_signed_response_alg: DEFAULT_DEVICE_FLOW_ALG,
})
);
}
22 changes: 22 additions & 0 deletions packages/synchronizer/src/createMonokleFetcherFromOrigin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {DEFAULT_ORIGIN} from './constants.js';
import {ApiHandler} from './handlers/apiHandler.js';
import {OriginConfig, fetchOriginConfig} from './handlers/configHandler.js';
import {Fetcher} from './utils/fetcher.js';

export async function createMonokleFetcherFromOrigin(origin: string = DEFAULT_ORIGIN) {
try {
const originConfig = await fetchOriginConfig(origin);

return createMonokleFetcherFromConfig(originConfig);
} catch (err: any) {
throw err;
}
}

export function createMonokleFetcherFromConfig(config: OriginConfig) {
if (!config?.apiOrigin) {
throw new Error(`No api origin found in origin config from ${origin}.`);
}

return new Fetcher(new ApiHandler(config));
}
28 changes: 20 additions & 8 deletions packages/synchronizer/src/createMonokleSynchronizerFromOrigin.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import {DEFAULT_ORIGIN} from './constants.js';
import {ApiHandler} from './handlers/apiHandler.js';
import {OriginConfig, fetchOriginConfig} from './handlers/configHandler.js';
import {GitHandler} from './handlers/gitHandler.js';
import {StorageHandlerPolicy} from './handlers/storageHandlerPolicy.js';
import {Fetcher} from './utils/fetcher.js';
import {Synchronizer} from './utils/synchronizer.js';

export async function createMonokleSynchronizerFromOrigin(origin: string = DEFAULT_ORIGIN) {
export async function createMonokleSynchronizerFromOrigin(
origin: string = DEFAULT_ORIGIN,
storageHandler: StorageHandlerPolicy = new StorageHandlerPolicy(),
gitHandler: GitHandler = new GitHandler()
) {
try {
const originConfig = await Fetcher.getOriginConfig(origin);
const originConfig = await fetchOriginConfig(origin);

if (!originConfig?.apiOrigin) {
throw new Error(`No api origin found in origin config from ${origin}.`);
}

return new Synchronizer(new StorageHandlerPolicy(), new ApiHandler(originConfig.apiOrigin), new GitHandler());
return createMonokleSynchronizerFromConfig(originConfig, storageHandler, gitHandler);
} catch (err: any) {
throw err;
}
}

export function createMonokleSynchronizerFromConfig(
config: OriginConfig,
storageHandler: StorageHandlerPolicy = new StorageHandlerPolicy(),
gitHandler: GitHandler = new GitHandler()
) {
if (!config?.apiOrigin) {
throw new Error(`No api origin found in origin config from ${origin}.`);
}

return new Synchronizer(storageHandler, new ApiHandler(config), gitHandler);
}
2 changes: 1 addition & 1 deletion packages/synchronizer/src/handlers/apiHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import normalizeUrl from 'normalize-url';
import fetch from 'node-fetch';
import {SuppressionStatus} from '@monokle/types';
import {DEFAULT_API_URL} from '../constants.js';
import {OriginConfig} from '../handlers/configHandler.js';
import type {TokenInfo} from './storageHandlerAuth.js';
import {OriginConfig} from '../utils/fetcher.js';

const getUserQuery = `
query getUser {
Expand Down
59 changes: 59 additions & 0 deletions packages/synchronizer/src/handlers/configHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import normalizeUrl from 'normalize-url';
import fetch from 'node-fetch';

export type OriginConfig = {
origin: string;
apiOrigin: string;
authOrigin: string;
[key: string]: string;
};

export type CachedOriginConfig = {
config: OriginConfig;
downloadedAt: number;
origin: string;
};

let originConfigCache: CachedOriginConfig | undefined = undefined;

export async function fetchOriginConfig(origin: string) {
if (originConfigCache) {
// Use recently fetched config if from same origin and it's less than 5 minutes old.
if (origin === originConfigCache.origin && Date.now() - originConfigCache.downloadedAt < 1000 * 60 * 5) {
return originConfigCache.config;
}
}

try {
const configUrl = normalizeUrl(`${origin}/config.js`);
const response = await fetch(configUrl);
const responseText = await response.text();

const values = Array.from(responseText.matchAll(/([A-Z_]+)\s*:\s*"(.*?)"/gm)).reduce(
(acc: Record<string, string>, match) => {
if (match[1] && match[2]) {
acc[match[1]] = match[2];
}
return acc;
},
{}
);

if (values) {
values.origin = normalizeUrl(origin);
values.apiOrigin = values.API_ORIGIN;
values.authOrigin = values.OIDC_DISCOVERY_URL;
}

originConfigCache = {
config: values as OriginConfig,
downloadedAt: Date.now(),
origin,
};

return values as OriginConfig;
} catch (error: any) {
// Rethrow error so integrations can catch it and propagate/react.
throw error;
}
}
2 changes: 2 additions & 0 deletions packages/synchronizer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './handlers/apiHandler.js';
export * from './handlers/configHandler.js';
export * from './handlers/deviceFlowHandler.js';
export * from './handlers/gitHandler.js';
export * from './handlers/storageHandler.js';
Expand All @@ -18,4 +19,5 @@ export * from './createDefaultMonokleFetcher.js';
export * from './createDefaultMonokleSynchronizer.js';

export * from './createMonokleAuthenticatorFromOrigin.js';
export * from './createMonokleFetcherFromOrigin.js';
export * from './createMonokleSynchronizerFromOrigin.js';
59 changes: 0 additions & 59 deletions packages/synchronizer/src/utils/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import normalizeUrl from 'normalize-url';
import fetch from 'node-fetch';
import {EventEmitter} from 'events';
import {ApiHandler} from '../handlers/apiHandler.js';
import {TokenInfo} from '../handlers/storageHandlerAuth.js';
Expand All @@ -15,70 +13,13 @@ export type QueryResult<T_DATA> = {
error?: string;
};

export type OriginConfig = {
origin: string;
apiOrigin: string;
authOrigin: string;
[key: string]: string;
};

export type CachedOriginConfig = {
config: OriginConfig;
downloadedAt: number;
origin: string;
};

export class Fetcher extends EventEmitter {
static _originConfig: CachedOriginConfig | undefined;

private _token: TokenInfo | undefined;

constructor(private _apiHandler: ApiHandler) {
super();
}

static async getOriginConfig(origin: string): Promise<OriginConfig | undefined> {
if (Fetcher._originConfig) {
// Use recently fetched config if from same origin and it's less than 5 minutes old.
if (origin === Fetcher._originConfig.origin && (Date.now()) - Fetcher._originConfig.downloadedAt < 1000 * 60 * 5) {
return Fetcher._originConfig.config;
}
}

try {
const configUrl = normalizeUrl(`${origin}/config.js`);
const response = await fetch(configUrl);
const responseText = await response.text();

const values = Array.from(responseText.matchAll(/([A-Z_]+)\s*:\s*"(.*?)"/gm)).reduce(
(acc: Record<string, string>, match) => {
if (match[1] && match[2]) {
acc[match[1]] = match[2];
}
return acc;
},
{}
);

if (values) {
values.origin = normalizeUrl(origin);
values.apiOrigin = values.API_ORIGIN;
values.authOrigin = values.OIDC_DISCOVERY_URL;
}

Fetcher._originConfig = {
config: values as OriginConfig,
downloadedAt: Date.now(),
origin,
};

return values as OriginConfig;
} catch (error: any) {
// Rethrow error so integrations can catch it and propagate/react.
throw error;
}
}

useBearerToken(token: string) {
this._token = {
accessToken: token,
Expand Down

0 comments on commit c559c45

Please sign in to comment.