diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index eaa1d78..64ec916 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -38,6 +38,6 @@ jobs: - name: Build application run: npm run build - name: Publish to npm - run: npm publish --access=public + run: npm publish --access=public --tag beta #TODO: To be removed, for PoC purposes only env: NODE_AUTH_TOKEN: ${{secrets.NPMJS_AUTHTOKEN}} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 264188a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -dist: focal -language: node_js -node_js: '18' -cache: npm -before_install: - - npm config set @schibsted:registry=https://registry.npmjs.org/ - - npm config set //registry.npmjs.org/:_authToken=${NPMJS_AUTHTOKEN} -install: npm ci -script: - - npm run cover - - npm run build - - npm run docs -deploy: - - provider: script - skip_cleanup: true - script: npm publish --access=public --tag beta - on: - branch: master - tags: true - - provider: pages - skip-cleanup: true - github-token: $GITHUB_TOKEN - keep-history: true - local-dir: docs - on: - branch: master diff --git a/__tests__/identity.js b/__tests__/identity.js index aea0934..f5b0a85 100644 --- a/__tests__/identity.js +++ b/__tests__/identity.js @@ -1,4 +1,4 @@ -/* Copyright 2018 Schibsted Products & Technology AS. Licensed under the terms of the MIT license. +/* Copyright 2024 Schibsted Products & Technology AS. Licensed under the terms of the MIT license. * See LICENSE.md in the project root. */ @@ -28,6 +28,13 @@ describe('Identity', () => { clientId: 'foo', redirectUri: 'http://foo.com', sessionDomain: 'http://id.foo.com', + callbackBeforeRedirect: jest.fn(), + window:{ + location:{ + href:'http://test.no', + origin: 'http://foo.bar' + }, + } }; beforeAll(() => { @@ -305,32 +312,63 @@ describe('Identity', () => { describe('hasSession', () => { let identity; + const getSessionMock = jest.fn(() => ({ ok: true, json: () => Fixtures.sessionResponse })); + const mockSessionOkResponse = (response)=>{ + getSessionMock.mockImplementationOnce(() => ({ ok: true, json: () => response })); + } + beforeEach(() => { identity = new Identity(defaultOptions); - identity._sessionService.fetch = jest.fn(() => ({ ok: true, json: () => Fixtures.sessionResponse })); + identity._sessionService.fetch = getSessionMock; + identity._clearVarnishCookie(); + }); + + afterEach(()=>{ + jest.clearAllMocks(); + }) + + test('should clear varnish cookie for domain', async () => { + identity.enableVarnishCookie(10); + + mockSessionOkResponse({ result: true, sp_id: 'abc', baseDomain: 'spid.no' }); + + await identity.hasSession(); + + expect(document.cookie).toBe('SP_ID=abc'); + identity._clearVarnishCookie(); + + expect(document.cookie).toBe(''); }); test('should be able to set varnish cookie', async () => { await identity.hasSession(); + expect(document.cookie).toBe(''); + identity.enableVarnishCookie(); + await identity.hasSession(); expect(document.cookie).toBe('SP_ID=some-jwt-token'); }); test('should not set varnish cookie if session has no `expiresIn`', async () => { identity.enableVarnishCookie(); - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: true, json: () => ({ result: true, sp_id: 'abc' }) })); + + mockSessionOkResponse({ result: true, sp_id: 'abc' }); + await identity.hasSession(); + expect(document.cookie).toBe(''); }); test('should set varnish cookie also when reading from cache', async () => { identity.enableVarnishCookie(); - const session = { result: true, sp_id: 'should_not_expire', expiresIn: 2 }; - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: true, json: () => session })); + + mockSessionOkResponse({ result: true, sp_id: 'should_not_expire', expiresIn: 2 }) + await identity.hasSession(); + expect(document.cookie).toBe('SP_ID=should_not_expire'); // 1. Here we first wait a little bit (*less* than the 2 second cache expiry) @@ -349,128 +387,198 @@ describe('Identity', () => { test('should work to set varnish cache expiration', async () => { identity.enableVarnishCookie(3); - const session = { result: true, sp_id: 'should_remain_after_one_sec', expiresIn: 1 }; - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: true, json: () => session })); + + mockSessionOkResponse({ result: true, sp_id: 'should_remain_after_one_sec', expiresIn: 1 }) + await identity.hasSession(); + await new Promise((resolve) => setTimeout(resolve, 1010)); + expect(document.cookie).toBe('SP_ID=should_remain_after_one_sec'); }); test('should work to clear varnish cookie', async () => { + mockSessionOkResponse({ result: true, sp_id: 'should_be_cleared', expiresIn: 1 }); + identity.enableVarnishCookie(3); - const session = { result: true, sp_id: 'should_be_cleared', expiresIn: 1 }; - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: true, json: () => session })); + await identity.hasSession(); + expect(document.cookie).toBe('SP_ID=should_be_cleared'); + identity._maybeClearVarnishCookie(); + expect(document.cookie).toBe(''); }); describe('`baseDomain`', () => { test('should respect `baseDomain` from session', async () => { identity.enableVarnishCookie(); - const session1 = { result: true, sp_id: 'abc', expiresIn: 3600, baseDomain: 'foo.com' }; - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: true, json: () => session1 })); + + mockSessionOkResponse({ result: true, sp_id: 'abc', expiresIn: 3600, baseDomain: 'foo.com' }); + await identity.hasSession(); + expect(document.cookie).toBe(''); }); test('should respect `baseDomain` from session', async () => { + mockSessionOkResponse({ result: true, sp_id: 'abc', expiresIn: 3600 }); + identity.enableVarnishCookie(); - const session2 = { result: true, sp_id: 'abc', expiresIn: 3600 }; - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: true, json: () => session2 })); + await identity.hasSession(); + expect(document.cookie).toBe('SP_ID=abc'); }); }); describe(`enableVarnishCookie domain`, () => { - test('works', async () => { - identity.enableVarnishCookie({ domain: 'spid.no' }); - expect(identity.varnishCookieDomain).toBe('spid.no'); - const session1 = { result: true, sp_id: 'abc', expiresIn: 3600, baseDomain: 'tv.spid.no' }; - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: true, json: () => session1 })); - await identity.hasSession(); - expect(document.cookie).toBe('SP_ID=abc'); - }); + const domain = 'spid.no'; + const expiresIn= 10; + + beforeEach(()=>{ + //session base domain is `tv.spid.no` which is different from jest testURL, so cookie is not set + mockSessionOkResponse({ result: true, sp_id: 'abc', expiresIn: 3600, baseDomain: 'tv.spid.no' }); + }) + + const cases = [ + [undefined, undefined, 0, ''], + [{expiresIn}, undefined, expiresIn, ''], + [{domain}, domain, 0, 'SP_ID=abc'], + [{domain, expiresIn}, domain, expiresIn, 'SP_ID=abc'], + ] + + test.each(cases)( + "with %p as cookieSetup, %p as varnishCookieDomain, %p as varnishExpiresIn set cookies %p", + async (cookieConfig, varnishCookieDomain, varnishExpiresIn, exepectedCookie) => { + identity.enableVarnishCookie(cookieConfig); + + expect(identity.varnishCookieDomain).toBe(varnishCookieDomain); + expect(identity.varnishExpiresIn).toBe(varnishExpiresIn); + + await identity.hasSession(); + + expect(document.cookie).toBe(exepectedCookie); + } + ); }); test('should only go to session-service for site specific logout', async () => { - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: false, status: 400, statusText: 'No cookie present' })); + getSessionMock.mockImplementationOnce(() => ({ ok: false, status: 400, statusText: 'No cookie present' })); + await expect(identity.hasSession()).rejects.toMatchObject({ message: 'HasSession failed' }); - expect(identity._sessionService.fetch.mock.calls.length).toBe(1); - expect(identity._sessionService.fetch.mock.calls[0][0]).toMatch(/^http:\/\/id.foo.com\/session/); + + expect(getSessionMock).toHaveBeenCalledTimes(1) + expect(getSessionMock).toHaveBeenCalledWith( + expect.stringMatching(/^http:\/\/id\.foo\.com\/v2\/session/), + {"credentials": "include", "headers": {}, "method": "get"} + ) }); test('should fail `hasSession` if session cookie is present but no session is found and site does not have specific logout', async () => { - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: false, status: 404, statusText: 'No session found' })); + getSessionMock.mockImplementationOnce(() => ({ ok: false, status: 404, statusText: 'No session found' })); + await expect(identity.hasSession()).rejects.toMatchObject({ message: 'HasSession failed' }); - expect(identity._sessionService.fetch.mock.calls.length).toBe(1); - expect(identity._sessionService.fetch.mock.calls[0][0]).toMatch(/^http:\/\/id.foo.com\/session/); + + expect(getSessionMock).toHaveBeenCalledTimes(1) + expect(getSessionMock).toHaveBeenCalledWith( + expect.stringMatching(/^http:\/\/id\.foo\.com\/v2\/session/), + {"credentials": "include", "headers": {}, "method": "get"} + ) }); test('should terminate "chain" if session-service call succeeds', async () => { - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: true, json: () => ({}) })); + mockSessionOkResponse({}); + await expect(identity.hasSession()).resolves.toMatchObject({}); - expect(identity._sessionService.fetch.mock.calls.length).toBe(1); - expect(identity._sessionService.fetch.mock.calls[0][0]).toMatch(/^http:\/\/id.foo.com\/session/); + + expect(getSessionMock).toHaveBeenCalledTimes(1) + expect(getSessionMock).toHaveBeenCalledWith( + expect.stringMatching(/^http:\/\/id\.foo\.com\/v2\/session/), + {"credentials": "include", "headers": {}, "method": "get"} + ) + }); + + test('should throw en SDK error when get /session returned an error', async () => { + mockSessionOkResponse({error: 'some error'}); + + await expect(identity.hasSession()).rejects.toThrowError('HasSession failed'); }); test('should emit event both when "real" and "cached" values are used', async () => { const spy = jest.fn(); + identity.on('login', spy); + await identity.hasSession(); await identity.hasSession(); + expect(spy).toHaveBeenCalledTimes(2); }); test('should return the same promise if invoked multiple times', async () => { - identity._sessionService.fetch.mockImplementationOnce(() => new Promise((resolve) => { - setTimeout(resolve({ ok: true, json: () => ({ sp_id: 'yo' }) }), 1); - })); + mockSessionOkResponse({ sp_id: 'yo' }); + const promise1 = identity.hasSession(); const promise2 = identity.hasSession(); // NOTE: no 'await' — we want the promise + expect(promise2).toBe(promise1); + const dummy = await promise1; + expect(dummy).toMatchObject({ sp_id: 'yo' }); }); test('should throw error if session-service returns error without 404', async () => { - identity._sessionService.fetch.mockImplementationOnce(() => ({ ok: false, status: 401, statusText: 'Unauthorized' })); + getSessionMock.mockImplementationOnce(() => ({ ok: false, status: 401, statusText: 'Unauthorized' })); + await expect(identity.hasSession()).rejects.toMatchObject({ message: 'HasSession failed' }); - expect(identity._sessionService.fetch.mock.calls.length).toBe(1); - expect(identity._sessionService.fetch.mock.calls[0][0]).toMatch(/^http:\/\/id.foo.com\/session/); + + expect(getSessionMock).toHaveBeenCalledTimes(1) + expect(getSessionMock).toHaveBeenCalledWith( + expect.stringMatching(/^http:\/\/id\.foo\.com\/v2\/session/), + {"credentials": "include", "headers": {}, "method": "get"} + ) }); describe('cache', () => { test('should never cache if caching is off', async () => { identity._enableSessionCaching = false; + await identity.hasSession(); await identity.hasSession(); - expect(identity._sessionService.fetch.mock.calls.length).toBe(2); + expect(getSessionMock).toHaveBeenCalledTimes(2) }); test('should use cached value on subsequent calls by default', async () => { await identity.hasSession(); await identity.hasSession(); - expect(identity._sessionService.fetch.mock.calls.length).toBe(1); + expect(getSessionMock).toHaveBeenCalledTimes(1) }); test('cache shouldn\'t be updated when hasSession returns data from cache, but should be if cache expired', async () => { - const getExpiresOn = () => JSON.parse(identity.cache.cache.get('hasSession-cache')).expiresOn; jest.spyOn(Date, 'now') .mockReturnValue(new Date("2019-11-09T10:00:00").getTime()); + + const getExpiresOn = () => JSON.parse(identity.cache.cache.get('hasSession-cache')).expiresOn; + await identity.hasSession(); + const cacheExpires = getExpiresOn(); jest.spyOn(Date, 'now') .mockReturnValue(new Date("2019-11-09T10:02:00").getTime()); + await identity.hasSession(); + expect(getExpiresOn()).toBe(cacheExpires); // expiresOn shouldn't change on call less than 5m jest.spyOn(Date, 'now') .mockReturnValue(new Date("2019-11-09T11:05:00").getTime()); + await identity.hasSession(); + // expiresOn should change after 1h expect(getExpiresOn()).not.toBe(cacheExpires); }); @@ -481,20 +589,30 @@ describe('Identity', () => { // the cached data should be removed so the second call should result in a new request await identity.hasSession(); - expect(identity._sessionService.fetch.mock.calls.length).toBe(2); + expect(getSessionMock).toHaveBeenCalledTimes(2) }); }); - describe('full page redirect', ()=>{ + describe('session refresh full page redirect', ()=>{ test('should do redirect when session endpoint respond with redirectURL only', async () => { - identity._sessionService.fetch.mockReturnValueOnce( - ({ ok: true, json: () => Fixtures.sessionNeedsToBeRefreshedResponse }) - ) - const spy = jest.spyOn(identity, 'emit'); + mockSessionOkResponse(Fixtures.sessionNeedsToBeRefreshedResponse) await identity.hasSession(); - expect(spy).toHaveBeenCalledWith('redirectToSessionService'); + expect(defaultOptions.callbackBeforeRedirect).toHaveBeenCalled(); + + expect(defaultOptions.window.location.href).toBe( + [ + defaultOptions.sessionDomain, + Fixtures.sessionNeedsToBeRefreshedResponse.redirectURL, + '?client_sdrn=sdrn%3Aschibsted.com%3Aclient%3A', + defaultOptions.clientId, + '&redirect_uri=', + encodeURIComponent(defaultOptions.window.location.origin), + '&sdk_version=', + version + ].join('') + ); }); }) }); diff --git a/__tests__/utils.js b/__tests__/utils.js index 9a727d0..cd4b0ef 100644 --- a/__tests__/utils.js +++ b/__tests__/utils.js @@ -60,7 +60,7 @@ const sessionResponse = { }; const sessionNeedsToBeRefreshedResponse = { - redirectURL: 'http://example.com/test' + redirectURL: '/refresh-cookie-test' }; const sessionServiceAccess = { diff --git a/package-lock.json b/package-lock.json index 54ef64d..32da30c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@schibsted/account-sdk-browser", - "version": "4.8.6-beta.2", + "version": "4.8.7-beta", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@schibsted/account-sdk-browser", - "version": "4.8.6-beta.2", + "version": "4.8.7-beta", "license": "MIT", "dependencies": { "tiny-emitter": "^2.1.0" @@ -15,7 +15,6 @@ "@babel/core": "^7.11.4", "@babel/preset-env": "^7.23.2", "babel-loader": "^8.1.0", - "codecov": "^3.6.5", "core-js": "^3.6.5", "docdash": "git+https://github.com/torarvid/docdash.git#v0.5.0", "eslint": "^6.8.0", @@ -2489,15 +2488,6 @@ "@sinonjs/commons": "^1.7.0" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@types/babel__core": { "version": "7.1.9", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", @@ -2881,18 +2871,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/ajv": { "version": "6.12.4", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", @@ -2987,15 +2965,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/argv": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz", - "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=", - "dev": true, - "engines": { - "node": ">=0.6.10" - } - }, "node_modules/arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -3997,25 +3966,6 @@ "node": ">= 0.12.0" } }, - "node_modules/codecov": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.7.2.tgz", - "integrity": "sha512-fmCjAkTese29DUX3GMIi4EaKGflHa4K51EoMc29g8fBHawdk/+KEq5CWOeXLdd9+AT7o1wO4DIpp/Z1KCqCz1g==", - "dev": true, - "dependencies": { - "argv": "0.0.2", - "ignore-walk": "3.0.3", - "js-yaml": "3.13.1", - "teeny-request": "6.0.1", - "urlgrey": "0.4.4" - }, - "bin": { - "codecov": "bin/codecov" - }, - "engines": { - "node": ">=4.0" - } - }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -6145,20 +6095,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -6180,28 +6116,6 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "node_modules/https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dev": true, - "dependencies": { - "agent-base": "5", - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/https-proxy-agent/node_modules/agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -6244,15 +6158,6 @@ "node": ">= 4" } }, - "node_modules/ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "dependencies": { - "minimatch": "^3.0.4" - } - }, "node_modules/import-fresh": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", @@ -11477,15 +11382,6 @@ "stream-shift": "^1.0.0" } }, - "node_modules/stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "dev": true, - "dependencies": { - "stubs": "^3.0.0" - } - }, "node_modules/stream-http": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", @@ -11642,12 +11538,6 @@ "node": ">=8" } }, - "node_modules/stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", - "dev": true - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -11759,19 +11649,6 @@ "node": ">=6" } }, - "node_modules/teeny-request": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.1.tgz", - "integrity": "sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g==", - "dev": true, - "dependencies": { - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.2.0", - "stream-events": "^1.0.5", - "uuid": "^3.3.2" - } - }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -12336,12 +12213,6 @@ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true }, - "node_modules/urlgrey": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz", - "integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=", - "dev": true - }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -15255,12 +15126,6 @@ "@sinonjs/commons": "^1.7.0" } }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, "@types/babel__core": { "version": "7.1.9", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", @@ -15635,15 +15500,6 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, - "agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", - "dev": true, - "requires": { - "debug": "4" - } - }, "ajv": { "version": "6.12.4", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", @@ -15725,12 +15581,6 @@ "sprintf-js": "~1.0.2" } }, - "argv": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz", - "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=", - "dev": true - }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -16565,19 +16415,6 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, - "codecov": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.7.2.tgz", - "integrity": "sha512-fmCjAkTese29DUX3GMIi4EaKGflHa4K51EoMc29g8fBHawdk/+KEq5CWOeXLdd9+AT7o1wO4DIpp/Z1KCqCz1g==", - "dev": true, - "requires": { - "argv": "0.0.2", - "ignore-walk": "3.0.3", - "js-yaml": "3.13.1", - "teeny-request": "6.0.1", - "urlgrey": "0.4.4" - } - }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -18362,17 +18199,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -18390,24 +18216,6 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dev": true, - "requires": { - "agent-base": "5", - "debug": "4" - }, - "dependencies": { - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true - } - } - }, "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -18441,15 +18249,6 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, "import-fresh": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", @@ -22719,15 +22518,6 @@ "stream-shift": "^1.0.0" } }, - "stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "dev": true, - "requires": { - "stubs": "^3.0.0" - } - }, "stream-http": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", @@ -22860,12 +22650,6 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -22957,19 +22741,6 @@ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, - "teeny-request": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.1.tgz", - "integrity": "sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g==", - "dev": true, - "requires": { - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.2.0", - "stream-events": "^1.0.5", - "uuid": "^3.3.2" - } - }, "terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -23428,12 +23199,6 @@ } } }, - "urlgrey": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz", - "integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=", - "dev": true - }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", diff --git a/package.json b/package.json index 663b3f8..a30fef5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@schibsted/account-sdk-browser", - "version": "4.8.6-beta.2", + "version": "4.8.7-beta", "description": "Schibsted account SDK for browsers", "main": "index.js", "type": "module", @@ -9,12 +9,10 @@ "clean": "rimraf .cache coverage dist docs", "docs": "rimraf docs && jsdoc -c ./jsdoc.conf.json --verbose", "lint": "eslint .", - "pretest": "npm run lint", + "lint:fix": "eslint . --fix", "test": "jest", - "precover": "npm run lint", "cover": "jest --coverage", - "postcover": "codecov", - "preversion": "npm test", + "preversion": "npm run lint && npm test", "version": "node ./scripts/genversion.js && git add src/version.js", "postversion": "git push && git push --tags" }, @@ -27,7 +25,6 @@ "@babel/core": "^7.11.4", "@babel/preset-env": "^7.23.2", "babel-loader": "^8.1.0", - "codecov": "^3.6.5", "core-js": "^3.6.5", "docdash": "git+https://github.com/torarvid/docdash.git#v0.5.0", "eslint": "^6.8.0", diff --git a/src/identity.d.ts b/src/identity.d.ts index de9fafc..ec7e029 100644 --- a/src/identity.d.ts +++ b/src/identity.d.ts @@ -13,15 +13,17 @@ export class Identity extends TinyEmitter { * @param {function} [options.log] - A function that receives debug log information. If not set, * no logging will be done * @param {object} [options.window] - window object + * @param {function} [options.callbackBeforeRedirect] - callback triggered before session refresh redirect happen * @throws {SDKError} - If any of options are invalid */ - constructor({ clientId, redirectUri, sessionDomain, env, log, window }: { + constructor({ clientId, redirectUri, sessionDomain, env, log, window, callbackBeforeRedirect }: { clientId: string; sessionDomain: string; redirectUri: string; env?: string; log?: Function; window?: any; + callbackBeforeRedirect?: Function; }); _sessionInitiatedSent: boolean; window: any; @@ -30,6 +32,7 @@ export class Identity extends TinyEmitter { redirectUri: string; env: string; log: Function; + callbackBeforeRedirect: Function; _sessionDomain: string; _enableSessionCaching: boolean; _session: {}; diff --git a/src/identity.js b/src/identity.js index 35d35ed..b9f4785 100644 --- a/src/identity.js +++ b/src/identity.js @@ -160,9 +160,18 @@ export class Identity extends EventEmitter { * @param {function} [options.log] - A function that receives debug log information. If not set, * no logging will be done * @param {object} [options.window] - window object + * @param {function} [options.callbackBeforeRedirect] - callback triggered before session refresh redirect happen * @throws {SDKError} - If any of options are invalid */ - constructor({ clientId, redirectUri, sessionDomain, env = 'PRE', log, window = globalWindow() }) { + constructor({ + clientId, + redirectUri, + sessionDomain, + env = 'PRE', + log, + window = globalWindow(), + callbackBeforeRedirect = ()=>{} + }) { super(); assert(isNonEmptyString(clientId), 'clientId parameter is required'); assert(isObject(window), 'The reference to window is missing'); @@ -177,6 +186,7 @@ export class Identity extends EventEmitter { this.redirectUri = redirectUri; this.env = env; this.log = log; + this.callbackBeforeRedirect = callbackBeforeRedirect; this._sessionDomain = sessionDomain; // Internal hack: set to false to always refresh from hassession @@ -483,6 +493,7 @@ export class Identity extends EventEmitter { if (this._hasSessionInProgress) { return this._hasSessionInProgress; } + const _postProcess = (sessionData) => { if (sessionData.error) { throw new SDKError('HasSession failed', sessionData.error); @@ -492,14 +503,14 @@ export class Identity extends EventEmitter { this._session = sessionData; return sessionData; }; + const _checkRedirectionNeed = (sessionData={})=>{ const sessionDataKeys = Object.keys(sessionData); - const isRedirectNeeded = sessionDataKeys.length === 1 && + return sessionDataKeys.length === 1 && sessionDataKeys[0] === 'redirectURL'; - - return isRedirectNeeded; } + const _getSession = async () => { if (this._enableSessionCaching) { // Try to resolve from cache (it has a TTL) @@ -510,7 +521,7 @@ export class Identity extends EventEmitter { } let sessionData = null; try { - sessionData = await this._sessionService.get('/session'); + sessionData = await this._sessionService.get('/v2/session'); } catch (err) { if (err && err.code === 400 && this._enableSessionCaching) { const expiresIn = 1000 * (err.expiresIn || 300); @@ -522,11 +533,10 @@ export class Identity extends EventEmitter { if(sessionData){ // for expiring session and safari browser do full page redirect to gain new session if(_checkRedirectionNeed(sessionData)){ - const client_sdrn = `sdrn:${NAMESPACE[this.env]}:client:${this.clientId}`; - const redirectBackUrl = this.redirectUri.substring(0 , this.redirectUri.lastIndexOf('/')); - const params = { redirect_uri: redirectBackUrl, client_sdrn: client_sdrn}; - this.emit('redirectToSessionService'); - this.window.location.href = this._sessionService.makeUrl(sessionData.redirectURL.substring(sessionData.redirectURL.lastIndexOf('/')), params); + await this.callbackBeforeRedirect(); + + this.window.location.href = this._sessionService.makeUrl(sessionData.redirectURL, {redirect_uri: this.window.location.origin}); + return; } @@ -657,8 +667,6 @@ export class Identity extends EventEmitter { * @param {string|null} optionalSuffix * @description This function calls {@link Identity#hasSession} internally and thus has the side * effect that it might perform an auto-login on the user - * @param {string} externalParty - * @param {string|null} optionalSuffix * @throws {SDKError} If the `pairId` is missing in user session. * @throws {SDKError} If the `externalParty` is not defined * @return {Promise} The merchant- and 3rd-party-specific `externalId` diff --git a/src/version.js b/src/version.js index 79827bc..8e16d26 100644 --- a/src/version.js +++ b/src/version.js @@ -1,5 +1,5 @@ // Automatically generated in 'npm version' by scripts/genversion.js 'use strict' -const version = '4.8.6-beta.2'; +const version = '4.8.7-beta'; export default version;