scrivito
Version:
Scrivito is a professional, yet easy to use SaaS Enterprise Content Management Service, built for digital agencies and medium to large businesses. It is completely maintenance-free, cost-effective, and has unprecedented performance and security.
87 lines (74 loc) • 2.46 kB
text/typescript
import { getEditorAuthToken } from 'scrivito_sdk/app_support/get_editor_auth_token';
import { uiAdapter } from 'scrivito_sdk/app_support/ui_adapter';
import {
ExponentialBackoff,
fetchBrowserToken,
loginRedirectHandler,
withLoginHandler,
} from 'scrivito_sdk/client';
import { ArgumentError, assumePresence } from 'scrivito_sdk/common';
interface Result<T> {
result: T;
}
interface AuthenticationFailed {
authenticationFailed: {
error: string;
code: string;
details?: object;
};
}
type ResultOrAuthenticationFailed<T> = Result<T> | AuthenticationFailed;
/** @public */
export async function performWithIamToken<T>(
audience: string,
callback: (token: string) => Promise<ResultOrAuthenticationFailed<T>>
): Promise<T> {
let fetchTokenPromise: Promise<unknown> | undefined;
const backoff = new ExponentialBackoff();
let fetchedTokenBefore = false;
// note: using a loop instead of recursion avoids stack overflow
// eslint-disable-next-line no-constant-condition
while (true) {
if (!fetchTokenPromise) {
fetchTokenPromise = (async () => {
if (fetchedTokenBefore) await backoff.nextDelay();
fetchedTokenBefore = true;
return uiAdapter
? assumePresence(getEditorAuthToken({ audience }))
: withLoginHandler(loginRedirectHandler, async () =>
fetchBrowserToken({ audience })
);
})();
}
const tokenResult = await fetchTokenPromise;
if (typeof tokenResult === 'string') {
const outcome = await callback(tokenResult);
assertIsResultOrAuthenticationFailed(outcome);
if ('result' in outcome) return outcome.result;
}
fetchTokenPromise = undefined;
}
}
function assertIsResultOrAuthenticationFailed<T>(
outcome: unknown
): asserts outcome is ResultOrAuthenticationFailed<T> {
if (
!(
outcome &&
typeof outcome === 'object' &&
('result' in outcome ||
('authenticationFailed' in outcome &&
outcome.authenticationFailed &&
typeof outcome.authenticationFailed === 'object' &&
'error' in outcome.authenticationFailed &&
'code' in outcome.authenticationFailed))
)
) {
throw new ArgumentError(
`performWithIamToken callback returned an invalid response: ${JSON.stringify(
outcome
)} ` +
'Expected an object with either { result } or { authenticationFailed: { error, code } }.'
);
}
}