You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1db8435
Browse filesBrowse the repository at this point in the historyBrowse files
feat: setUserJwt, setUserJwtProvider and setAuthTokens (customer identity) (#203)
* feat(identity): setUserJwt, setUserJwtProvider and setAuthTokens
Customer identity (contract §2, §3, §5, §6): one HS256 JWT signed by the
app's backend is the whole identify.
- Gleap.setUserJwt(token) decodes the payload (no verification), takes
userId / user_id and sends the existing identify with userHash = token and
no contact fields. Bad shape, no user id or over 4096 chars: console
warning, no request. Memory only. The once-only signed-identify record adds
a fingerprint of a JWT, so a new token is sent once even when nothing else
changed; legacy hashes keep the gleapId:userId key.
- Gleap.setUserJwtProvider(fn) is asked on identify 401 token_expired (one
retry with the new token) and before a file-access renewal or identify
replay when the token expires within 5 minutes. One call in flight, 10 s
timeout, 30 s pause after a failure. token_expired / token_invalid never
clear the local session.
- Gleap.setAuthTokens(tokens) posts to /sessions/auth-tokens; names checked
against ^[a-zA-Z][a-zA-Z0-9_-]{0,39}$, null deletes, kept in memory and
resent after a signed identify on a session they were not sent to; 401
warns and keeps the session.
- clearIdentity drops the token and auth tokens; the provider stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(identity): README teaches the signed identity token first
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(identity): carry the signed-session grant on every request
A signed identify now returns identity.grant. The SDK keeps it in memory
and in the gleap-grant-<apiKey> cookie (same domain rules as the session
cookie, expiring with the grant), never in the persisted session. It is
restored on reload for the same session, sent as Gleap-Grant on every SDK
request, pushed to the messenger and AI chatbar as a top-level grant field
of session-update, and refreshed a minute before it expires.
clearIdentity drops it and its cookie (the provider stays). A 400
identity_mismatch drops the local session and retries once as a fresh
visitor. getIdentity returns the grant; initialize takes sessionDuration
(ms), sent with identify. Gleap-Grant is masked in captured network logs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(identity): grant refresh from its lifetime, setUserJwt never rejects, signed cookie
- Grant refresh: delay from the grant's own lifetime (exp - iat) counted from
receipt, at least 30 s, at most one refresh identify a minute, skipped while
one is in flight. A short grant or a fast device clock no longer loops.
- A token re-minted per page load (same kid and claims, new iat/exp/jti) is not
identified again; a changed claim or key is.
- setUserJwt never rejects: refusals and network/server errors are logged.
- signature_required is logged.
- Gleap.setSessionDuration(ms) next to the initialize option.
- setUserJwtProvider accepts null (removes it) and a provider returning null.
- gleap-grant-<apiKey> cookie: SameSite=Lax, Secure on https.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(identity): signed-mode grant for returning users, logout race, refresh retries
- Signed mode without a grant on an identified session: one identify per
session load with the held token or hash, even when it was sent before
(at most once a minute, never a loop).
- An identify answer that lands after clearSession is dropped (logout
generation), as is a partial update answer.
- signature_required for another user than the session's clears the session.
- A failed or skipped grant refresh is tried again a minute later; a failed
identify is replayed when the browser comes back online.
- A provider token that already expired counts as a provider failure; a
refused token is not resent on every render (memory-only fingerprint).
- setUserJwt before initialize decides once the cached session and its grant
cookie are loaded, so a live grant costs no identify.
- setUseCookies(false) erases the grant cookie.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(identity): grant for agent conversations, legacy initialize typing, changelog
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(identity): drop a grant the server answered as not signed and re-identify
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* chore: version 19.5.0 (customer identity)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Companion to Server `AUTHENTICATED_FILES.md`. When the API flags a project as requiring authenticated files, a successful server-verified `identify` call supplies a short-lived file session to Messenger. The SDK refreshes it while active, preserves it only for the same identity, and requests revocation on logout. Unverified anonymous sessions cannot read protected files.
3
+
Companion to Server `AUTHENTICATED_FILES.md`. When the API flags a project as requiring authenticated files, a successful server-verified identify (a signed identity token via `Gleap.setUserJwt`, or the legacy user hash via `Gleap.identify(userId, data, userHash)`) supplies a short-lived file session to Messenger. The SDK refreshes it while active, preserves it only for the same identity, and requests revocation on logout. Guests and contacts identified without a signed identity (token or legacy hash) cannot read protected files.
4
4
5
-
A customer application configured for emailed file links must preserve `gleapFile` through its own login flow and call verified `identify`; the SDK then asks the API for the authorized conversation and opens it. Deploy the API and compatible Messenger before enabling the per-project setting. Offline revocation cannot reach the server; tokens still expire after 15 minutes.
5
+
The grant a signed identify returns (`identity.grant`) is a separate credential: it rides as `Gleap-Grant` on every SDK request (file uploads and feedback included) and goes to Messenger next to the session, never inside it, so the file session and the grant are never persisted together. Logout drops both.
6
+
7
+
A customer application configured for emailed file links must preserve `gleapFile` through its own login flow and identify the customer with a signed identity (`Gleap.setUserJwt`, or the legacy user hash); the SDK then asks the API for the authorized conversation and opens it. Deploy the API and compatible Messenger before enabling the per-project setting. Offline revocation cannot reach the server; tokens still expire after 15 minutes.
6
8
7
9
Validation: `npm test -- --runInBand --watchman=false` (277 tests), `npm run build`. No package publishing or synchronized SDK version bump is included.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,14 @@
1
1
# Changelog
2
2
3
+
## 19.5.0
4
+
Added: signed customer identity. `Gleap.setUserJwt(token)` identifies the user with an identity token (HS256 JWT signed by your backend); its `userId` claim names the user and every other claim lands on the contact as signed data. The token is kept in memory only, and the call never rejects: refusals are logged.
5
+
Added: `Gleap.setUserJwtProvider(provider)` hands Gleap a function that returns a fresh identity token; it is called when the current token expired or is about to.
6
+
Added: `Gleap.setAuthTokens(tokens)` stores per-user tokens for your API tools on the signed contact (`{{session.authTokens.<name>}}`); `null` removes one. Kept in memory only.
7
+
Added: `Gleap.setSessionDuration(ms)` and `Gleap.initialize(sdkKey, { sessionDuration })` ask the server for a shorter signed session (never beyond the project's maximum). Only sent with an identify when you set it. A boolean second argument to `initialize` (the legacy `disablePing`) is still accepted and typed.
8
+
Added: a signed identify returns a grant, the server-signed session credential. The SDK sends it as the `Gleap-Grant` header on every request, keeps it in the `gleap-grant-<apiKey>` cookie (when cookies are on) so a reload stays signed, refreshes it a minute before it runs out, hands it to the messenger, the AI chat bar and agent conversations, and returns it from `Gleap.getIdentity()`. `Gleap.clearIdentity()` drops it everywhere.
9
+
Added: when the project runs in signed identity mode (`identity.mode === 'signed'`) and a returning user's session has no live grant, the SDK sends the held identify once to get one, without looping.
10
+
Added: identify refusals are recognised by code (`token_expired`, `token_invalid`, `signature_required`, `identity_mismatch`); an expired token asks the JWT provider for a new one.
11
+
3
12
## 19.4.1
4
13
Fixed: since 19.4.0 the SDK failed to load on pages that are not UTF-8 (no charset declared, or for example ISO-8859-1), with `Invalid regular expression: Range out of order in character class`, so the widget did not appear at all. The bundle held raw non-ASCII characters, which such a page decodes with its own encoding. The build now escapes all non-ASCII characters, which also keeps text in the bundle intact on those pages.
Copy file name to clipboardExpand all lines: README.md
+50Lines changed: 50 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -47,6 +47,56 @@ Gleap.setRegion("us");
47
47
Gleap.initialize("SDK-TOKEN");
48
48
```
49
49
50
+
### Identify your users
51
+
52
+
#### Signed identity (recommended)
53
+
54
+
Your backend signs an HS256 JWT with an identity key from Settings > Security > Identity keys (put the key id in the `kid` header; the secret stays on your server). `userId` is required. Optional claims: `email`, `name`, `company` (`{ id, name }`), `plan`, and provider ids such as `stripeCustomerId`. Set `exp` to at most one hour. The token is the whole identify: every claim lands on the contact as signed data that the browser cannot change. Call `Gleap.setUserJwt(token)` after login and on every page load of a logged-in user. See [Customer identity](https://docs.gleap.ai/documentation/guides/customer-identity).
55
+
56
+
```
57
+
// Your backend (Node, jsonwebtoken). Use an identity key from Settings > Security > Identity keys.
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-<SDK-TOKEN>` cookie on your top-level domain (unless `Gleap.setUseCookies(false)`), 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:
or later with `Gleap.setSessionDuration(24 * 60 * 60 * 1000)` (the same call as in the mobile SDKs).
89
+
90
+
`Gleap.setUserJwt` never rejects: a refused token (`token_expired`, `token_invalid`, `signature_required`) or a network error is logged to the console.
91
+
92
+
`Gleap.getIdentity()` returns `{ gleapId, gleapHash, grant, ... }` for handing the signed session to your own help center or portal. A token for another user than the current session's makes the SDK start a fresh session and identify again (the server merges it into the right contact).
93
+
94
+
`Gleap.clearIdentity()` on logout clears the contact, the token, the grant (and its cookie) and the auth tokens. The JWT provider stays set.
95
+
96
+
#### Legacy user hash
97
+
98
+
Instead of a token you can still call `Gleap.identify(userId, data, userHash)`, with `userHash` the HMAC-SHA256 of the user id created on your server with the project's identity verification secret. It is accepted while "Accept legacy user hash" is on for the project.
99
+
50
100
### Dark mode (optional)
51
101
52
102
The widget can match your app's dark/light mode. Enable "Adapt to dark / light mode" in the dashboard (Widget → Look & feel) and set the dark colors there: header colors, UI color and background. In dark mode the widget uses these dark colors, plus the dark logo, header image and composer glow set in the dashboard; without dark colors it keeps its normal colors. Override the scheme at runtime, e.g. from your own theme toggle (only takes effect while "Adapt to dark / light mode" is enabled in the dashboard; until you call it, the dashboard setting applies):
Copy file name to clipboardExpand all lines: package.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
{
2
2
"description": "JavaScript and TypeScript SDK for Gleap, an Intercom alternative with AI customer support, live chat, in-app bug reporting and customer feedback.",
0 commit comments