import {throwFailedRequest} from '../../clients/common'; import {decodeSessionToken} from '../../session/decode-session-token'; import {sanitizeShop} from '../../utils/shop-validator'; import {ConfigInterface} from '../../base-types'; import {Session} from '../../session/session'; import {DataType} from '../../clients/types'; import {fetchRequestFactory} from '../../utils/fetch-request'; import {createSession} from './create-session'; import {AccessTokenResponse} from './types'; export enum RequestedTokenType { OnlineAccessToken = 'urn:shopify:params:oauth:token-type:online-access-token', OfflineAccessToken = 'urn:shopify:params:oauth:token-type:offline-access-token', } const TokenExchangeGrantType = 'urn:ietf:params:oauth:grant-type:token-exchange'; const IdTokenType = 'urn:ietf:params:oauth:token-type:id_token'; export interface TokenExchangeParams { shop: string; sessionToken: string; requestedTokenType: RequestedTokenType; expiring?: boolean; } export type TokenExchange = ( params: TokenExchangeParams, ) => Promise<{session: Session}>; export function tokenExchange(config: ConfigInterface): TokenExchange { return async ({ shop, sessionToken, requestedTokenType, expiring, }: TokenExchangeParams) => { await decodeSessionToken(config)(sessionToken); const body = { client_id: config.apiKey, client_secret: config.apiSecretKey, grant_type: TokenExchangeGrantType, subject_token: sessionToken, subject_token_type: IdTokenType, requested_token_type: requestedTokenType, expiring: expiring ? '1' : '0', }; const cleanShop = sanitizeShop(config)(shop, true)!; const postResponse = await fetchRequestFactory(config)( `https://${cleanShop}/admin/oauth/access_token`, { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': DataType.JSON, Accept: DataType.JSON, }, }, ); if (!postResponse.ok) { throwFailedRequest(await postResponse.json(), false, postResponse); } return { session: createSession({ accessTokenResponse: await postResponse.json(), shop: cleanShop, // We need to keep this as an empty string as our template DB schemas have this required state: '', config, }), }; }; }