Skip to content

Commit

Permalink
feat(web-component): prefer static from api base url
Browse files Browse the repository at this point in the history
  • Loading branch information
omercnet committed Dec 22, 2024
1 parent 334053c commit 1b155d4
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 193 deletions.
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
import { pathJoin, compose, createSingletonMixin } from '@descope/sdk-helpers';
import { compose, createSingletonMixin, pathJoin } from '@descope/sdk-helpers';
import { baseUrlMixin } from '../baseUrlMixin';
import { loggerMixin } from '../loggerMixin';
import { projectIdMixin } from '../projectIdMixin';
import {
ASSETS_FOLDER,
BASE_CONTENT_URL,
OVERRIDE_CONTENT_URL,
} from './constants';
import { projectIdMixin } from '../projectIdMixin';
import { baseUrlMixin } from '../baseUrlMixin';

type Format = 'text' | 'json';

export function getResourceUrl({
projectId,
filename,
assetsFolder = ASSETS_FOLDER,
baseUrl,
}: {
projectId: string;
filename: string;
assetsFolder?: string;
baseUrl?: string;
}) {
const url = new URL(OVERRIDE_CONTENT_URL || baseUrl || BASE_CONTENT_URL);
url.pathname = pathJoin(url.pathname, projectId, assetsFolder, filename);

return url.toString();
}

export const staticResourcesMixin = createSingletonMixin(
<T extends CustomElementConstructor>(superclass: T) => {
const BaseClass = compose(
Expand All @@ -36,29 +19,76 @@ export const staticResourcesMixin = createSingletonMixin(
)(superclass);

return class StaticResourcesMixinClass extends BaseClass {
preferredBaseURL?: string;

async fetchStaticResource<F extends Format>(
filename: string,
format: F,
assetsFolder = ASSETS_FOLDER,
): Promise<{
body: F extends 'json' ? Record<string, any> : string;
headers: Record<string, string>;
}> {
const resourceUrl = getResourceUrl({
projectId: this.projectId,
filename,
baseUrl: this.baseStaticUrl,
});
const res = await fetch(resourceUrl, { cache: 'default' });
if (!res.ok) {
this.logger.error(
`Error fetching URL ${resourceUrl} [${res.status}]`,
const fetchResourceFromBaseURL = async (baseUrl: string) => {
// Compute the base URL to fetch the resource from
// This allows overriding the base URL for static resources
const computedBaseUrl = new URL(
OVERRIDE_CONTENT_URL ||
this.preferredBaseURL ||
baseUrl ||
BASE_CONTENT_URL,
);

const resourceUrl = new URL(
pathJoin(
computedBaseUrl.pathname,
this.projectId,
assetsFolder,
filename,
),
computedBaseUrl,
);
}

return {
body: await res[format](),
headers: Object.fromEntries(res.headers.entries()),
const res = await fetch(resourceUrl, { cache: 'default' });
if (!res.ok) {
this.logger.error(
`Error fetching URL ${resourceUrl} [${res.status}]`,
);
} else {
if (!this.preferredBaseURL) {
this.logger.debug(`Fetched URL ${resourceUrl} [${res.status}]`);
this.logger.debug(
`Updating preferred base URL to ${computedBaseUrl.toString()}`,
);
this.preferredBaseURL = computedBaseUrl.toString();
}
}
return res;
};

try {
// We prefer to fetch the resource from the base API URL
let res = await fetchResourceFromBaseURL(
new URL('/pages', this.baseUrl).toString(),
);
if (!res.ok) {
// If the resource is not found in the base API URL, we try to fetch it from the static URL
res = await fetchResourceFromBaseURL(this.baseStaticUrl);
}
return {
body: await res[format](),
headers: Object.fromEntries(res.headers.entries()),
};
} catch (e) {
this.logger.error(
`Error fetching static resource ${filename} from ${this.baseStaticUrl}`,
);
const res = await fetchResourceFromBaseURL(this.baseStaticUrl);
return {
body: await res[format](),
headers: Object.fromEntries(res.headers.entries()),
};
}
}

get baseStaticUrl() {
Expand Down
51 changes: 25 additions & 26 deletions packages/sdks/web-component/src/lib/descope-wc/BaseDescopeWc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import createSdk from '@descope/web-js-sdk';
import { themeMixin } from '@descope/sdk-mixins/themeMixin';
import { compose } from '@descope/sdk-helpers';
import { staticResourcesMixin } from '@descope/sdk-mixins';
import { themeMixin } from '@descope/sdk-mixins/themeMixin';
import createSdk from '@descope/web-js-sdk';
import {
CONFIG_FILENAME,
ELEMENTS_TO_IGNORE_ENTER_KEY_ON,
Expand All @@ -10,37 +11,39 @@ import {
import {
camelCase,
clearRunIdsFromUrl,
fetchContent,
getContentUrl,
getRunIdsFromUrl,
handleUrlParams,
State,
withMemCache,
} from '../helpers';
import {
extractNestedAttribute,
transformFlowInputFormData,
} from '../helpers/flowInputs';
import { IsChanged } from '../helpers/state';
import { formMountMixin } from '../mixins';
import {
AutoFocusOptions,
DebuggerMessage,
DebugState,
FlowState,
FlowStateUpdateFn,
SdkConfig,
DescopeUI,
ProjectConfiguration,
FlowConfig,
FlowState,
FlowStateUpdateFn,
FlowStatus,
ProjectConfiguration,
SdkConfig,
} from '../types';
import initTemplate from './initTemplate';
import {
extractNestedAttribute,
transformFlowInputFormData,
} from '../helpers/flowInputs';

// this is replaced in build time
declare const BUILD_VERSION: string;

const BaseClass = compose(themeMixin, formMountMixin)(HTMLElement);
const BaseClass = compose(
themeMixin,
formMountMixin,
staticResourcesMixin,
)(HTMLElement);

// this base class is responsible for WC initialization
class BaseDescopeWc extends BaseClass {
Expand Down Expand Up @@ -298,14 +301,12 @@ class BaseDescopeWc extends BaseClass {
}

async #isPrevVerConfig() {
const prevVerConfigUrl = getContentUrl({
projectId: this.projectId,
filename: CONFIG_FILENAME,
assetsFolder: PREV_VER_ASSETS_FOLDER,
baseUrl: this.baseStaticUrl,
});
try {
await fetchContent(prevVerConfigUrl, 'json');
await this.fetchStaticResource(
CONFIG_FILENAME,
'json',
PREV_VER_ASSETS_FOLDER,
);
return true;
} catch (e) {
return false;
Expand All @@ -314,13 +315,11 @@ class BaseDescopeWc extends BaseClass {

// we want to get the config only if we don't have it already
getConfig = withMemCache(async () => {
const configUrl = getContentUrl({
projectId: this.projectId,
filename: CONFIG_FILENAME,
baseUrl: this.baseStaticUrl,
});
try {
const { body, headers } = await fetchContent(configUrl, 'json');
const { body, headers } = await this.fetchStaticResource(
CONFIG_FILENAME,
'json',
);
return {
projectConfig: body as ProjectConfiguration,
executionContext: { geo: headers['x-geo'] },
Expand Down
62 changes: 22 additions & 40 deletions packages/sdks/web-component/src/lib/descope-wc/DescopeWc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
ensureFingerprintIds,
clearFingerprintData,
ensureFingerprintIds,
} from '@descope/web-js-sdk';
import {
CUSTOM_INTERACTIONS,
Expand All @@ -15,30 +15,28 @@ import {
URL_TOKEN_PARAM_NAME,
} from '../constants';
import {
fetchContent,
clearPreviousExternalInputs,
getAnimationDirection,
getContentUrl,
getElementDescopeAttributes,
getFirstNonEmptyValue,
getUserLocale,
handleAutoFocus,
handleReportValidityOnBlur,
injectSamlIdpForm,
isConditionalLoginSupported,
updateScreenFromScreenState,
updateTemplateFromScreenState,
leadingDebounce,
setTOTPVariable,
showFirstScreenOnExecutionInit,
State,
submitForm,
withMemCache,
getFirstNonEmptyValue,
leadingDebounce,
handleReportValidityOnBlur,
getUserLocale,
clearPreviousExternalInputs,
timeoutPromise,
updateScreenFromScreenState,
updateTemplateFromScreenState,
withMemCache,
} from '../helpers';
import { calculateConditions, calculateCondition } from '../helpers/conditions';
import { getLastAuth, setLastAuth } from '../helpers/lastAuth';
import { getABTestingKey } from '../helpers/abTestingKey';
import { calculateCondition, calculateConditions } from '../helpers/conditions';
import { getLastAuth, setLastAuth } from '../helpers/lastAuth';
import { IsChanged } from '../helpers/state';
import {
disableWebauthnButtons,
Expand Down Expand Up @@ -227,22 +225,22 @@ class DescopeWc extends BaseDescopeWc {
return filenameWithLocale;
}

async getPageContent(htmlUrl: string, htmlLocaleUrl: string) {
if (htmlLocaleUrl) {
async getPageContent(html: string, htmlLocale: string) {
if (htmlLocale) {
// try first locale url, if can't get for some reason, fallback to the original html url (the one without locale)
try {
const { body } = await fetchContent(htmlLocaleUrl, 'text');
const { body } = await this.fetchStaticResource(htmlLocale, 'text');
return body;
} catch (ex) {
this.loggerWrapper.error(
`Failed to fetch flow page from ${htmlLocaleUrl}. Fallback to url ${htmlUrl}`,
`Failed to fetch flow page from ${htmlLocale}. Fallback to url ${html}`,
ex,
);
}
}

try {
const { body } = await fetchContent(htmlUrl, 'text');
const { body } = await this.fetchStaticResource(html, 'text');
return body;
} catch (ex) {
this.loggerWrapper.error(`Failed to fetch flow page`, ex.message);
Expand Down Expand Up @@ -586,18 +584,8 @@ class DescopeWc extends BaseDescopeWc {
name: this.sdk.getLastUserDisplayName() || loginId,
},
},
htmlUrl: getContentUrl({
projectId,
filename: `${readyScreenId}.html`,
baseUrl: this.baseStaticUrl,
}),
htmlLocaleUrl:
filenameWithLocale &&
getContentUrl({
projectId,
filename: filenameWithLocale,
baseUrl: this.baseStaticUrl,
}),
html: `${readyScreenId}.html`,
htmlLocale: filenameWithLocale,
samlIdpUsername,
oidcLoginHint,
oidcPrompt,
Expand Down Expand Up @@ -962,17 +950,11 @@ class DescopeWc extends BaseDescopeWc {
}

async onStepChange(currentState: StepState, prevState: StepState) {
const {
htmlUrl,
htmlLocaleUrl,
direction,
next,
screenState,
openInNewTabUrl,
} = currentState;
const { html, htmlLocale, direction, next, screenState, openInNewTabUrl } =
currentState;

const stepTemplate = document.createElement('template');
stepTemplate.innerHTML = await this.getPageContent(htmlUrl, htmlLocaleUrl);
stepTemplate.innerHTML = await this.getPageContent(html, htmlLocale);

const clone = stepTemplate.content.cloneNode(true) as DocumentFragment;

Expand Down Expand Up @@ -1025,7 +1007,7 @@ class DescopeWc extends BaseDescopeWc {
this.rootElement.replaceChildren(clone);

// If before html url was empty, we deduce its the first time a screen is shown
const isFirstScreen = !prevState.htmlUrl;
const isFirstScreen = !prevState.html;

// we need to wait for all components to render before we can set its value
setTimeout(() => {
Expand Down
Loading

0 comments on commit 1b155d4

Please sign in to comment.