Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d26e58b

Browse files
authoredMar 31, 2025··
Merge pull request #36 from s-group-dev/service-info
Service info
2 parents 5661b75 + 8f03b2b commit d26e58b

File tree

6 files changed

+157
-22
lines changed

6 files changed

+157
-22
lines changed
 

‎MIGRATION.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
- Read the Usercentrics Migration guide here for more context, although it shouldn't be relevant when using this package: https://usercentrics.com/docs/web/migration/migration-from-v2/
66
- The `windowEvent` prop for `<UsercentricsProvider />` is no longer supported and should be removed. The CMP v3 handles this automatically with a new event internally.
77
- The `uiVersion` prop for `<UsercentricsScript />` is no longer supported and should be removed. The CMP v3 loader script currently supports only the "latest" version.
8-
- This also means that hard-coding a version number and its [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) hash is no longer supported. Instead, use a random `nonce` value when implementing a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP).
8+
- This also means that hard-coding a version number and its [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) hash is no longer supported. Instead, use a random `nonce` value when implementing a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP): https://usercentrics.com/docs/web/implementation/ui/optional-steps/#adding-a-nonce-to-the-script-tag
99
- The `showSecondLayer()` util no longer supports passing a service id argument to directly open to the service info. Instead, use the new `showServiceDetails(serviceId: ServiceId)` util.
1010
- The `acceptService()` util has been replaced with `updateServicesConsents()` with a signature supporting multiple consents at a time.
11+
- You will also need to call `saveConsents()` after updating them.
1112
- The `getServicesFromLocalStorage()` util has been replaced with `getConsentsFromLocalStorage()` with a different format.
12-
- The following utils and hooks have been removed because CMP v3 no longer supports them:
13-
- `getServicesBaseInfo()`
14-
- `useServiceInfo()`
15-
- `getServicesFullInfo()`
16-
- `useServiceFullInfo()`
13+
- Some utils and hooks have been removed because CMP v3 no longer supports them:
14+
- Removed:
15+
- `getServicesBaseInfo()`
16+
- `useServiceInfo()`
17+
- `getServicesFullInfo()`
18+
- `useServiceFullInfo()`
19+
- Added new `getServiceInfo()` util for getting service names and descriptions
1720
- All the utils are now `async` to match the CMP v3 implementations
1821

1922
## 2.0.0

‎README.md

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,6 @@ declare module '@s-group/react-usercentrics/augmented' {
6969

7070
## API
7171

72-
### Types
73-
74-
#### `ConsentType`
75-
76-
When giving consent using the API (instead of customer clicking the Dialog),
77-
consent can be either explicit (e.g. when clicking some custom button) or implicit.
78-
79-
#### `UCWindow`
80-
81-
Augmented window type, possibly including the `__ucCmp` API.
82-
Do not declare this globally, but prefer to use the included utility functions instead.
83-
8472
### Components
8573

8674
#### `UsercentricsScript`
@@ -354,8 +342,20 @@ Updates consents for individual or multiple services.
354342

355343
See also https://usercentrics.com/docs/web/features/api/control-functionality/#updateservicesconsents
356344

345+
**Warning:** Updating consents doesn't save them! Remember to also call `saveConsents`.
346+
357347
```tsx
358-
updateServicesConsents([{ id: 'my-service-id', consent: true }])
348+
await updateServicesConsents([{ id: 'my-service-id', consent: true }])
349+
```
350+
351+
#### `saveConsents`
352+
353+
Saves the consents after being updated.
354+
355+
See also https://usercentrics.com/docs/web/features/api/control-functionality/#saveconsents
356+
357+
```tsx
358+
await saveConsents()
359359
```
360360

361361
#### `updateLanguage`
@@ -368,3 +368,13 @@ See also https://usercentrics.com/docs/web/features/api/control-functionality/#c
368368
```tsx
369369
updateLanguage('fi')
370370
```
371+
372+
#### `getServiceInfo`
373+
374+
Programmatic way to get the translated i18n content of the Web CMP modal.
375+
Useful for rendering custom UI with like listing services' names and descriptions.
376+
377+
```tsx
378+
const services = await getServiceInfo()
379+
const { name, description } = services['my-service-id']
380+
```

‎src/hooks/use-has-service-consent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const useHasServiceConsent = (serviceId: ServiceId): boolean | null => {
2525
return consents[serviceId] ?? null
2626
}
2727

28-
if (strictMode) {
28+
if (strictMode && !(serviceId in consents)) {
2929
throw new Error(`Usercentrics Service not found for id "${serviceId}"`)
3030
}
3131

‎src/types.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
66
export type ServiceId = import('@s-group/react-usercentrics/augmented').ServiceId
77

8+
export type ConsentType = 'EXPLICIT' | 'IMPLICIT'
9+
810
/** Partial type for service info read from local storage, if available. Unused values are left out. */
911
export type ServiceData = {
1012
name: string
1113
consent?: {
1214
given: boolean
13-
type: 'IMPLICIT' | 'EXPLICIT'
15+
type: ConsentType
1416
}
1517
}
1618

@@ -21,6 +23,12 @@ export type ServiceData = {
2123
type ConsentData = {
2224
status: 'ALL_ACCEPTED' | 'ALL_DENIED' | 'SOME_ACCEPTED' | 'SOME_DENIED'
2325
required: boolean
26+
language: string
27+
setting: {
28+
id: string
29+
type: string
30+
version: string
31+
}
2432
}
2533

2634
/**
@@ -72,9 +80,19 @@ type UC_CMP = {
7280
* @see https://usercentrics.com/docs/web/features/api/control-functionality/#updateservicesconsents
7381
*
7482
* @example updateServicesConsents([{ id: 'my-service-id', consent: true }])
83+
*
84+
* @warn Updating consents doesn't save them! Remember to also call `saveConsents`.
7585
*/
7686
updateServicesConsents: (servicesConsents: { id: ServiceId; consent: boolean }[]) => Promise<void>
7787

88+
/**
89+
* Saves the consents after being updated.
90+
* @see https://usercentrics.com/docs/web/features/api/control-functionality/#saveconsents
91+
*
92+
* @example saveConsents()
93+
*/
94+
saveConsents: () => Promise<void>
95+
7896
/**
7997
* Retrieves all the consent details
8098
*
@@ -134,3 +152,17 @@ export type UCUIVIewChanged = CustomEvent<{
134152
view: UCUIView
135153
previousView: UCUIView
136154
}>
155+
156+
/**
157+
* The i18n localization content for the Web CMP. Unused values are left out.
158+
*/
159+
export type CMPi18nContent = {
160+
services: Record<
161+
ServiceId,
162+
{
163+
id: ServiceId
164+
name: string
165+
description: string
166+
}
167+
>
168+
}

‎src/utils.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ServiceId, UCDataFromLocalStorage, UCWindow } from './types.js'
1+
import type { CMPi18nContent, ServiceId, UCDataFromLocalStorage, UCWindow } from './types.js'
22

33
/**
44
* A method to check if user has interacted with the consent prompt and given consent information.
@@ -95,13 +95,25 @@ export const getConsentsFromLocalStorage = (): UCDataFromLocalStorage['consent']
9595
* @see https://usercentrics.com/docs/web/features/api/control-functionality/#updateservicesconsents
9696
*
9797
* @example updateServicesConsents([{ id: 'my-service-id', consent: true }])
98+
*
99+
* @warn Updating consents doesn't save them! Remember to also call `saveConsents`.
98100
*/
99101
export const updateServicesConsents = async (
100102
servicesConsents: { id: ServiceId; consent: boolean }[],
101103
): Promise<void> => {
102104
await (window as UCWindow).__ucCmp?.updateServicesConsents(servicesConsents)
103105
}
104106

107+
/**
108+
* Saves the consents after being updated.
109+
* @see https://usercentrics.com/docs/web/features/api/control-functionality/#saveconsents
110+
*
111+
* @example saveConsents()
112+
*/
113+
export async function saveConsents(): Promise<void> {
114+
await (window as UCWindow).__ucCmp?.saveConsents()
115+
}
116+
105117
/**
106118
* Programmatic way to set language for the CMP.
107119
* @param countryCode Two character country code, e.g. "en" = set language to English
@@ -123,3 +135,35 @@ export const isOpen = (): boolean => {
123135

124136
return false
125137
}
138+
139+
/**
140+
* Programmatic way to get the translated i18n content of the Web CMP modal.
141+
* Useful for rendering custom UI with like listing services' names and descriptions.
142+
* @param [countryCode] Two character country code, e.g. "en" = set language to English; defaults to configured language
143+
*
144+
* @example
145+
* const services = await getServiceInfo()
146+
* const { name, description } = services['my-service-id']
147+
*/
148+
export const getServiceInfo = async (countryCode?: string): Promise<CMPi18nContent['services']> => {
149+
const consentDetails = await (window as UCWindow).__ucCmp!.getConsentDetails()
150+
const language = countryCode ?? consentDetails.consent.language
151+
const { id, type, version } = consentDetails.consent.setting
152+
153+
const response = await window.fetch(
154+
`https://v1.api.service.cmp.usercentrics.eu/latest/i18n/${language}/${type}/${id}/${version}`,
155+
)
156+
157+
const i18n = (await response.json()) as CMPi18nContent
158+
return Object.values(i18n.services).reduce(
159+
(acc, service) => ({
160+
...acc,
161+
[service.id]: {
162+
id: service.id,
163+
name: service.name,
164+
description: service.description,
165+
},
166+
}),
167+
{} as CMPi18nContent['services'],
168+
)
169+
}

‎tests/utils.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
import type { UCWindow } from '../src/types.js'
22
import * as utils from '../src/utils.js'
33

4+
const TEST_SERVICE_INFO = {
5+
'test-id': {
6+
name: 'Test Service',
7+
id: 'test-id',
8+
description: 'This is a mocked test service',
9+
},
10+
}
11+
412
describe('Usercentrics', () => {
513
beforeAll(() => {
614
;(window as UCWindow).__ucCmp = {
715
changeLanguage: jest.fn(),
816
getConsentDetails: jest.fn(),
17+
saveConsents: jest.fn(),
918
showFirstLayer: jest.fn(),
1019
showSecondLayer: jest.fn(),
1120
showServiceDetails: jest.fn(),
1221
updateServicesConsents: jest.fn(),
1322
}
23+
24+
global.fetch = jest.fn().mockReturnValue({
25+
json: jest.fn().mockReturnValue({
26+
services: TEST_SERVICE_INFO,
27+
}),
28+
})
1429
})
1530

1631
afterEach(() => {
@@ -29,6 +44,11 @@ describe('Usercentrics', () => {
2944
])
3045
})
3146

47+
test('saveConsents', async () => {
48+
await utils.saveConsents()
49+
expect((window as UCWindow).__ucCmp?.saveConsents).toHaveBeenCalledWith()
50+
})
51+
3252
test('showFirstLayer', async () => {
3353
await utils.showFirstLayer()
3454
expect((window as UCWindow).__ucCmp?.showFirstLayer).toHaveBeenCalledTimes(1)
@@ -72,5 +92,31 @@ describe('Usercentrics', () => {
7292
expect(utils.getConsentsFromLocalStorage()).toEqual(ucData.consent.services)
7393
})
7494
})
95+
96+
describe('getServiceInfo', () => {
97+
it('should call upstream API with configured values', async () => {
98+
jest.mocked((window as UCWindow).__ucCmp?.getConsentDetails)?.mockResolvedValueOnce({
99+
consent: {
100+
language: 'fi',
101+
required: false,
102+
status: 'ALL_DENIED',
103+
setting: {
104+
id: 'mock-id',
105+
type: 'GDPR',
106+
version: '1.2.3',
107+
},
108+
},
109+
services: {},
110+
})
111+
112+
const serviceInfo = await utils.getServiceInfo()
113+
114+
expect(fetch).toHaveBeenCalledWith(
115+
'https://v1.api.service.cmp.usercentrics.eu/latest/i18n/fi/GDPR/mock-id/1.2.3',
116+
)
117+
118+
expect(serviceInfo).toEqual(TEST_SERVICE_INFO)
119+
})
120+
})
75121
})
76122
})

0 commit comments

Comments
 (0)
Please sign in to comment.