diff --git a/dist/api/exchange-token.d.ts b/dist/api/exchange-token.d.ts index cc0d117..ac77606 100644 --- a/dist/api/exchange-token.d.ts +++ b/dist/api/exchange-token.d.ts @@ -1,2 +1,2 @@ -import { StoredOptions, ExchangeTokenOptions } from '../typings'; -export default function exchangeToken(storedOptions: StoredOptions, options?: ExchangeTokenOptions): Promise; +import { StoredOptions, ExchangeTokenOptions, Token } from '../typings'; +export default function exchangeToken(storedOptions: StoredOptions, options?: ExchangeTokenOptions): Promise; diff --git a/dist/index.d.ts b/dist/index.d.ts index 0f38d01..9062318 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,4 +1,4 @@ -import { StoredOptions, LogoutOptions, InitOptions, AuthorizeOptions, OnboardOptions, ExchangeTokenOptions, RequestLoginLinkOptions, TokenInfo, AuthorizeUrlOptions, LogoutUrlOptions, OnboardUrlOptions, LinkKitOpenServiceUrlOptions, MyAccountOpenServiceUrlOptions, LinkKitOpenServiceOptions, MyAccountOpenServiceOptions, ConfigsOptions, ConfigsOptionsWithoutIsNewTab, VaultOpenServiceUrlViewServiceList, VaultOpenServiceUrlViewServiceConnection, VaultOpenServiceUrlViewConnectionSetting, VaultOpenServiceUrlViewCustomerSupport, VaultOpenServiceViewServiceList, VaultOpenServiceViewServiceConnection, VaultOpenServiceViewConnectionSetting, VaultOpenServiceViewCustomerSupport } from './typings'; +import { Token, StoredOptions, LogoutOptions, InitOptions, AuthorizeOptions, OnboardOptions, ExchangeTokenOptions, RequestLoginLinkOptions, TokenInfo, AuthorizeUrlOptions, LogoutUrlOptions, OnboardUrlOptions, LinkKitOpenServiceUrlOptions, MyAccountOpenServiceUrlOptions, LinkKitOpenServiceOptions, MyAccountOpenServiceOptions, ConfigsOptions, ConfigsOptionsWithoutIsNewTab, VaultOpenServiceUrlViewServiceList, VaultOpenServiceUrlViewServiceConnection, VaultOpenServiceUrlViewConnectionSetting, VaultOpenServiceUrlViewCustomerSupport, VaultOpenServiceViewServiceList, VaultOpenServiceViewServiceConnection, VaultOpenServiceViewConnectionSetting, VaultOpenServiceViewCustomerSupport } from './typings'; export * from './typings'; export declare class MtLinkSdk { storedOptions: StoredOptions; @@ -25,7 +25,7 @@ export declare class MtLinkSdk { openServiceUrl(serviceId: 'vault', options?: VaultOpenServiceUrlViewConnectionSetting): string; openServiceUrl(serviceId: 'vault', options?: VaultOpenServiceUrlViewCustomerSupport): string; requestLoginLink(options?: RequestLoginLinkOptions): Promise; - exchangeToken(options?: ExchangeTokenOptions): Promise; + exchangeToken(options?: ExchangeTokenOptions): Promise; tokenInfo(token: string): Promise; } declare const mtLinkSdk: MtLinkSdk; diff --git a/dist/index.js b/dist/index.js index 7fa9e69..d0a90c6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -33,4 +33,4 @@ e.read=function(t,e,r,i,n){var o,a,s=8*n-i-1,f=(1<>1,h=-7,c=r?n-1:0,d= * isUrlSafeBase64(safe) * // > true */ -var i={"+":"-","/":"_","=":"."},n={"-":"+",_:"/",".":"="};e.encode=function(t){return t.replace(/[+/=]/g,(function(t){return i[t]}))};e.decode=function(t){return t.replace(/[-_.]/g,(function(t){return n[t]}))};e.trim=function(t){return t.replace(/[.=]{1,2}$/,"")};e.isBase64=function(t){return/^[A-Za-z0-9+/]*[=]{0,2}$/.test(t)};e.isUrlSafeBase64=function(t){return/^[A-Za-z0-9_-]*[.]{0,2}$/.test(t)}},function(t,e,r){var i=r(228),n=r(229),o=n;o.v1=i,o.v4=n,t.exports=o},function(t,e,r){var i,n,o=r(111),a=r(112),s=0,f=0;t.exports=function(t,e,r){var u=e&&r||0,h=e||[],c=(t=t||{}).node||i,d=void 0!==t.clockseq?t.clockseq:n;if(null==c||null==d){var l=o();null==c&&(c=i=[1|l[0],l[1],l[2],l[3],l[4],l[5]]),null==d&&(d=n=16383&(l[6]<<8|l[7]))}var p=void 0!==t.msecs?t.msecs:(new Date).getTime(),b=void 0!==t.nsecs?t.nsecs:f+1,m=p-s+(b-f)/1e4;if(m<0&&void 0===t.clockseq&&(d=d+1&16383),(m<0||p>s)&&void 0===t.nsecs&&(b=0),b>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");s=p,f=b,n=d;var y=(1e4*(268435455&(p+=122192928e5))+b)%4294967296;h[u++]=y>>>24&255,h[u++]=y>>>16&255,h[u++]=y>>>8&255,h[u++]=255&y;var g=p/4294967296*1e4&268435455;h[u++]=g>>>8&255,h[u++]=255&g,h[u++]=g>>>24&15|16,h[u++]=g>>>16&255,h[u++]=d>>>8|128,h[u++]=255&d;for(var v=0;v<6;++v)h[u+v]=c[v];return e||a(h)}},function(t,e,r){var i=r(111),n=r(112);t.exports=function(t,e,r){var o=e&&r||0;"string"==typeof t&&(e="binary"===t?new Array(16):null,t=null);var a=(t=t||{}).random||(t.rng||i)();if(a[6]=15&a[6]|64,a[8]=63&a[8]|128,e)for(var s=0;s<16;++s)e[o+s]=a[s];return e||n(a)}},function(t,e,r){"use strict";var i=this&&this.__rest||function(t,e){var r={};for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&e.indexOf(i)<0&&(r[i]=t[i]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var n=0;for(i=Object.getOwnPropertySymbols(t);n0&&n[n.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!n||s[1]>n[0]&&s[1]=300)throw new Error(_.statusText);return[3,4];case 3:throw M=o.sent(),new Error("[mt-link-sdk] `requestLoginLink` execution failed. ".concat(M));case 4:return[2]}}))}))}},function(t,e,r){"use strict";var i=this&&this.__assign||function(){return(i=Object.assign||function(t){for(var e,r=1,i=arguments.length;r0&&n[n.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!n||s[1]>n[0]&&s[1]0&&n[n.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!n||s[1]>n[0]&&s[1]s)&&void 0===t.nsecs&&(b=0),b>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");s=p,f=b,n=d;var y=(1e4*(268435455&(p+=122192928e5))+b)%4294967296;h[u++]=y>>>24&255,h[u++]=y>>>16&255,h[u++]=y>>>8&255,h[u++]=255&y;var g=p/4294967296*1e4&268435455;h[u++]=g>>>8&255,h[u++]=255&g,h[u++]=g>>>24&15|16,h[u++]=g>>>16&255,h[u++]=d>>>8|128,h[u++]=255&d;for(var v=0;v<6;++v)h[u+v]=c[v];return e||a(h)}},function(t,e,r){var i=r(111),n=r(112);t.exports=function(t,e,r){var o=e&&r||0;"string"==typeof t&&(e="binary"===t?new Array(16):null,t=null);var a=(t=t||{}).random||(t.rng||i)();if(a[6]=15&a[6]|64,a[8]=63&a[8]|128,e)for(var s=0;s<16;++s)e[o+s]=a[s];return e||n(a)}},function(t,e,r){"use strict";var i=this&&this.__rest||function(t,e){var r={};for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&e.indexOf(i)<0&&(r[i]=t[i]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var n=0;for(i=Object.getOwnPropertySymbols(t);n0&&n[n.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!n||s[1]>n[0]&&s[1]=300)throw new Error(_.statusText);return[3,4];case 3:throw M=o.sent(),new Error("[mt-link-sdk] `requestLoginLink` execution failed. ".concat(M));case 4:return[2]}}))}))}},function(t,e,r){"use strict";var i=this&&this.__assign||function(){return(i=Object.assign||function(t){for(var e,r=1,i=arguments.length;r0&&n[n.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!n||s[1]>n[0]&&s[1]0&&n[n.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!n||s[1]>n[0]&&s[1]Usage: -One way to use this API is by calling it in the script on your redirection page. For example, if `authorize` redirects to `https://yourapp.com/callback?code=somecode`, you can call this function in the script loaded on that redirection page and the client library will automatically extract the code to exchange for a token. +One way to use this API is by calling it on your redirection page. For example, if `authorize` redirects to `https://yourapp.com/callback?code=somecode`, you can call this function in the script loaded on that redirection page and the client library will automatically extract the code to exchange for a token. Alternatively, you can extract the `code` manually from the redirect URL and pass it to this function via the options object yourself. ```javascript const token = await mtLinkSdk.exchangeToken(options); +token.access_token; // access token +token.refresh_token; // refresh token +token.token_type; // token type +token.created_at: // created at in seconds +token.expires_in; // expiry in seconds +token.scope; // scope of the token ``` | Parameter | Type | Required | Default Value | Description | @@ -253,7 +259,7 @@ This method generates a URL to log out the guest. See the `logout` API for detai mtLinkSdk.logoutUrl(options); ``` -This API has exactly the same parameters as `logout`, the only difference being that it returns an URL instead of opening immediately with `window.open`. +This API has exactly the same parameters as `logout`, the only difference being that it returns an URL instead of opening immediately with `window.open`. #### Open Vault Services Page @@ -355,7 +361,7 @@ This method can generate URLs for various services provided by Moneytree, such a mtLinkSdk.openServiceUrl(serviceId, options); ``` -This API has exactly the same parameters as `openService`, the only difference being that it returns an URL instead of opening immediately with `window.open`. +This API has exactly the same parameters as `openService`, the only difference being that it returns an URL instead of opening immediately with `window.open`. ### requestLoginLink diff --git a/sample/src/index.ts b/sample/src/index.ts index 7ffb6e9..0229f34 100644 --- a/sample/src/index.ts +++ b/sample/src/index.ts @@ -263,17 +263,17 @@ const disabledFunctions = () => { const validateToken = async () => { try { - const accessToken = await mtLinkSdk.exchangeToken(); - elements.accessTokenLabel.innerText = `Your access token is ${accessToken}.`; + const token = await mtLinkSdk.exchangeToken(); + elements.accessTokenLabel.innerText = `Your access token is ${token.access_token}.`; const authHeaders = new Headers({ method: 'GET', - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${token.access_token}` }); const response = await fetch('https://myaccount-staging.getmoneytree.com/oauth/token/info.json', { headers: authHeaders }); const data: ITokenInfo = await response.json(); - elements.accessTokenLabel.innerText = `Your access token is ${accessToken} + elements.accessTokenLabel.innerText = `Your access token is ${token.access_token} It was generated for the app: ${data.aud.name}. It will expire on ${new Date(data.exp * 1000)}. It allows you to: ${data.scopes.join(', ')} diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 5452da6..c3d8aaf 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -59,9 +59,18 @@ describe('index', () => { expect(result5).toBeUndefined(); expect(requestLoginLink).toBeCalledWith(storedOptions, { loginLinkTo: 'settings' }); - mocked(exchangeToken).mockResolvedValueOnce('test'); + const token = { + access_token: 'access_token', + refresh_token: 'refresh_token', + expires_in: 3600, + token_type: 'bearer', + scope: 'guest_read', + created_at: Date.now(), + resource_server: 'jp-api' + }; + mocked(exchangeToken).mockResolvedValueOnce(token); const result6 = await instance.exchangeToken({ code: 'code' }); - expect(result6).toBe('test'); + expect(result6).toEqual(token); expect(exchangeToken).toBeCalledWith(storedOptions, { code: 'code' }); // @ts-ignore: set tokenInfo with invalid type value diff --git a/src/api/__tests__/exchange-token.test.ts b/src/api/__tests__/exchange-token.test.ts index 212795d..f9d26d2 100644 --- a/src/api/__tests__/exchange-token.test.ts +++ b/src/api/__tests__/exchange-token.test.ts @@ -11,7 +11,15 @@ describe('api', () => { const clientId = 'clientId'; const code = 'code'; const redirectUri = 'redirectUri'; - const token = 'token'; + const token = { + access_token: 'access_token', + refresh_token: 'refresh_token', + expires_in: 3600, + token_type: 'bearer', + scope: 'guest_read', + created_at: Date.now(), + resource_server: 'jp-api' + }; const state = 'state'; const mtLinkSdk = new MtLinkSdk(); @@ -48,8 +56,7 @@ describe('api', () => { test('make request', async () => { fetch.mockClear(); - - fetch.mockResponseOnce(JSON.stringify({ access_token: token })); + fetch.mockResponseOnce(JSON.stringify(token)); await exchangeToken(mtLinkSdk.storedOptions, { code, codeVerifier: '' }); @@ -94,14 +101,12 @@ describe('api', () => { test('auto extract code from url query if no code was passed', async () => { fetch.mockClear(); + fetch.mockResponseOnce(JSON.stringify(token)); - const code1 = 'code1'; - const code2 = 'code2'; - - fetch.mockResponseOnce(JSON.stringify({ access_token: token })); + const code = 'realCode'; jest.spyOn(window, 'location', 'get').mockReturnValueOnce({ - search: `?code=${code1}&code=${code2}` + search: `?code=otherCode&code=${code}` } as typeof window.location); await exchangeToken(mtLinkSdk.storedOptions, { state }); @@ -109,21 +114,20 @@ describe('api', () => { const result = fetch.mock.calls[0][1] || {}; const data = JSON.parse(result.body as string); - expect(data.code).toBe(code2); + expect(data.code).toBe(code); }); test('auto extract state from url query if no state was passed or set during init', async () => { fetch.mockClear(); - - const state1 = 'state1'; - - fetch.mockResponseOnce(JSON.stringify({ access_token: token })); + fetch.mockResponseOnce(JSON.stringify(token)); jest.spyOn(window, 'location', 'get').mockReturnValueOnce({ - search: `?state=${state1}&state=${state}` + search: `?state=otherState&state=${state}` } as typeof window.location); - await expect(exchangeToken(mtLinkSdk.storedOptions, { code, redirectUri })).resolves.toBe(token); + const actual = await exchangeToken(mtLinkSdk.storedOptions, { code, redirectUri }); + + expect(actual).toEqual(token); }); test('non browser environment will not auto extract code from url', async () => { diff --git a/src/api/exchange-token.ts b/src/api/exchange-token.ts index 19013dd..1d6852e 100644 --- a/src/api/exchange-token.ts +++ b/src/api/exchange-token.ts @@ -2,7 +2,7 @@ import qs from 'qs'; import { generateSdkHeaderInfo } from '../helper'; import { MY_ACCOUNT_DOMAINS } from '../server-paths'; -import { StoredOptions, ExchangeTokenOptions } from '../typings'; +import { StoredOptions, ExchangeTokenOptions, Token } from '../typings'; import storage from '../storage'; function getCode(): string | undefined { @@ -21,7 +21,7 @@ function getCode(): string | undefined { export default async function exchangeToken( storedOptions: StoredOptions, options: ExchangeTokenOptions = {} -): Promise { +): Promise { const { clientId, redirectUri: defaultRedirectUri, mode } = storedOptions; if (!clientId) { @@ -64,7 +64,7 @@ export default async function exchangeToken( throw new Error(result.error_description); } - return result.access_token; + return result as Token; } catch (error) { throw new Error(`[mt-link-sdk] \`exchangeToken\` execution failed. ${error}`); } diff --git a/src/index.ts b/src/index.ts index 8259366..6c9d27a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ import requestLoginLink from './api/request-login-link'; import exchangeToken from './api/exchange-token'; import tokenInfo from './api/token-info'; import { + Token, StoredOptions, ServiceId, LogoutOptions, @@ -141,7 +142,7 @@ export class MtLinkSdk { return requestLoginLink(this.storedOptions, options); } - public exchangeToken(options?: ExchangeTokenOptions): Promise { + public exchangeToken(options?: ExchangeTokenOptions): Promise { return exchangeToken(this.storedOptions, options); } diff --git a/src/typings.ts b/src/typings.ts index dcd23bc..3cda954 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -170,3 +170,13 @@ export interface TokenInfo { lang: string; }; } + +export interface Token { + access_token: string; + refresh_token: string; + token_type: string; + created_at: number; + expires_in: number; + scope: string; + resource_server: string; +}