diff --git a/CHANGELOG.md b/CHANGELOG.md index 06e6b3b7..7491154e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 19.6.0 +Changed: the key `Gleap.initialize` takes is now called **Project ID** (formerly "SDK key" or "API key"). The value is the same and every existing key keeps working; nothing needs to change in your app. Typings, JSDoc and the README name the parameter `projectId`. +Added: `Gleap.initialize({ projectId, sessionDuration })` takes the Project ID inside the options object, and `Gleap.initialize(undefined, { projectId })` works too. The positional form `Gleap.initialize(projectId, options)` and the legacy boolean second argument are unchanged. The options are typed as `Gleap.InitializeOptions`. + ## 19.5.2 Fixed: `Gleap.setUserJwtProvider` alone never signed the session again after a page reload. When the grant cookie was missing (an IP-address host, or the cookie deleted) and the app set only the provider, without calling `setUserJwt`, the user stayed unsigned although the server reported Signed mode. The SDK now asks the provider once per page load when the server reports Signed mode for an identified session without a valid grant and no token is held, and identifies with the returned token like `setUserJwt` (a token for another user goes through the usual `identity_mismatch` recovery). A provider that returns `null` sends nothing. Also applies when the provider is set after the session loaded. Fixed: cookies were never stored on IP-address hosts (for example `http://127.0.0.1:8080` or `http://[::1]:8080`). The SDK wrote an invalid `domain` attribute there, so the browser dropped the session and grant cookies. On an IP address they are now host-only cookies (no `domain`), and erasing them works the same way. Domain hosts and `localhost` are unchanged. diff --git a/README.md b/README.md index 72256491..1568d91d 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ import Gleap from 'gleap'; ### Initialize the SDK -Add the following code to initialize the Gleap JavaScript SDK. Replace "SDK-TOKEN" with your actual SDK token from the [Gleap dashboard](https://app.gleap.io). +Add the following code to initialize the Gleap JavaScript SDK. Replace "YOUR_PROJECT_ID" with your Project ID from the [Gleap dashboard](https://app.gleap.io) (Settings > Project). The Project ID was called "SDK key" or "API key" before; the value is the same and existing keys keep working. ``` -Gleap.initialize("SDK-TOKEN"); +Gleap.initialize("YOUR_PROJECT_ID"); ``` ### Data region (optional) @@ -44,7 +44,7 @@ Projects hosted in the US data region select it before initializing. The default ``` Gleap.setRegion("us"); -Gleap.initialize("SDK-TOKEN"); +Gleap.initialize("YOUR_PROJECT_ID"); ``` ### Identify your users @@ -79,10 +79,10 @@ Per-user tokens for your API tools (used as `{{session.authTokens.}}`) are Gleap.setAuthTokens({ backend: userAccessToken }); ``` -A valid token signs the session: the server answers with a grant that the SDK sends as the `Gleap-Grant` header on every request and hands to the messenger. It is kept in memory and in the `gleap-grant-` cookie on your top-level domain (unless `Gleap.setUseCookies(false)`; on an IP address such as `127.0.0.1` it is a host-only cookie), so a page reload and a help center on a subdomain stay signed until it expires. The project's session max age (Settings > Security, 7 days by default) sets its lifetime; shorten it per app with `sessionDuration` in milliseconds: +A valid token signs the session: the server answers with a grant that the SDK sends as the `Gleap-Grant` header on every request and hands to the messenger. It is kept in memory and in the `gleap-grant-` cookie on your top-level domain (unless `Gleap.setUseCookies(false)`; on an IP address such as `127.0.0.1` it is a host-only cookie), so a page reload and a help center on a subdomain stay signed until it expires. The project's session max age (Settings > Security, 7 days by default) sets its lifetime; shorten it per app with `sessionDuration` in milliseconds: ``` -Gleap.initialize("SDK-TOKEN", { sessionDuration: 24 * 60 * 60 * 1000 }); +Gleap.initialize("YOUR_PROJECT_ID", { sessionDuration: 24 * 60 * 60 * 1000 }); ``` or later with `Gleap.setSessionDuration(24 * 60 * 60 * 1000)` (the same call as in the mobile SDKs). diff --git a/index.d.ts b/index.d.ts index 682ba24d..60753073 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,15 +2,39 @@ export type GleapRegion = "eu" | "us"; export namespace Gleap { + /** Options for `initialize`. */ + interface InitializeOptions { + /** + * Your Project ID (Gleap dashboard: Settings > Project, or the install page). + * Only needed when `initialize` is called with the options object alone. + */ + projectId?: string; + /** + * How long (ms) a signed session stays signed before a new identify is needed. + * The server never goes beyond the project's session max age. + */ + sessionDuration?: number; + /** @deprecated Has no effect: the ping it controlled no longer exists. */ + disablePing?: boolean; + } /** + * Initializes Gleap with your Project ID (formerly called "SDK key" or "API key"; + * the value is the same, existing keys keep working). + * * `options.sessionDuration`: how long (ms) a signed session stays signed before a new * identify is needed. The server never goes beyond the project's session max age. * A boolean second argument is the legacy `disablePing` flag and is still accepted * (the ping it controlled no longer exists, so it has no effect). + * + * @example Gleap.initialize("YOUR_PROJECT_ID"); + * @example Gleap.initialize({ projectId: "YOUR_PROJECT_ID", sessionDuration: 86400000 }); */ function initialize( - sdkKey: string, - options?: boolean | { disablePing?: boolean; sessionDuration?: number } + projectId: string, + options?: boolean | InitializeOptions + ): void; + function initialize( + options: InitializeOptions & { projectId: string } ): void; function sendSilentCrashReport( description: string, @@ -200,7 +224,7 @@ export namespace Gleap { function setUserJwtProvider(provider: (() => string | null | Promise) | null): void; /** * How long (ms) a signed session stays signed; the server never goes beyond the - * project's maximum. Same as `initialize(sdkKey, { sessionDuration })`. `null` drops it. + * project's maximum. Same as `initialize(projectId, { sessionDuration })`. `null` drops it. */ function setSessionDuration(sessionDuration: number | null): void; /** diff --git a/package-lock.json b/package-lock.json index 1827dd99..3f879328 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gleap", - "version": "19.5.2", + "version": "19.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gleap", - "version": "19.5.2", + "version": "19.6.0", "license": "Commercial", "dependencies": { "@floating-ui/dom": "^1.7.5", diff --git a/package.json b/package.json index 96dcf966..3f1b58a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "description": "JavaScript and TypeScript SDK for Gleap, an Intercom alternative with AI customer support, live chat, in-app bug reporting and customer feedback.", "name": "gleap", - "version": "19.5.2", + "version": "19.6.0", "main": "build/cjs/index.js", "module": "build/esm/index.mjs", "exports": { diff --git a/src/Gleap.js b/src/Gleap.js index 5c785543..feb1767f 100755 --- a/src/Gleap.js +++ b/src/Gleap.js @@ -235,20 +235,30 @@ class Gleap { } /** - * Initializes the SDK - * @param {*} sdkKey - * @param {boolean | { disablePing?: boolean, sessionDuration?: number }} [options] + * Initializes the SDK with your Project ID (formerly called "SDK key"; the value is + * the same, existing keys keep working). + * @param {string | { projectId: string, sessionDuration?: number }} projectId + * The Project ID, or an options object that carries it as `projectId`. + * @param {boolean | { projectId?: string, disablePing?: boolean, sessionDuration?: number }} [options] * sessionDuration: how long (ms) a signed session stays signed; the server never goes * beyond the project's maximum. A boolean (the legacy `disablePing` argument) and * `disablePing` are accepted and have no effect: the ping no longer exists. */ - static initialize(sdkKey, options) { + static initialize(projectId, options) { const instance = this.getInstance(); if (instance.initialized) { console.warn('Gleap already initialized.'); return; } + // Gleap.initialize({ projectId, ...options }) + if (projectId && typeof projectId === 'object') { + options = projectId; + projectId = options.projectId; + } else if (!projectId && options && typeof options === 'object' && options.projectId) { + projectId = options.projectId; + } + if (options && typeof options === 'object' && 'sessionDuration' in options) { GleapSession.getInstance().setSessionDuration(options.sessionDuration); } @@ -273,7 +283,7 @@ class Gleap { // Start session const sessionInstance = GleapSession.getInstance(); - sessionInstance.sdkKey = sdkKey; + sessionInstance.sdkKey = projectId; sessionInstance.setOnSessionReady(() => { // Run auto configuration. setTimeout(() => { @@ -498,7 +508,7 @@ class Gleap { /** * How long (ms) a signed session stays signed; the server never goes beyond * the project's maximum. Sent with every identify. `null` drops it. - * Same as `initialize(sdkKey, { sessionDuration })`. + * Same as `initialize(projectId, { sessionDuration })`. * @param {number | null} sessionDuration */ static setSessionDuration(sessionDuration) { diff --git a/src/GleapInitialize.test.js b/src/GleapInitialize.test.js new file mode 100644 index 00000000..92c5b85c --- /dev/null +++ b/src/GleapInitialize.test.js @@ -0,0 +1,52 @@ +/** + * @jest-environment jsdom + */ +import Gleap, { GleapSession } from './Gleap'; + +// initialize() names the key "Project ID"; the positional form keeps working. +describe('Gleap.initialize(projectId)', () => { + let startSpy; + let warnSpy; + + beforeEach(() => { + Gleap.getInstance().initialized = false; + GleapSession.instance = undefined; + // startSession is an instance arrow property: stub it on the fresh singleton. + startSpy = jest.spyOn(GleapSession.getInstance(), 'startSession').mockImplementation(() => {}); + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + startSpy.mockRestore(); + warnSpy.mockRestore(); + Gleap.getInstance().initialized = false; + }); + + test('positional Project ID (the former SDK key) is used as before', () => { + Gleap.initialize('PROJECT_ID_1'); + expect(GleapSession.getInstance().sdkKey).toBe('PROJECT_ID_1'); + expect(startSpy).toHaveBeenCalledTimes(1); + }); + + test('positional Project ID with options keeps sessionDuration', () => { + Gleap.initialize('PROJECT_ID_2', { sessionDuration: 60000 }); + expect(GleapSession.getInstance().sdkKey).toBe('PROJECT_ID_2'); + expect(GleapSession.getInstance().sessionDuration).toBe(60000); + }); + + test('legacy boolean second argument is still accepted', () => { + Gleap.initialize('PROJECT_ID_3', true); + expect(GleapSession.getInstance().sdkKey).toBe('PROJECT_ID_3'); + }); + + test('options object with projectId', () => { + Gleap.initialize({ projectId: 'PROJECT_ID_4', sessionDuration: 120000 }); + expect(GleapSession.getInstance().sdkKey).toBe('PROJECT_ID_4'); + expect(GleapSession.getInstance().sessionDuration).toBe(120000); + }); + + test('projectId in the second argument when the first is empty', () => { + Gleap.initialize(undefined, { projectId: 'PROJECT_ID_5' }); + expect(GleapSession.getInstance().sdkKey).toBe('PROJECT_ID_5'); + }); +}); diff --git a/src/GleapSession.js b/src/GleapSession.js index a36bb4d0..1919391a 100644 --- a/src/GleapSession.js +++ b/src/GleapSession.js @@ -112,7 +112,7 @@ export default class GleapSession { // How the last identify failed ('refused' when a refusal was logged, else // 'network error' / 'HTTP '), for setUserJwt's log line. lastIdentifyFailure = null; - // Gleap.initialize(sdkKey, { sessionDuration }): asks the server for a + // Gleap.initialize(projectId, { sessionDuration }): asks the server for a // shorter grant (ms). Sent with every identify. sessionDuration = null; // Bumped only by clearSession (logout, destroy, rejected session). An @@ -1457,7 +1457,7 @@ export default class GleapSession { return retry; }; - /** Gleap.setSessionDuration / initialize(sdkKey, { sessionDuration }): a positive number of milliseconds, or null to drop it. */ + /** Gleap.setSessionDuration / initialize(projectId, { sessionDuration }): a positive number of milliseconds, or null to drop it. */ setSessionDuration = (sessionDuration) => { if (sessionDuration === undefined || sessionDuration === null) { this.sessionDuration = null;