Skip to content
This repository was archived by the owner on Oct 14, 2026. It is now read-only.

Commit 83a21c0

Browse files
Ravi VermaCopilot
andcommitted
feat(ims-client): support optional ttl for promise token
getPromiseToken accepts an optional third ttl argument. It is included in the IMS promise token request only when it is a finite number greater than zero; otherwise the key is omitted. Backward compatible. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent fc75b50 commit 83a21c0

4 files changed

Lines changed: 81 additions & 2 deletions

File tree

‎packages/spacecat-shared-ims-client/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ async function fetchServiceAccessToken() {
5858
fetchServiceAccessToken();
5959
```
6060

61+
### Creating a Promise Token
62+
63+
An emitter `ImsPromiseClient` creates a promise token from an access token. Pass `true`
64+
as the second argument to encrypt the returned token. An optional third argument, `ttl`,
65+
is sent to IMS only when it is a finite number greater than zero; otherwise it is omitted
66+
and the IMS default applies.
67+
68+
```javascript
69+
const emitter = ImsPromiseClient.createFrom(context, ImsPromiseClient.CLIENT_TYPE.EMITTER);
70+
const { promise_token, expires_in } = await emitter.getPromiseToken(accessToken, false, 3600);
71+
```
72+
6173
### Getting IMS Organization Details
6274

6375
Retrieve details about an IMS organization by its ID using the `getImsOrganizationDetails` method.

‎packages/spacecat-shared-ims-client/src/clients/ImsPromiseClient.d.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,20 @@ export class ImsPromiseClient {
4242
* @param {string} accessToken The access token to get a promise token for.
4343
* @param {boolean} enableEncryption Whether to enable encryption of the promise
4444
* token, default false.
45+
* @param {number} [ttl] Optional token lifetime sent to IMS. Only included in the
46+
* request when it is a finite number greater than zero.
4547
* @throws Error when encryption is enabled and the encryption secret and salt is not set.
4648
* @returns {Promise<{
4749
* promise_token: string,
4850
* token_type: string,
4951
* expires_in: number,
5052
* }>} The promise token.
5153
*/
52-
getPromiseToken(accessToken: string, enableEncryption?: boolean): Promise<object>;
54+
getPromiseToken(
55+
accessToken: string,
56+
enableEncryption?: boolean,
57+
ttl?: number,
58+
): Promise<object>;
5359

5460
/**
5561
* Exchanges a promise token for an access token.

‎packages/spacecat-shared-ims-client/src/clients/ims-promise-client.js‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,15 @@ export default class ImsPromiseClient extends ImsBaseClient {
9797
this.type = type;
9898
}
9999

100-
async getPromiseToken(accessToken, enableEncryption = false) {
100+
/**
101+
* Creates a promise token for the given access token.
102+
* @param {string} accessToken - The authenticating access token.
103+
* @param {boolean} [enableEncryption=false] - Whether to encrypt the returned promise token.
104+
* @param {number} [ttl] - Optional token lifetime sent to IMS. Only included in the
105+
* request when it is a finite number greater than zero.
106+
* @returns {Promise<{promise_token: string, token_type: string, expires_in: number}>}
107+
*/
108+
async getPromiseToken(accessToken, enableEncryption = false, ttl = undefined) {
101109
if (this.type === ImsPromiseClient.CLIENT_TYPE.CONSUMER) {
102110
throw new Error('Consumer type does not support getPromiseToken method.');
103111
}
@@ -112,6 +120,7 @@ export default class ImsPromiseClient extends ImsBaseClient {
112120
grant_type: 'promise',
113121
promise_definition_id: this.config.promiseDefinitionId,
114122
authenticating_token: accessToken,
123+
...(Number.isFinite(ttl) && ttl > 0 ? { ttl } : {}),
115124
},
116125
{ noAuth: true, noContentType: true },
117126
);

‎packages/spacecat-shared-ims-client/test/clients/ims-promise-client.test.js‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,58 @@ describe('ImsPromiseClient', () => {
270270
});
271271
});
272272

273+
describe('getPromiseToken ttl', () => {
274+
const testAccessToken = '******';
275+
let emitterClient;
276+
let capturedBody;
277+
278+
beforeEach(() => {
279+
capturedBody = undefined;
280+
emitterClient = ImsPromiseClient.createFrom(
281+
mockContext,
282+
ImsPromiseClient.CLIENT_TYPE.EMITTER,
283+
);
284+
nock(`https://${DUMMY_HOST}`)
285+
.post(IMS_TOKEN_ENDPOINT, (body) => {
286+
capturedBody = body;
287+
return true;
288+
})
289+
.reply(200, {
290+
promise_token: 'promiseTokenExample',
291+
token_type: 'promise_token',
292+
expires_in: 3600,
293+
});
294+
});
295+
296+
it('sends ttl when it is a positive number', async () => {
297+
const result = await emitterClient.getPromiseToken(testAccessToken, false, 3600);
298+
expect(capturedBody).to.include('name="ttl"\r\n\r\n3600\r\n');
299+
expect(capturedBody).to.include('name="grant_type"\r\n\r\npromise');
300+
expect(result.expires_in).to.equal(3600);
301+
});
302+
303+
it('does not send ttl when omitted', async () => {
304+
await emitterClient.getPromiseToken(testAccessToken);
305+
expect(capturedBody).to.include('name="grant_type"\r\n\r\npromise');
306+
expect(capturedBody).to.not.include('name="ttl"');
307+
});
308+
309+
[
310+
['zero', 0],
311+
['negative', -60],
312+
['NaN', NaN],
313+
['Infinity', Infinity],
314+
['a numeric string', '3600'],
315+
['null', null],
316+
].forEach(([label, ttl]) => {
317+
it(`does not send ttl when it is ${label}`, async () => {
318+
await emitterClient.getPromiseToken(testAccessToken, false, ttl);
319+
expect(capturedBody).to.include('name="grant_type"\r\n\r\npromise');
320+
expect(capturedBody).to.not.include('name="ttl"');
321+
});
322+
});
323+
});
324+
273325
describe('exchangeToken', () => {
274326
const testToken = 'eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEyMzQ1IiwidHlwZSI6ImFjY2Vzc190b2tlbiIsImNsaWVudF9pZCI6ImV4YW1wbGVfYXBwIiwidXNlcl9pZCI6Ijk4NzY1NDc4OTBBQkNERUYxMjM0NTY3OEBhYmNkZWYxMjM0NTY3ODkuZSIsImFzIjoiaW1zLW5hMSIsImFhX2lkIjoiMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4QGFkb2JlLmNvbSIsImNyZWF0ZWRfYXQiOiIxNzEwMjQ3MDAwMDAwIn0.MRDpxgxSHDj4DmA182hPnjMAnKkly-VUJ_bXpQ-J8EQ';
275327
let consumerClient;

0 commit comments

Comments
 (0)