@learnorama/mozaik-sdk 0.14.1-staging.51.1 → 0.15.0-staging.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CUSTOMER-ADMIN.md +101 -0
- package/CVS-PLAYBACK.md +135 -0
- package/PRESS.md +208 -0
- package/README.md +25 -11
- package/TV-LOGIN.md +137 -0
- package/dist/api/account.d.ts +1 -1
- package/dist/api/account.js.map +1 -1
- package/dist/api/admin-catalogue.d.ts +13 -1
- package/dist/api/admin-catalogue.js +53 -1
- package/dist/api/admin-catalogue.js.map +1 -1
- package/dist/api/admin-consumption.d.ts +1 -1
- package/dist/api/admin-consumption.js.map +1 -1
- package/dist/api/admin-contracts.d.ts +1 -1
- package/dist/api/admin-contracts.js.map +1 -1
- package/dist/api/admin-customer.d.ts +79 -0
- package/dist/api/admin-customer.js +213 -0
- package/dist/api/admin-customer.js.map +1 -0
- package/dist/api/admin-desk.d.ts +1 -1
- package/dist/api/admin-desk.js.map +1 -1
- package/dist/api/admin-licenses.js.map +1 -1
- package/dist/api/admin-partner-selections.d.ts +1 -1
- package/dist/api/admin-partner-selections.js.map +1 -1
- package/dist/api/admin-patrons.d.ts +1 -1
- package/dist/api/admin-patrons.js.map +1 -1
- package/dist/api/admin-reviews.d.ts +1 -1
- package/dist/api/admin-reviews.js.map +1 -1
- package/dist/api/admin-roles.d.ts +1 -1
- package/dist/api/admin-roles.js.map +1 -1
- package/dist/api/admin-support.d.ts +1 -1
- package/dist/api/admin-support.js.map +1 -1
- package/dist/api/admin-tenants.js.map +1 -1
- package/dist/api/admin-tutoring.d.ts +1 -1
- package/dist/api/admin-tutoring.js.map +1 -1
- package/dist/api/auth-device.d.ts +1 -1
- package/dist/api/auth-device.js.map +1 -1
- package/dist/api/auth.d.ts +1 -1
- package/dist/api/auth.js.map +1 -1
- package/dist/api/biblio-billing-webhooks.d.ts +1 -1
- package/dist/api/biblio-billing-webhooks.js.map +1 -1
- package/dist/api/biblio-billing.d.ts +1 -1
- package/dist/api/biblio-billing.js.map +1 -1
- package/dist/api/biblio-catalogue.d.ts +1 -1
- package/dist/api/biblio-catalogue.js.map +1 -1
- package/dist/api/biblio-home.d.ts +1 -1
- package/dist/api/biblio-home.js.map +1 -1
- package/dist/api/biblio-learning-author-profiles.d.ts +1 -1
- package/dist/api/biblio-learning-author-profiles.js.map +1 -1
- package/dist/api/biblio-learning-authoring.d.ts +1 -1
- package/dist/api/biblio-learning-authoring.js.map +1 -1
- package/dist/api/biblio-learning-editorial.d.ts +1 -1
- package/dist/api/biblio-learning-editorial.js.map +1 -1
- package/dist/api/biblio-learning-feedback.d.ts +1 -1
- package/dist/api/biblio-learning-feedback.js.map +1 -1
- package/dist/api/biblio-learning-finance.d.ts +1 -1
- package/dist/api/biblio-learning-finance.js.map +1 -1
- package/dist/api/biblio-learning-integrations.d.ts +1 -1
- package/dist/api/biblio-learning-integrations.js.map +1 -1
- package/dist/api/biblio-learning-membership.d.ts +1 -1
- package/dist/api/biblio-learning-membership.js.map +1 -1
- package/dist/api/biblio-learning.d.ts +1 -1
- package/dist/api/biblio-learning.js.map +1 -1
- package/dist/api/biblio-library.d.ts +13 -1
- package/dist/api/biblio-library.js +25 -1
- package/dist/api/biblio-library.js.map +1 -1
- package/dist/api/biblio-nextory.d.ts +1 -1
- package/dist/api/biblio-nextory.js.map +1 -1
- package/dist/api/biblio-press.d.ts +45 -1
- package/dist/api/biblio-press.js +125 -1
- package/dist/api/biblio-press.js.map +1 -1
- package/dist/api/biblio-search.js.map +1 -1
- package/dist/api/biblio-telemetry.js.map +1 -1
- package/dist/api/biblio-youscribe.d.ts +51 -0
- package/dist/api/biblio-youscribe.js +66 -0
- package/dist/api/biblio-youscribe.js.map +1 -0
- package/dist/api/consumption.d.ts +4 -4
- package/dist/api/consumption.js +3 -3
- package/dist/api/consumption.js.map +1 -1
- package/dist/api/cvs-app.d.ts +1 -1
- package/dist/api/cvs-app.js.map +1 -1
- package/dist/api/cvs-documents.d.ts +33 -1
- package/dist/api/cvs-documents.js +81 -1
- package/dist/api/cvs-documents.js.map +1 -1
- package/dist/api/cvs-favorites.d.ts +1 -1
- package/dist/api/cvs-favorites.js.map +1 -1
- package/dist/api/cvs-ils.d.ts +1 -1
- package/dist/api/cvs-ils.js.map +1 -1
- package/dist/api/cvs-loans.d.ts +1 -1
- package/dist/api/cvs-loans.js.map +1 -1
- package/dist/api/cvs-payments.d.ts +1 -1
- package/dist/api/cvs-payments.js.map +1 -1
- package/dist/api/cvs-streaming.d.ts +11 -0
- package/dist/api/cvs-streaming.js +19 -1
- package/dist/api/cvs-streaming.js.map +1 -1
- package/dist/api/index.d.ts +11 -8
- package/dist/api/index.js +11 -8
- package/dist/api/partner-catalogue.d.ts +21 -1
- package/dist/api/partner-catalogue.js +28 -1
- package/dist/api/partner-catalogue.js.map +1 -1
- package/dist/api/partner-reader-nextory.d.ts +52 -0
- package/dist/api/partner-reader-nextory.js +92 -0
- package/dist/api/partner-reader-nextory.js.map +1 -0
- package/dist/api/partner-reader-youscribe.d.ts +63 -0
- package/dist/api/partner-reader-youscribe.js +97 -0
- package/dist/api/partner-reader-youscribe.js.map +1 -0
- package/dist/api/profiles.d.ts +1 -1
- package/dist/api/profiles.js.map +1 -1
- package/dist/api/search.d.ts +6 -1
- package/dist/api/search.js +10 -1
- package/dist/api/search.js.map +1 -1
- package/dist/api/tutoring-catalogue.d.ts +1 -1
- package/dist/api/tutoring-catalogue.js.map +1 -1
- package/dist/api/tutoring-chat.d.ts +1 -1
- package/dist/api/tutoring-chat.js.map +1 -1
- package/dist/api/tutoring-dispatch.d.ts +1 -1
- package/dist/api/tutoring-dispatch.js.map +1 -1
- package/dist/api/tutoring-practitioner.js.map +1 -1
- package/dist/api/tutoring-requests.d.ts +1 -1
- package/dist/api/tutoring-requests.js.map +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/chunks/client2.d.ts +1 -1
- package/dist/chunks/connection.d.ts +1 -1
- package/dist/chunks/index.d.ts +1506 -560
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +2 -2
- package/dist/models.js +157 -2
- package/dist/models.js.map +1 -1
- package/dist/react/account.d.ts +1 -1
- package/dist/react/account.js +1 -1
- package/dist/react/account.js.map +1 -1
- package/dist/react/admin-catalogue.d.ts +51 -1
- package/dist/react/admin-catalogue.js +110 -2
- package/dist/react/admin-catalogue.js.map +1 -1
- package/dist/react/admin-consumption.d.ts +1 -1
- package/dist/react/admin-consumption.js +1 -1
- package/dist/react/admin-consumption.js.map +1 -1
- package/dist/react/admin-contracts.d.ts +1 -1
- package/dist/react/admin-contracts.js +1 -1
- package/dist/react/admin-contracts.js.map +1 -1
- package/dist/react/admin-customer.d.ts +347 -0
- package/dist/react/admin-customer.js +545 -0
- package/dist/react/admin-customer.js.map +1 -0
- package/dist/react/admin-desk.d.ts +1 -1
- package/dist/react/admin-desk.js +1 -1
- package/dist/react/admin-desk.js.map +1 -1
- package/dist/react/admin-licenses.d.ts +1 -1
- package/dist/react/admin-licenses.js +1 -1
- package/dist/react/admin-licenses.js.map +1 -1
- package/dist/react/admin-partner-selections.d.ts +1 -1
- package/dist/react/admin-partner-selections.js +1 -1
- package/dist/react/admin-partner-selections.js.map +1 -1
- package/dist/react/admin-patrons.d.ts +1 -1
- package/dist/react/admin-patrons.js +1 -1
- package/dist/react/admin-patrons.js.map +1 -1
- package/dist/react/admin-reviews.d.ts +1 -1
- package/dist/react/admin-reviews.js +1 -1
- package/dist/react/admin-reviews.js.map +1 -1
- package/dist/react/admin-roles.d.ts +1 -1
- package/dist/react/admin-roles.js +1 -1
- package/dist/react/admin-roles.js.map +1 -1
- package/dist/react/admin-support.d.ts +1 -1
- package/dist/react/admin-support.js +1 -1
- package/dist/react/admin-support.js.map +1 -1
- package/dist/react/admin-tenants.d.ts +1 -1
- package/dist/react/admin-tenants.js +1 -1
- package/dist/react/admin-tenants.js.map +1 -1
- package/dist/react/admin-tutoring.d.ts +1 -1
- package/dist/react/admin-tutoring.js +1 -1
- package/dist/react/admin-tutoring.js.map +1 -1
- package/dist/react/auth-device.d.ts +1 -1
- package/dist/react/auth-device.js +1 -1
- package/dist/react/auth-device.js.map +1 -1
- package/dist/react/auth.d.ts +1 -1
- package/dist/react/auth.js +1 -1
- package/dist/react/auth.js.map +1 -1
- package/dist/react/biblio-billing-webhooks.d.ts +1 -1
- package/dist/react/biblio-billing-webhooks.js +1 -1
- package/dist/react/biblio-billing-webhooks.js.map +1 -1
- package/dist/react/biblio-billing.d.ts +1 -1
- package/dist/react/biblio-billing.js +1 -1
- package/dist/react/biblio-billing.js.map +1 -1
- package/dist/react/biblio-catalogue.d.ts +1 -1
- package/dist/react/biblio-catalogue.js +1 -1
- package/dist/react/biblio-catalogue.js.map +1 -1
- package/dist/react/biblio-home.d.ts +1 -1
- package/dist/react/biblio-home.js +1 -1
- package/dist/react/biblio-home.js.map +1 -1
- package/dist/react/biblio-learning-author-profiles.d.ts +1 -1
- package/dist/react/biblio-learning-author-profiles.js +1 -1
- package/dist/react/biblio-learning-author-profiles.js.map +1 -1
- package/dist/react/biblio-learning-authoring.d.ts +1 -1
- package/dist/react/biblio-learning-authoring.js +1 -1
- package/dist/react/biblio-learning-authoring.js.map +1 -1
- package/dist/react/biblio-learning-editorial.d.ts +1 -1
- package/dist/react/biblio-learning-editorial.js +1 -1
- package/dist/react/biblio-learning-editorial.js.map +1 -1
- package/dist/react/biblio-learning-feedback.d.ts +1 -1
- package/dist/react/biblio-learning-feedback.js +1 -1
- package/dist/react/biblio-learning-feedback.js.map +1 -1
- package/dist/react/biblio-learning-finance.d.ts +1 -1
- package/dist/react/biblio-learning-finance.js +1 -1
- package/dist/react/biblio-learning-finance.js.map +1 -1
- package/dist/react/biblio-learning-integrations.d.ts +1 -1
- package/dist/react/biblio-learning-integrations.js +1 -1
- package/dist/react/biblio-learning-integrations.js.map +1 -1
- package/dist/react/biblio-learning-membership.d.ts +1 -1
- package/dist/react/biblio-learning-membership.js +1 -1
- package/dist/react/biblio-learning-membership.js.map +1 -1
- package/dist/react/biblio-learning.d.ts +1 -1
- package/dist/react/biblio-learning.js +1 -1
- package/dist/react/biblio-learning.js.map +1 -1
- package/dist/react/biblio-library.d.ts +30 -1
- package/dist/react/biblio-library.js +54 -2
- package/dist/react/biblio-library.js.map +1 -1
- package/dist/react/biblio-nextory.d.ts +1 -1
- package/dist/react/biblio-nextory.js +1 -1
- package/dist/react/biblio-nextory.js.map +1 -1
- package/dist/react/biblio-press.d.ts +196 -1
- package/dist/react/biblio-press.js +310 -2
- package/dist/react/biblio-press.js.map +1 -1
- package/dist/react/biblio-search.d.ts +1 -1
- package/dist/react/biblio-search.js +1 -1
- package/dist/react/biblio-search.js.map +1 -1
- package/dist/react/biblio-telemetry.d.ts +1 -1
- package/dist/react/biblio-telemetry.js +1 -1
- package/dist/react/biblio-telemetry.js.map +1 -1
- package/dist/react/biblio-youscribe.d.ts +81 -0
- package/dist/react/biblio-youscribe.js +128 -0
- package/dist/react/biblio-youscribe.js.map +1 -0
- package/dist/react/chat.d.ts +1 -1
- package/dist/react/consumption.d.ts +4 -4
- package/dist/react/consumption.js +4 -4
- package/dist/react/consumption.js.map +1 -1
- package/dist/react/cvs-app.d.ts +1 -1
- package/dist/react/cvs-app.js +1 -1
- package/dist/react/cvs-app.js.map +1 -1
- package/dist/react/cvs-documents.d.ts +141 -1
- package/dist/react/cvs-documents.js +179 -2
- package/dist/react/cvs-documents.js.map +1 -1
- package/dist/react/cvs-favorites.d.ts +1 -1
- package/dist/react/cvs-favorites.js +1 -1
- package/dist/react/cvs-favorites.js.map +1 -1
- package/dist/react/cvs-ils.d.ts +1 -1
- package/dist/react/cvs-ils.js +1 -1
- package/dist/react/cvs-ils.js.map +1 -1
- package/dist/react/cvs-loans.d.ts +1 -1
- package/dist/react/cvs-loans.js +1 -1
- package/dist/react/cvs-loans.js.map +1 -1
- package/dist/react/cvs-payments.d.ts +1 -1
- package/dist/react/cvs-payments.js +1 -1
- package/dist/react/cvs-payments.js.map +1 -1
- package/dist/react/cvs-streaming.d.ts +39 -1
- package/dist/react/cvs-streaming.js +45 -2
- package/dist/react/cvs-streaming.js.map +1 -1
- package/dist/react/index.d.ts +11 -8
- package/dist/react/index.js +11 -8
- package/dist/react/partner-catalogue.d.ts +48 -1
- package/dist/react/partner-catalogue.js +54 -2
- package/dist/react/partner-catalogue.js.map +1 -1
- package/dist/react/partner-reader-nextory.d.ts +127 -0
- package/dist/react/partner-reader-nextory.js +190 -0
- package/dist/react/partner-reader-nextory.js.map +1 -0
- package/dist/react/partner-reader-youscribe.d.ts +152 -0
- package/dist/react/partner-reader-youscribe.js +224 -0
- package/dist/react/partner-reader-youscribe.js.map +1 -0
- package/dist/react/profiles.d.ts +1 -1
- package/dist/react/profiles.js +1 -1
- package/dist/react/profiles.js.map +1 -1
- package/dist/react/search.d.ts +6 -1
- package/dist/react/search.js +11 -2
- package/dist/react/search.js.map +1 -1
- package/dist/react/tutoring-catalogue.d.ts +1 -1
- package/dist/react/tutoring-catalogue.js +1 -1
- package/dist/react/tutoring-catalogue.js.map +1 -1
- package/dist/react/tutoring-chat.d.ts +1 -1
- package/dist/react/tutoring-chat.js +1 -1
- package/dist/react/tutoring-chat.js.map +1 -1
- package/dist/react/tutoring-dispatch.d.ts +1 -1
- package/dist/react/tutoring-dispatch.js +1 -1
- package/dist/react/tutoring-dispatch.js.map +1 -1
- package/dist/react/tutoring-practitioner.d.ts +1 -1
- package/dist/react/tutoring-practitioner.js +1 -1
- package/dist/react/tutoring-practitioner.js.map +1 -1
- package/dist/react/tutoring-requests.d.ts +1 -1
- package/dist/react/tutoring-requests.js +1 -1
- package/dist/react/tutoring-requests.js.map +1 -1
- package/package.json +12 -8
- package/dist/api/partner-reader.d.ts +0 -96
- package/dist/api/partner-reader.js +0 -164
- package/dist/api/partner-reader.js.map +0 -1
- package/dist/react/partner-reader.d.ts +0 -261
- package/dist/react/partner-reader.js +0 -368
- package/dist/react/partner-reader.js.map +0 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Customer administrator integration
|
|
2
|
+
|
|
3
|
+
Use the release containing the `admin-customer` SDK module. Customer permissions
|
|
4
|
+
are separate from Mozaik staff permissions. No customer gets the internal desk
|
|
5
|
+
DTOs or unrestricted tenant selectors.
|
|
6
|
+
|
|
7
|
+
## Sign in
|
|
8
|
+
|
|
9
|
+
Use `createMozaikAuth` from `@learnorama/mozaik-sdk/auth`, with the normal
|
|
10
|
+
`prepareSso` / `completeSso` PKCE flow and a callback at `/auth/callback`.
|
|
11
|
+
The initial integration shares the `mozaik-web` public Keycloak client. The
|
|
12
|
+
operator must allow the exact frontend origin in API redirects, Keycloak and
|
|
13
|
+
CORS: local port 5175, `https://admin.stg.mozaik.co` for staging, and the production
|
|
14
|
+
origin in production only. Configure `CUSTOMER_ADMIN_ORIGIN` consistently.
|
|
15
|
+
|
|
16
|
+
Passwords and password setup stay in Keycloak. This change does not implement
|
|
17
|
+
the mockup's separate six-digit email MFA or trusted device for 30 days. The
|
|
18
|
+
realm password policy applies to all newly set/changed passwords, not just
|
|
19
|
+
customer administrators; existing passwords are not reset.
|
|
20
|
+
|
|
21
|
+
An invitation email may first complete Keycloak required actions. A callback
|
|
22
|
+
with `kc_action_status=success` **and no OAuth authorization code** is not a
|
|
23
|
+
session: start the normal PKCE login. Do not pass that status as a login code.
|
|
24
|
+
After an authenticated verified callback from the configured admin origin, the
|
|
25
|
+
backend accepts the matching pending invitation. The explicit
|
|
26
|
+
`acceptCustomerAdminInvitation()` operation also supports accepting after login.
|
|
27
|
+
|
|
28
|
+
## Read the organization and capabilities
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {
|
|
32
|
+
readCustomerOrganization, readCustomerDashboard,
|
|
33
|
+
listCustomerBeneficiaries, listCustomerAdministrators,
|
|
34
|
+
} from '@learnorama/mozaik-sdk/api/admin-customer';
|
|
35
|
+
|
|
36
|
+
const organization = await readCustomerOrganization({ client });
|
|
37
|
+
if (organization.statsOpen) {
|
|
38
|
+
const dashboard = await readCustomerDashboard({ days: 90 }, { client });
|
|
39
|
+
}
|
|
40
|
+
if (organization.enrolledVisible) {
|
|
41
|
+
const page = await listCustomerBeneficiaries({ page: 1, limit: 24 }, { client });
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Use `organization.role` (also projected in auth principal roles):
|
|
46
|
+
|
|
47
|
+
| Role | Access in this first integration |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `customer_admin_principal` | Enabled reads; invite/revoke administrators |
|
|
50
|
+
| `customer_admin_manager` | Enabled reads; no administrator or subscription management |
|
|
51
|
+
| `customer_admin_readonly` | Enabled reads; no mutations |
|
|
52
|
+
|
|
53
|
+
Beneficiary management/communication mutations are future rubrics; their absence
|
|
54
|
+
is not permission to reuse internal staff routes. All requests recheck native
|
|
55
|
+
grants, so revocation does not wait for access-token expiration. Users without
|
|
56
|
+
a customer grant receive 403. Support impersonation and child profiles cannot
|
|
57
|
+
act as customer administrators.
|
|
58
|
+
|
|
59
|
+
The home organization controls scope. A CSE cannot read descendants. A library
|
|
60
|
+
consortium may include its permitted library sites. `statsOpen` and
|
|
61
|
+
`enrolledVisible` are settings controlled by Mozaik, false until enabled;
|
|
62
|
+
administrators cannot enable their own access to personal data. The beneficiary
|
|
63
|
+
directory never exposes individual reading, tutoring or consumption history.
|
|
64
|
+
|
|
65
|
+
Use `readCustomerOrganizationOverview()` for totals and
|
|
66
|
+
`readCustomerContractUsage(contractId)` for organization-contract usage. A
|
|
67
|
+
personal contract or another customer's contract is refused. Usage units are
|
|
68
|
+
not an activation-code stock count. Missing branding/access/account-manager
|
|
69
|
+
metadata remains null. Connection metrics state their definition and tracking
|
|
70
|
+
coverage; a null previous value means no reliable comparison, not zero.
|
|
71
|
+
|
|
72
|
+
## Invite and revoke
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import {
|
|
76
|
+
inviteCustomerAdministrator, revokeCustomerAdministrator,
|
|
77
|
+
} from '@learnorama/mozaik-sdk/api/admin-customer';
|
|
78
|
+
|
|
79
|
+
const invitation = await inviteCustomerAdministrator({
|
|
80
|
+
email: 'colleague@example.org', firstName: 'Camille', lastName: 'Martin',
|
|
81
|
+
role: 'customer_admin_readonly',
|
|
82
|
+
}, { client });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
An invitation lasts seven days and acceptance consumes it once. Existing
|
|
86
|
+
verified accounts keep their password; new accounts complete Keycloak email
|
|
87
|
+
verification/password setup. The local pending account grants no patron or
|
|
88
|
+
administrative rights before acceptance. A person already attached to another
|
|
89
|
+
organization requires staff reconciliation; email matching never moves them.
|
|
90
|
+
|
|
91
|
+
Only an existing principal can invite from the customer app. Mozaik staff use
|
|
92
|
+
`inviteFirstCustomerAdministrator(tenantId, body)` under their existing scope
|
|
93
|
+
and high-assurance authentication; the first role must be principal.
|
|
94
|
+
`listCustomerAdministrators` includes active and pending/expired invitations.
|
|
95
|
+
Use `revokeCustomerAdministrator(userId)` for active grants and
|
|
96
|
+
`revokeCustomerAdministratorInvitation(invitationId)` for pending invitations.
|
|
97
|
+
The last active principal is protected. Revocation removes the administrative
|
|
98
|
+
grant, not an existing beneficiary's personal account.
|
|
99
|
+
|
|
100
|
+
No existing legacy customer administrators are automatically granted access.
|
|
101
|
+
A release still needs explicit organization settings and first invitations.
|
package/CVS-PLAYBACK.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Cinema integration with the SDK
|
|
2
|
+
|
|
3
|
+
This guide targets the frontend-request update prepared on 27 October 2026.
|
|
4
|
+
Use the SDK from the release containing these operations; this document does
|
|
5
|
+
not mean the pending backend changes have already reached staging.
|
|
6
|
+
|
|
7
|
+
## Browse and quote
|
|
8
|
+
|
|
9
|
+
Reuse the authenticated SDK client and active profile headers. Show Cinema
|
|
10
|
+
when `readCapabilities().cinema.catalogueAllowed` is true. Its `homeUrl`,
|
|
11
|
+
`catalogueUrl` and `accessUrl` correspond to these typed operations:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
readCinemaHome, listCinemaFilms, readCinemaAccess,
|
|
16
|
+
} from '@learnorama/mozaik-sdk/api/cvs-documents';
|
|
17
|
+
|
|
18
|
+
const home = await readCinemaHome({ limit: 8 }, { client });
|
|
19
|
+
const films = await listCinemaFilms({ page: 1, limit: 24, sort: 'newest' }, { client });
|
|
20
|
+
const film = films.data[0];
|
|
21
|
+
const quote = film && await readCinemaAccess({ docId: film.id }, { client });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Cards include `genres`, `durationSeconds`, `directors`, and `coverUrl` when known.
|
|
25
|
+
Null metadata is unknown. Follow each home row's `documentsUrl` for subsequent
|
|
26
|
+
pages; keep its filters. `listCvsSelectionDocuments` retrieves an owned CVS
|
|
27
|
+
list in recorded order. Search remains available separately; do not fetch the
|
|
28
|
+
whole inventory to classify it on the frontend.
|
|
29
|
+
|
|
30
|
+
`readCinemaAccess()` without `docId` is an overview. Several allowances must
|
|
31
|
+
**not be added together**: they can apply to different films. An overview with
|
|
32
|
+
more than 100 allowances sets `balancesComplete=false`. A film-specific quote
|
|
33
|
+
resolves the actual act, cost and allowance:
|
|
34
|
+
|
|
35
|
+
- `credits.left`, `total`, `until`, `grantedBy`, `contractId`, `contractReference`
|
|
36
|
+
describe the selected grant. `unlimited=true` has null amounts, not zero.
|
|
37
|
+
- `scope` says whether the allowance belongs to a profile, household or tenant.
|
|
38
|
+
- `allowed` and `reason` describe the current decision; this is not a reservation.
|
|
39
|
+
- `unitCost` is the actual consumption policy, currently one unit for a new act,
|
|
40
|
+
zero when starting playback under an existing valid loan. It is not the
|
|
41
|
+
document's catalogue price or legacy `unitCount`.
|
|
42
|
+
- `act`, `loanHours`, `validUntil`, and `actionUrl` distinguish direct viewing
|
|
43
|
+
from borrowing and from an already-held loan.
|
|
44
|
+
|
|
45
|
+
After borrowing or starting playback, invalidate/refetch `/me/cinema` for the
|
|
46
|
+
film and the overview. Never decrement a simulated balance. The mutation checks
|
|
47
|
+
rights and quota again, so a concurrent household spend may still return 409.
|
|
48
|
+
|
|
49
|
+
## Explicit Play action
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { borrow } from '@learnorama/mozaik-sdk/api/cvs-loans';
|
|
53
|
+
import { startSession, readDashManifest } from '@learnorama/mozaik-sdk/api/cvs-streaming';
|
|
54
|
+
import { renewLease, closeLease } from '@learnorama/mozaik-sdk/api/consumption';
|
|
55
|
+
|
|
56
|
+
// Run only after the user presses Play, with a fresh quote for this docId.
|
|
57
|
+
if (!quote?.allowed) throw new Error('This film is not currently available');
|
|
58
|
+
if (quote.actionUrl === '/cvs/loans') {
|
|
59
|
+
if (quote.act !== 'loan_view' && quote.act !== 'loan_download') {
|
|
60
|
+
throw new Error('Unexpected loan contract');
|
|
61
|
+
}
|
|
62
|
+
await borrow({ workId: film.id, act: quote.act, requestKey: crypto.randomUUID() }, { client });
|
|
63
|
+
}
|
|
64
|
+
const playback = await startSession({ docId: film.id }, {
|
|
65
|
+
client,
|
|
66
|
+
headers: {
|
|
67
|
+
'Idempotency-Key': crypto.randomUUID(),
|
|
68
|
+
'X-Client-Platform': 'web',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
await readCinemaAccess({ docId: film.id }, { client });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Retain each request key for an intentional retry of that same action. Borrow
|
|
75
|
+
and playback need different keys. Do not prefetch these mutations or blindly
|
|
76
|
+
retry them: opening DRM is an external operation. `streamingDetails(docId)` is
|
|
77
|
+
a read-only readiness check and returns no licence or lease.
|
|
78
|
+
|
|
79
|
+
Use `web` for Tizen/webOS, `android` for Android TV, `ios` for tvOS. These are
|
|
80
|
+
DRM/OS families; there is no fourth DRM family named `tv`.
|
|
81
|
+
|
|
82
|
+
## Player and DRM
|
|
83
|
+
|
|
84
|
+
| Response field | Meaning |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `streamUrl` / `drm.hls` | HLS manifest |
|
|
87
|
+
| `drm.dash` | Direct CDN MPEG-DASH manifest |
|
|
88
|
+
| `drm.normalizedDashUrl` | Optional authenticated backend MPD normalization route |
|
|
89
|
+
| `drm.widevine`, `drm.fairplay` | Licence acquisition URLs |
|
|
90
|
+
| `drm.certificate` | URL of public DER FairPlay application-certificate bytes |
|
|
91
|
+
| `drm.token` | Provider licence token; not the Mozaik bearer |
|
|
92
|
+
| `drm.kid` | Content key identifier; not a raw decryption key |
|
|
93
|
+
| `drm.contentId` | Provider media identity; not the CVS document ID |
|
|
94
|
+
| `lease.eventId`, `lease.expiresAt` | Playback heartbeat handle and deadline |
|
|
95
|
+
|
|
96
|
+
The SDK does not implement a player/CDM. Configure the player with its supported
|
|
97
|
+
provider protocol; its licence challenge returns the protected key material.
|
|
98
|
+
Fetch the public FairPlay certificate as bytes, not as a JSON or base64 field.
|
|
99
|
+
The configured legacy certificate is reachable but actual Safari/iOS playback
|
|
100
|
+
still needs device validation. Its embedded X.509 expiration alone was not used
|
|
101
|
+
to claim successful or failed FairPlay decryption.
|
|
102
|
+
|
|
103
|
+
For a DASH player affected by self-referencing `dependencyId`, fetch the
|
|
104
|
+
normalized MPD with the SDK, then pass a Blob URL to the player:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const xml = await readDashManifest(film.id, { client });
|
|
108
|
+
const manifestUrl = URL.createObjectURL(new Blob([xml], { type: 'application/dash+xml' }));
|
|
109
|
+
// await player.load(manifestUrl);
|
|
110
|
+
// Revoke after player unload: URL.revokeObjectURL(manifestUrl);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The backend removes only a representation's dependency on itself. Other
|
|
114
|
+
dependencies remain; segment BaseURL resolves to the original CDN. The endpoint
|
|
115
|
+
is bounded and read-only, and does not grant playback rights by itself. Do not
|
|
116
|
+
send API bearer/profile headers to CDN, certificate or licence hosts.
|
|
117
|
+
|
|
118
|
+
## Heartbeat and stop
|
|
119
|
+
|
|
120
|
+
Call `renewLease(playback.lease.eventId, { client })` while playing, typically
|
|
121
|
+
every 30 seconds. Honor its returned deadline; a 404 means the lease is gone.
|
|
122
|
+
Call `closeLease` on stop/unmount/player failure. JSON carries the handle; no
|
|
123
|
+
`Location` header is needed. The durable database expiry survives Redis loss
|
|
124
|
+
and is the same deadline used by the reaper.
|
|
125
|
+
|
|
126
|
+
Playback has its own event, linked to a fixed-term loan where applicable.
|
|
127
|
+
Closing playback does not return the loan; renewing playback cannot extend the
|
|
128
|
+
loan's contractual deadline. Returning a loan releases a seat without refunding
|
|
129
|
+
spent units. Never substitute a loan ID, docId, or key ID for the playback event.
|
|
130
|
+
|
|
131
|
+
Handle 401 through SSO, 403 as a profile restriction, 404 as unavailable/unready,
|
|
132
|
+
409 as exhausted quota or no seat, 429 using Retry-After, and 503 as provider
|
|
133
|
+
unavailability. Verify video **and audio**, licence challenge, several heartbeats,
|
|
134
|
+
and stop on each target platform after release. Provider-stubbed tests prove the
|
|
135
|
+
backend contract, not device/CDM interoperability.
|
package/PRESS.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Press integration
|
|
2
|
+
|
|
3
|
+
Use the SDK generated from the release containing these routes. Publications,
|
|
4
|
+
dated editions and articles have distinct identities:
|
|
5
|
+
|
|
6
|
+
| Object | Identity | Listing |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| Publication / newspaper | `contentId`, plus provider `publicationId` when applicable | `listPressTitles` |
|
|
9
|
+
| Dated PressReader edition | publication + `issueDate` | `readIssues(contentId)` |
|
|
10
|
+
| Article | native `articleId`; separate `providerArticleId` | `listPressArticles`, `readPressReview` |
|
|
11
|
+
| Newspaper voucher offer | `lemonde`, `lequipe`, `lefigaro` | `listPressPartnerAccess` |
|
|
12
|
+
|
|
13
|
+
`readCapabilities()` supplies `biblio.pressArticlesUrl`, `pressReviewUrl` and
|
|
14
|
+
`pressPartnersUrl`. Provider cards identify their catalogue and reader action.
|
|
15
|
+
Use the same authenticated effective profile throughout. The backend filters
|
|
16
|
+
rights and age restrictions before counts, deduplication and pagination.
|
|
17
|
+
|
|
18
|
+
## Article review
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { request } from '@learnorama/mozaik-sdk';
|
|
22
|
+
import {
|
|
23
|
+
readPressReview, listPressArticles, openPressArticleReader,
|
|
24
|
+
} from '@learnorama/mozaik-sdk/api/biblio-press';
|
|
25
|
+
import type { PressArticlePage } from '@learnorama/mozaik-sdk/models';
|
|
26
|
+
import { renewLease, closeLease } from '@learnorama/mozaik-sdk/api/consumption';
|
|
27
|
+
|
|
28
|
+
const review = await readPressReview({ limit: 6 }, { client });
|
|
29
|
+
const first = review.sections[0];
|
|
30
|
+
if (first && first.articles.meta.totalPages > 1) {
|
|
31
|
+
// Keep the server's selection, date window and page-size filters intact.
|
|
32
|
+
const url = new URL(first.articlesUrl, client.baseUrl);
|
|
33
|
+
url.searchParams.set('page', '2');
|
|
34
|
+
const next = await request<PressArticlePage>(url.pathname + url.search, { client });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const page = await listPressArticles({
|
|
38
|
+
publicationId: '2526', page: 1, limit: 24,
|
|
39
|
+
}, { client });
|
|
40
|
+
|
|
41
|
+
// Only after the reader presses Open. Do not prefetch this mutation.
|
|
42
|
+
const article = page.data[0];
|
|
43
|
+
if (article) {
|
|
44
|
+
const opened = await openPressArticleReader(article.articleId, {
|
|
45
|
+
client, headers: { 'Idempotency-Key': crypto.randomUUID() },
|
|
46
|
+
});
|
|
47
|
+
// Open opened.url in the licensed reader. Never log its token-bearing URL.
|
|
48
|
+
// Renew while actually reading, and close when the reader exits:
|
|
49
|
+
await renewLease(opened.lease.eventId, { client });
|
|
50
|
+
await closeLease(opened.lease.eventId, { client });
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`/press-feed` remains a feed of publication cards. `/press-review` contains real
|
|
55
|
+
article cards and uses publication favourites already saved with
|
|
56
|
+
`mode=press_title`. It offers selected headlines and related themes; each
|
|
57
|
+
section includes its own page and continuation URL. Review selection applies
|
|
58
|
+
the editorial rules and a maximum of four articles per publication before
|
|
59
|
+
pagination. Monthly and other long-period editions remain eligible for their
|
|
60
|
+
recorded editorial lifetime. An ordinary article query defaults to seven days;
|
|
61
|
+
explicit `since`/`until` queries can cover up to 366 days.
|
|
62
|
+
|
|
63
|
+
Article metadata is local: GET requests do not contact PressReader or grant
|
|
64
|
+
reader access. `publishedAt` can be null because an issue date is not an article
|
|
65
|
+
publication timestamp. There is no full-text body endpoint; the licensed reader
|
|
66
|
+
opens the stored provider article ID and issue date. Empty review sections are
|
|
67
|
+
omitted. Invalidate the review after changing publication favourites and clear
|
|
68
|
+
profile-scoped caches when switching profiles.
|
|
69
|
+
|
|
70
|
+
## Publisher codes
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import {
|
|
74
|
+
readPressPartnerAccess, claimPressPartnerCode,
|
|
75
|
+
} from '@learnorama/mozaik-sdk/api/biblio-press';
|
|
76
|
+
|
|
77
|
+
const state = await readPressPartnerAccess('lemonde', { client });
|
|
78
|
+
// Explicit user action only; GET/capabilities/onboarding never claim a code.
|
|
79
|
+
if (state.canRequestCode) {
|
|
80
|
+
const access = await claimPressPartnerCode('lemonde', { client });
|
|
81
|
+
// Show access.code and access.activationUrl privately to this household.
|
|
82
|
+
// Never log, persist in analytics, or automatically visit the activation URL.
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The same household reuses its existing eligible code across devices and adult
|
|
87
|
+
profiles. Concurrent requests cannot spend two codes for that household. Support
|
|
88
|
+
impersonation cannot reveal or allocate codes. A generic press category does
|
|
89
|
+
not override provider exclusions; contracts can permit each newspaper separately.
|
|
90
|
+
|
|
91
|
+
Allocation is not activation. `activatedOn`, `benefitEndsOn` and
|
|
92
|
+
`renewalAvailableOn` stay null until a publisher report confirms the date.
|
|
93
|
+
The legacy policies are Le Monde three calendar months; L’Équipe thirty days
|
|
94
|
+
with renewal after twenty-eight (older banks used three months); Le Figaro one
|
|
95
|
+
calendar month. Actual bank policy is explicit in the response.
|
|
96
|
+
|
|
97
|
+
Handle 404 as unavailable to this profile, 409 as no currently usable stock,
|
|
98
|
+
and 503 as unavailable configuration. Refetch status after a claim. A code claim
|
|
99
|
+
records an allocation audit, not a newspaper-reading session.
|
|
100
|
+
|
|
101
|
+
## Operator setup
|
|
102
|
+
|
|
103
|
+
Global catalogue administrators with high-assurance authentication use the
|
|
104
|
+
generated `admin-catalogue` functions for offer/title bindings, bank policies,
|
|
105
|
+
code import, disablement and publisher activation reports. Imports are bounded
|
|
106
|
+
to 500 codes/report entries and are atomic. Same-bank retries are idempotent;
|
|
107
|
+
conflicting activation dates need an explicit correction reason. Lists and audit
|
|
108
|
+
records never contain code values.
|
|
109
|
+
|
|
110
|
+
Article refresh requires `PRESSREADER_DISCOVERY_ENABLED`,
|
|
111
|
+
`PRESSREADER_DISCOVERY_URL` and the private `PRESSREADER_DISCOVERY_KEY`.
|
|
112
|
+
The worker and minute dispatcher process one bounded page per task, retaining
|
|
113
|
+
previous article data on failures. Global administrators can enqueue a refresh
|
|
114
|
+
with a unique request key and inspect progress through
|
|
115
|
+
`/admin/press/article-refreshes`.
|
|
116
|
+
|
|
117
|
+
Voucher encryption uses the existing `COMMERCE_SECRET_KEY`; keep it stable.
|
|
118
|
+
Stock and confirmed-activation reports must be supplied separately. Importing
|
|
119
|
+
legacy records does not enable an offer or allocate any new code. Staging exports
|
|
120
|
+
omit real voucher stock; create synthetic test banks explicitly for frontend work.
|
|
121
|
+
|
|
122
|
+
## Article enrichment and profile preferences
|
|
123
|
+
|
|
124
|
+
The frontend-request update adds `language`, `wordCount`, `readingMinutes`,
|
|
125
|
+
`topics`, catalogue `themes`, `read` and `lastOpenedAt` to article cards.
|
|
126
|
+
`read` means this profile opened the licensed reader, not that it finished the
|
|
127
|
+
article. `readingMinutes` is estimated at 200 words/minute **only when a full
|
|
128
|
+
article word count is known**. The sampled Discovery response has no such count;
|
|
129
|
+
null must remain absent in the UI. A short summary is not the full article.
|
|
130
|
+
|
|
131
|
+
`topics` are PressReader-supplied named entities, with stable native `topicId`
|
|
132
|
+
and counts over the accessible cached articles. They are distinct from the
|
|
133
|
+
publication's catalogue `themeId`; they are not an inferred editorial grouping
|
|
134
|
+
such as “pension reform.” `listPressTopics` defaults to issues since yesterday.
|
|
135
|
+
Counts describe imported, accessible articles, not the entire provider archive.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import {
|
|
139
|
+
listPressTopics, followPressTopic, unfollowPressTopic,
|
|
140
|
+
readPressFacets, updateReadingPreferences, acknowledgePressReview,
|
|
141
|
+
} from '@learnorama/mozaik-sdk/api/biblio-press';
|
|
142
|
+
|
|
143
|
+
const topics = await listPressTopics({ limit: 20 }, { client });
|
|
144
|
+
// On the user's explicit Follow action:
|
|
145
|
+
if (topics.data[0]) await followPressTopic(topics.data[0].topicId, { client });
|
|
146
|
+
const filtered = await listPressArticles({ topicId: 21, lang: 'en', page: 1 }, { client });
|
|
147
|
+
const facets = await readPressFacets({ client });
|
|
148
|
+
await updateReadingPreferences({ language: 'fr', regionCode: 'FR-ARA' }, { client });
|
|
149
|
+
// Only once this review was actually displayed, never on prefetch:
|
|
150
|
+
await acknowledgePressReview({ through: review.generatedAt }, { client });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Use actual IDs/codes from topics/facets in production. Regional facets require
|
|
154
|
+
explicitly mapped publications; a country or IP address does not establish a
|
|
155
|
+
publication's region. No mappings are fabricated. Language filters preserve
|
|
156
|
+
original-language headlines. GETs never advance the visit cursor or follow a
|
|
157
|
+
subject.
|
|
158
|
+
|
|
159
|
+
The section `key` is now a generated enum:
|
|
160
|
+
|
|
161
|
+
| Key | Selection |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `headlines` | Selected headlines |
|
|
164
|
+
| `favoritesHeadlines` | Headlines from followed publications, unopened first |
|
|
165
|
+
| `basedOnFavorites` | Themes related to followed publications |
|
|
166
|
+
| `theme` | Explicit catalogue theme |
|
|
167
|
+
| `focus` | Most-covered supplied subject since yesterday, with counts |
|
|
168
|
+
| `followedTopics` | Subjects followed by this profile |
|
|
169
|
+
| `shortReads` | Known full word count from 1 to 200 |
|
|
170
|
+
| `longReads` | Known full word count of at least 1,600, recent month |
|
|
171
|
+
| `regional` | Explicit publication mapping and profile's selected region |
|
|
172
|
+
| `international` | Available articles outside the selected profile language |
|
|
173
|
+
|
|
174
|
+
Render existing nonempty sections; use `features[].available` and `reason` to
|
|
175
|
+
explain unavailable functionality. There is no integrated press-video source:
|
|
176
|
+
`video` is unavailable and no video section is invented. A long article is not
|
|
177
|
+
necessarily evergreen; the backend does not claim that classification.
|
|
178
|
+
|
|
179
|
+
`updatedAt` describes cached metadata updates. `generatedAt` is the display
|
|
180
|
+
snapshot for the explicit visit acknowledgment. `newCount` counts newly ingested
|
|
181
|
+
accessible articles after the stored visit; `newSince=null` on the first visit
|
|
182
|
+
means no prior baseline. It is not a count of articles the person finished.
|
|
183
|
+
Refetch article/review queries after opening, following, changing preferences,
|
|
184
|
+
or acknowledging a displayed review. Clear caches on profile switches.
|
|
185
|
+
|
|
186
|
+
Le Monde, L’Équipe and Le Figaro voucher offers stay outside publication/edition
|
|
187
|
+
kiosk lists. Use their explicit partner access operations; do not try to open
|
|
188
|
+
PressReader issues for a voucher offer.
|
|
189
|
+
|
|
190
|
+
## Clear Biblio reading history
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { clearReadingHistory } from '@learnorama/mozaik-sdk/api/biblio-library';
|
|
194
|
+
await clearReadingHistory({ state: 'finished' }, { client });
|
|
195
|
+
// state: 'in_progress', or omit the query for all Biblio profile history.
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The mutation is idempotent (204) and uses the active authenticated profile.
|
|
199
|
+
`listHistory` accepts the same state filter and returns its page total.
|
|
200
|
+
After clearing, refetch history and home-feed. Finished means progress >= 100;
|
|
201
|
+
other previously opened entries are in progress. New reading after the clearing
|
|
202
|
+
cutoff can reappear. Progress, favourites, downloads, loans, credits and the
|
|
203
|
+
accounting ledger stay intact. Support impersonation cannot clear it.
|
|
204
|
+
|
|
205
|
+
This is profile-visible Biblio history clearing. It is not a claim that all
|
|
206
|
+
provider, cinema, search or legally retained accounting records are erased.
|
|
207
|
+
The broader privacy-screen wording must match that scope until its separate
|
|
208
|
+
retention/erasure policy is implemented.
|
package/README.md
CHANGED
|
@@ -131,17 +131,23 @@ articles or trending topics that the upstream catalogue does not contain.
|
|
|
131
131
|
`reason: "license_required"`; a configured activation can be requested when the
|
|
132
132
|
eligible adult explicitly opens the reader.
|
|
133
133
|
|
|
134
|
-
- `POST /
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
the
|
|
138
|
-
|
|
139
|
-
- `GET /partners/youscribe/user/auth`
|
|
140
|
-
an existing
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
134
|
+
- `POST /biblio/me/youscribe/license` takes the seat. Call it on the patron's
|
|
135
|
+
Read click and nowhere else — not on entering the books section, prefetch,
|
|
136
|
+
bootstrap or onboarding: a seat is a paid subscription. It is idempotent and
|
|
137
|
+
the only call that allocates one, and it answers `409` with counters when the
|
|
138
|
+
household's four are taken. `DELETE` on the same path gives a seat back.
|
|
139
|
+
- `GET /partners/youscribe/user/auth` returns `{licenseNumber, token}` for the
|
|
140
|
+
native SDK. It requires an existing seat and never creates one. There is no
|
|
141
|
+
per-title variant: a YouScribe token is issued per patron, not per title.
|
|
142
|
+
- For the hosted (web) reader, read `reader.url` from
|
|
143
|
+
`GET /partners/youscribe/media/{mediaId}`. A `null` url carries
|
|
144
|
+
`unavailable: "forbidden"` (allowed to browse, not to read) or
|
|
145
|
+
`"unreachable"` (our side failed) — they are different answers.
|
|
146
|
+
|
|
147
|
+
Call the generated `createYouscribeLicense` function, or invoke its TanStack
|
|
148
|
+
**mutation**, from the reader-opening event. `openNativeAuth` and
|
|
149
|
+
`openNativeReader` still work as in 0.13.0 but are deprecated. Never invoke
|
|
150
|
+
any of them on mount,
|
|
145
151
|
prefetch, login, onboarding, catalogue navigation, or for every household profile.
|
|
146
152
|
One activated profile keeps the same provider identity across retries and releases;
|
|
147
153
|
staging and production use different identities. No token should be persisted.
|
|
@@ -564,3 +570,11 @@ import { apiVersion, backendRevision, schemaSha256 } from '@learnorama/mozaik-sd
|
|
|
564
570
|
Generated HTTP TypeScript types describe the API contract; they do not validate
|
|
565
571
|
response bodies at runtime. The package contains the client and type declarations, not application
|
|
566
572
|
credentials or backend implementation code.
|
|
573
|
+
|
|
574
|
+
### Frontend integration guides
|
|
575
|
+
|
|
576
|
+
The published package includes [cinema browsing, quotes and playback](CVS-PLAYBACK.md),
|
|
577
|
+
[press articles, topics and publisher codes](PRESS.md),
|
|
578
|
+
[customer administrator sign-in and permissions](CUSTOMER-ADMIN.md), and
|
|
579
|
+
[TV pairing](TV-LOGIN.md). Use the guide and generated operations from the same
|
|
580
|
+
published release; an uncommitted local guide does not establish staging availability.
|
package/TV-LOGIN.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Connexion de l’application TV
|
|
2
|
+
|
|
3
|
+
La TV affiche un QR code et un code court. L’utilisateur ouvre la page web
|
|
4
|
+
d’activation sur son téléphone ou ordinateur, se connecte puis **confirme dans
|
|
5
|
+
Keycloak**. La TV reçoit ensuite une session utilisable par le SDK.
|
|
6
|
+
|
|
7
|
+
Ce document concerne les nouvelles routes et le SDK publiés avec cette modification.
|
|
8
|
+
Ils seront disponibles après la release habituelle ; aucun déploiement manuel n’est requis.
|
|
9
|
+
|
|
10
|
+
## Côté TV
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { startTvLogin } from '@learnorama/mozaik-sdk/auth';
|
|
14
|
+
import { createClient } from '@learnorama/mozaik-sdk';
|
|
15
|
+
import type { SessionRead } from '@learnorama/mozaik-sdk/models';
|
|
16
|
+
|
|
17
|
+
const login = await startTvLogin({
|
|
18
|
+
baseUrl: 'https://api.stg.mozaik.co',
|
|
19
|
+
onStatus: status => console.log(status), // pending ou slow_down, sans jeton
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Afficher :
|
|
23
|
+
// <img src={login.qrCodeDataUrl} alt="Connecter cette TV" />
|
|
24
|
+
// login.userCode, login.verificationUri et un compte à rebours vers login.expiresAt.
|
|
25
|
+
|
|
26
|
+
let session: SessionRead;
|
|
27
|
+
try {
|
|
28
|
+
session = await login.waitForSession();
|
|
29
|
+
} finally {
|
|
30
|
+
login.cancel(); // aussi à appeler si l’utilisateur quitte l’écran
|
|
31
|
+
}
|
|
32
|
+
const api = createClient({
|
|
33
|
+
baseUrl: 'https://api.stg.mozaik.co',
|
|
34
|
+
getAccessToken: () => session.access_token,
|
|
35
|
+
});
|
|
36
|
+
// session.account est le compte connecté ; api appelle les routes métier.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Le helper génère le PKCE S256, garde le vérificateur et le code privé en mémoire,
|
|
40
|
+
attend avant le premier appel et respecte les intervalles. Un `slow_down`
|
|
41
|
+
augmente durablement le délai. Les erreurs transitoires et `Retry-After` entraînent
|
|
42
|
+
un recul borné par l’expiration du code. Refus et expiration arrêtent la boucle.
|
|
43
|
+
Plusieurs appels à `waitForSession()` partagent une seule boucle.
|
|
44
|
+
|
|
45
|
+
Un nouveau QR nécessite un nouvel appel à `startTvLogin()`. Annuler l’ancien
|
|
46
|
+
avant de remplacer l’écran. `cancel()` arrête l’appairage ; il ne déconnecte pas
|
|
47
|
+
une session déjà remise à l’application.
|
|
48
|
+
|
|
49
|
+
Pour renouveler ou fermer la session, utiliser **le clientId renvoyé**, normalement
|
|
50
|
+
`mozaik-tv`, avec les méthodes existantes :
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { refreshSession, deleteSession } from '@learnorama/mozaik-sdk/api/auth';
|
|
54
|
+
|
|
55
|
+
session = await refreshSession({
|
|
56
|
+
clientId: login.clientId,
|
|
57
|
+
refresh_token: session.refresh_token!,
|
|
58
|
+
}, { client: api });
|
|
59
|
+
|
|
60
|
+
await deleteSession({
|
|
61
|
+
clientId: login.clientId,
|
|
62
|
+
refresh_token: session.refresh_token!,
|
|
63
|
+
}, { client: api });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Le store d’authentification TV doit sérialiser les renouvellements, appliquer les
|
|
67
|
+
durées `expires_in`/`refresh_expires_in`, et oublier ses jetons à la déconnexion.
|
|
68
|
+
S’il persiste des credentials, utiliser le stockage sécurisé de la plateforme.
|
|
69
|
+
Les règles de durée et de révocation SSO restent celles du realm Keycloak.
|
|
70
|
+
|
|
71
|
+
## Page web à ajouter : `/activate`
|
|
72
|
+
|
|
73
|
+
Le QR mène à `FRONTEND_HOST/activate?user_code=ABCD-EFGH`.
|
|
74
|
+
La même page sans paramètre propose la saisie manuelle du code affiché sur la TV.
|
|
75
|
+
|
|
76
|
+
1. Lire `user_code` ou laisser l’utilisateur le saisir.
|
|
77
|
+
2. S’il n’est pas connecté dans ce navigateur, utiliser **le parcours de connexion
|
|
78
|
+
déjà présent dans l’application**. Conserver `/activate?user_code=...` comme
|
|
79
|
+
`returnTo`. Cela permet aussi aux utilisateurs SSO de choisir leur connexion
|
|
80
|
+
habituelle ; ne pas les envoyer directement sur un formulaire de mot de passe.
|
|
81
|
+
3. Avec le client authentifié, appeler :
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { prepareDeviceVerification } from '@learnorama/mozaik-sdk/api/auth-device';
|
|
85
|
+
|
|
86
|
+
const { verificationUrl } = await prepareDeviceVerification({
|
|
87
|
+
userCode: userCode.trim().toUpperCase(),
|
|
88
|
+
}, { client: auth.client });
|
|
89
|
+
|
|
90
|
+
window.location.assign(verificationUrl);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Keycloak affiche la TV demandant l’accès et exige une confirmation, y compris
|
|
94
|
+
si le navigateur est déjà connecté ou a déjà approuvé une autre TV. L’utilisateur
|
|
95
|
+
peut refuser. Après confirmation, la page indique que l’on peut la fermer ; le
|
|
96
|
+
polling débloque la TV. Le front n’a pas d’endpoint d’« approbation automatique ».
|
|
97
|
+
|
|
98
|
+
Pour une connexion interne, le helper SSO existant est `prepareInternalSso` ;
|
|
99
|
+
pour un partenaire, `prepareProviderSso(alias)` ; pour un compte natif,
|
|
100
|
+
`prepareProviderSso('native')`. Réutiliser la sélection déjà faite par le front.
|
|
101
|
+
Un jeton mobile seul ne crée pas de cookie SSO dans le navigateur : le QR doit
|
|
102
|
+
passer par la page web d’activation et son parcours de connexion.
|
|
103
|
+
|
|
104
|
+
## Contrat HTTP, également utilisable hors JavaScript
|
|
105
|
+
|
|
106
|
+
| Appel | Entrée | Résultat |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `POST /auth/device-authorizations` | `codeChallenge` : SHA-256/base64url du vérificateur | QR PNG en data URL, `userCode`, liens d’activation, `deviceCode` privé, `clientId`, `expiresIn`, `interval` |
|
|
109
|
+
| `POST /auth/device-authorizations/verification` | `userCode`, bearer du compte connecté | URL de confirmation Keycloak ; cet appel n’approuve pas la TV |
|
|
110
|
+
| `POST /auth/sessions/device` | `deviceCode`, `codeVerifier` | `pending`, `slow_down`, `denied`, `expired`, ou `authorized` avec `session` |
|
|
111
|
+
| `PUT /auth/sessions` | `clientId`, `refresh_token` | Session renouvelée |
|
|
112
|
+
| `DELETE /auth/sessions` | `clientId`, `refresh_token` | Déconnexion suivant les règles SSO existantes |
|
|
113
|
+
|
|
114
|
+
Le code dure cinq minutes. `deviceCode` et `codeVerifier` ne doivent jamais
|
|
115
|
+
apparaître dans un QR, une URL, une capture de diagnostic ou un log. Le code
|
|
116
|
+
court `userCode` ne permet pas d’obtenir les jetons. Sur `slow_down`, augmenter
|
|
117
|
+
le délai courant d’au moins cinq secondes ; sur `401`, démarrer un nouvel
|
|
118
|
+
appairage ; sur `429`/`503`, respecter `Retry-After` sans dépasser l’expiration.
|
|
119
|
+
|
|
120
|
+
## Profils et sécurité
|
|
121
|
+
|
|
122
|
+
Le navigateur et les TV peuvent partager un `sid` de SSO Keycloak. Un nonce
|
|
123
|
+
signé par Keycloak et conservé au renouvellement distingue chaque appairage TV
|
|
124
|
+
dans le mécanisme existant des profils. Changer de profil sur une TV ne change
|
|
125
|
+
pas celui du téléphone ou d’une autre TV. Cela n’altère pas les règles globales
|
|
126
|
+
de révocation de la session SSO du broker.
|
|
127
|
+
|
|
128
|
+
Une session de support ou un profil dépendant ne peut pas lancer la vérification.
|
|
129
|
+
Le backend vérifie aussi le verrou enfant du navigateur avant d’enregistrer la
|
|
130
|
+
TV : appeler directement le broker ne donne pas accès à l’API avec un jeton TV
|
|
131
|
+
non enregistré. Toujours terminer via `POST /auth/sessions/device`.
|
|
132
|
+
|
|
133
|
+
La release inclut le client public `mozaik-tv`, le PKCE et son mapper de nonce
|
|
134
|
+
dans la configuration Keycloak/Helm. Aucun nouveau secret fournisseur n’est
|
|
135
|
+
nécessaire. Si `KEYCLOAK_ALLOWED_CLIENT_IDS` est surchargé explicitement dans un
|
|
136
|
+
environnement, y inclure `mozaik-tv`. `FRONTEND_HOST` doit désigner le front qui
|
|
137
|
+
sert `/activate`.
|
package/dist/api/account.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import { Il as FrontendCapabilitiesRead, Q as ServiceListRead, si as OnboardingRead, x_ as AccountDetailRead, y_ as AccountUpdateWrite } from "../chunks/index.js";
|
|
3
3
|
import { c as request } from "../chunks/client.js";
|
|
4
4
|
//#region src/api/account/account.d.ts
|
|
5
5
|
export declare const getDeleteAccountUrl: () => string;
|