Identity Providers - Cognigy Documentation
Prerequisites
- An account with the admin role in Cognigy.AI.
- The organization ID of your Cognigy.AI organization, referred to as
<organization-id>in the examples. You can copy this ID from the My Profile page by clicking > Copy Organization ID. - The API base URL of your Cognigy.AI installation, referred to as
<api-base-url>in the examples. - Your Cognigy.AI URL, referred to as
<cognigy-url>in the examples. For example, for the trial environment, this URL ishttps://trial.cognigy.ai/. - An API key for sending configuration requests to the Cognigy.AI API.
- Administrator access to the IdP tenant that you want to integrate with Cognigy.AI.
- For Okta, you need an X.509 certificate. For more information, read Okta’s documentation about app certificate use.
Limitations
- An organization can have only one SSO configuration. To replace an SSO configuration, delete it, then create another one. For more information, read Change an SSO Configuration in Cognigy.AI.
- Only Microsoft Entra ID and OneLogin support single logout for Cognigy.AI.
Create an IdP App
To configure an IdP app, follow these steps:
Auth0 - OpenID Connect
Auth0 - SAML 2.0
Microsoft Entra ID
Google
Okta
OneLogin
- Log in to the Auth0 Dashboard and select your tenant.
- In the left-side menu, go to Applications > Applications and click + Create Application.
- Enter a name, for example,
Cognigy.AI, select Regular Web Applications as the app type, and click Create. - Go to the Settings tab and copy the values from the fields in the Basic Information section. You will use them later to configure the IdP in Cognigy.AI: - Domain — used in the
idpIssuerparameter in the request payload.- Client ID — used in the
idpClientIdparameter in the request payload. - Client Secret — used in the
idpClientSecretparameter in the request payload.
- Client ID — used in the
- On the Settings tab, configure the following using the API base URL and organization ID from the Prerequisites section: - Application Login URI — enter
https://<api-base-url>/auth/oidc/callback/<organization-id>.- Allowed Callback URLs — enter
https://<api-base-url>/auth/oidc/login/callback/<organization-id>. - Allowed Logout URLs — enter
https://<api-base-url>/logout/<organization-id>. - Allowed Web Origins — enter
https://*.cognigy.ai. - Allowed Origins (CORS) — enter
https://*.cognigy.ai.
- Allowed Callback URLs — enter
1
Set Up an App
- Log in to the Auth0 Dashboard and select your tenant.
- In the left-side menu, go to Applications > Applications and click + Create Application.
- Enter a name, for example,
Cognigy.AI, select Single Page Web Applications as the app type, and click Create. - Go to the Settings tab, configure the following using the API base URL and organization ID from the Prerequisites section: - Application Login URI — enter
https://<api-base-url>/auth/saml/login/<organization-id>.- Allowed Callback URLs — enter
https://<api-base-url>/auth/oidc/login/callback/<organization-id>.
- Allowed Callback URLs — enter
- On the Addons tab, activate the SAML2 Web App add-on. Auth0 opens the add-on settings dialog. From the Usage tab, copy and save the values from the following fields for later use in the API request payload to configure the IdP in Cognigy.AI: - Issuer — used in the
idpIssuerparameter in the request payload.- Identity Provider Login URL — used in the
idpLoginEndpointparameter in the request payload. - Identity Provider Certificate — used in the
idpCertificateparameter in the request payload. Click Download Auth0 certificate. Base64-encode the certificate without line breaks. You can use the following terminal command:
- Identity Provider Login URL — used in the
PowerShell
Bash (macOS)
Bash (Linux)
[Convert]::ToBase64String([IO.File]::ReadAllBytes(".\path-to-certificate"))
```
base64 -i ./<path-to-certificate> | tr -d '\n'
```
cat ./<path-to-certificate> | base64 -w0
```
2
Map Roles
For each user which you want to apply SSO to, set `user_metadata` to map the user’s name and Cognigy.AI role. To do so, follow these steps:
1. Go to **User Management > Users**, select the user, scroll to the **Metadata** section, and paste the following JSON into the **user\_metadata** field:
{
"family_name": "
The `role` value must match a [role in Cognigy.AI](https://docs.cognigy.com/ai/administer/access/admin-center/access-control), for example, `admin` or `base_role`.
2. Go to **Actions > Library**, click **Create Action**, and configure the following:
- **Name** — enter a name.
- **Login / Post Login** — activate this option.
- In the editor, paste the following code:
exports.onExecutePostLogin = async (event, api) => { const userMetadata = event.user.user_metadata || {};
api.samlResponse.setAttribute( "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", event.user.email ); api.samlResponse.setAttribute("firstName", userMetadata.given_name || ""); api.samlResponse.setAttribute("lastName", userMetadata.family_name || ""); api.samlResponse.setAttribute("role", userMetadata.role || ""); };
3. Deploy the Action.
1
Set Up an App
1. Log in to the [Azure portal](https://portal.azure.com/) with an administrator account and navigate to **Microsoft Entra ID**.
2. In the top bar, click **\+ Add** and select **Enterprise applications**. The **Browse Microsoft Entra Gallery** page opens.
3. In the top bar, click **\+ Create your own application**.
4. Enter a name, for example, `Cognigy.AI`, select **Integrate any other app you don’t find in the gallery (Non-gallery)**, and click **Create**.
5. On the new app page, open **Single sign-on** in the left navigation and select **SAML** as the sign-on method.
6. In the **Basic SAML Configuration** section, click **Edit** and configure the following: - **Identifier (Entity ID)** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
- **Reply URL (Assertion Consumer Service URL)** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
- **Sign on URL** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
- _(Optional)_ **Logout Url** — enter `https://<cognigy-url>/slo/<organization-id>`.
7. Click **Save**.
2
Map User Attributes
1. In the **User Attributes and Claims** section, click **Edit** and confirm that the following claims are mapped to the user’s profile attributes:
| Claim name | Value |
| --- | --- |
| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | `user.mail` |
| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | `user.givenname` |
| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | `user.surname` |
Cognigy.AI uses the email claim to identify the user and to create the Cognigy.AI account on the first login.
3
Get Authentication Data
1. In the **SAML Signing Certificate** section, locate **Certificate (Base64)** and click **Download**. Save the file locally. Use this certificate as `idpCertificate` in the API request to [configure the IdP in Cognigy.AI](https://docs.cognigy.com/ai/administer/installation/identity-providers#configure-sso-in-cognigy-ai). You need to encode the certificate as base64 without newlines beforehand. To do so, use the following terminal command:
PowerShell
Bash (macOS)
Bash (Linux)
base64 -i ./
cat ./
2. In the **Set up `<application name>`** section, copy the following values to be used in the API request to [configure the IdP in Cognigy.AI](https://docs.cognigy.com/ai/administer/installation/identity-providers#configure-sso-in-cognigy-ai):
- **Login URL** — used in the `idpLoginEndpoint` parameter in the request payload.
- _(Optional)_ **Logout URL** — used in the `idpLogoutUrl` parameter in the request payload when you enable single logout.
4
Assign Roles
1. In the top bar, search for `App registrations` and select this option.
2. In the **All applications** tab, search for the app you created and select it.
3. In the left-side menu, go to **Manage > Manifest** and deactivate the default `User` and `msiam_access` roles. To do so: 1. Set `isEnabled` to `false` for the `User` and `msiam_access` roles, for example:
"appRoles": [\
{\
"allowedMemberTypes": [\
"User"\
],\
"description": "User",\
"displayName": "User",\
"id": "18d14569-c3bd-439b-9a66-3a2aee01d14f",\
"isEnabled": false,\
"origin": "Application",\
"value": null\
},\
{\
"allowedMemberTypes": [\
"User"\
],\
"description": "msiam_access",\
"displayName": "msiam_access",\
"id": "b9632174-c057-4f7e-951b-be3adc52bfe6",\
"isEnabled": false,\
"origin": "Application",\
"value": null\
}\
]
```
- In the left-side menu, go to Manage > App roles, select the User role, and click Delete on the right-side pane. Do the same for the
msiam_accessrole. - Paste the following JSON code in the
appRolesarray:
Roles Array
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Admin",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bc1",
"isEnabled": true,
"description": "The Admin role in Cognigy.AI",
"value": "admin"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "API Keys",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bc2",
"isEnabled": true,
"description": "The apiKeys role in Cognigy.AI",
"value": "apiKeys"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Base",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bc3",
"isEnabled": true,
"description": "The base role in Cognigy.AI",
"value": "base_role"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Full Support User",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bc4",
"isEnabled": true,
"description": "Admin privileges, no user assignments in Cognigy.AI",
"value": "fullSupportUser"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "OData",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bc5",
"isEnabled": true,
"description": "The OData role in Cognigy.AI",
"value": "odata"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Project Manager",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bc6",
"isEnabled": true,
"description": "The Project Manager role in Cognigy.AI",
"value": "projectManager"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "User Manager",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bc7",
"isEnabled": true,
"description": "The User Manager role in Cognigy.AI",
"value": "userManager"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Administrator",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bc8",
"isEnabled": true,
"description": "The Administrator role in Live Agent",
"value": "liveAgentAdmin"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Agent",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bc9",
"isEnabled": true,
"description": "The Agent role in Live Agent",
"value": "liveAgentAgent"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Supervisor",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bd1",
"isEnabled": true,
"description": "The Supervisor role in Live Agent",
"value": "liveagentSupervisor"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "View user details",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bd2",
"isEnabled": true,
"description": "The role to view user details in Cognigy.AI",
"value": "userDetailsViewer"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Voice Gateway User",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bd3",
"isEnabled": true,
"description": "The Account scope in Voice Gateway",
"value": "voiceGatewayUser"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Basic Support User",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bd4",
"isEnabled": true,
"description": "Partial Admin, read-only, no assignments, no OData/API, no Knowledge AI in Cognigy.AI",
"value": "basicSupportUser"
},
{
"allowedMemberTypes": [\
"User"\
],
"displayName": "Project Assigner",
"id": "8d17fe88-c0ca-4903-ae2a-a51098998bd5",
"isEnabled": true,
"description": "Assigns Agents, read-only access, no global roles, limited features in Cognigy.AI",
"value": "projectAssigner"
}
- In the left-side menu, go to Users and groups and assign the app to the users or groups that must have access to Cognigy.AI through SSO.
1
Add a Custom Attribute
You need to add a custom attribute that Cognigy.AI uses when the user logs in for the first time. To do so, follow these steps:
- Log in to the Google Admin console.
- In the left-side menu, go to Directory > Users.
- At the top of the Users list, click More options > Manage user attributes.
- Click the Add Custom Attribute button and configure the following:
- Category — enter a unique name to identify the custom attribute, for example,
Cognigy.AI SSO.- Description — enter a relevant description of the custom attribute.
- In the Custom fields section, configure three custom fields with the following values:
| Name | Info type | Visibility | Number of values | | --- | --- | --- | --- | | First Name | Text | Visible to user and admin | Single value | | Last Name | Text | Visible to user and admin | Single value | | Role | Text | Visible to user and admin | Single value |
If a user doesn’t have a role assigned, Cognigy.AI assigns base_role to the user.
- Click Add.
2
Set Up an App
- In the left-side menu, go to Apps > SAML Apps.
- Click the plus button in the lower-right corner and select Setup My Own Custom App.
- In the Google IdP Information dialog, copy the SSO URL value and download the certificate. You’ll use the SSO URL as
idpLoginEndpointand the certificate asidpCertificatein the API request to configure the IdP in Cognigy.AI. You need to encode the certificate as base64 without newlines beforehand. To do so, use the following terminal command:
PowerShell
Bash (macOS)
Bash (Linux)
[Convert]::ToBase64String([IO.File]::ReadAllBytes(".\path-to-certificate"))
base64 -i ./<path-to-certificate> | tr -d '\n'
cat ./<path-to-certificate> | base64 -w0
Click Next.
4. Enter a name for the app, for example, Cognigy.AI, and click Next.
5. On the service provider details page, configure the following:
- ACS URL — enter
https://<api-base-url>/auth/saml/login/<organization-id>.- Entity ID — enter
https://<api-base-url>/auth/saml/login/<organization-id>. - Signed Response — select the checkbox.
- Name ID Format — set to
EMAIL.
- Entity ID — enter
Click Next. 6. On the Attribute Mapping page, add the following attribute mappings so that Cognigy.AI receives the user’s first name, last name, and role from Google:
| Service Provider Attribute | Custom Attribute | Field |
|---|---|---|
firstName |
Cognigy.AI SSO | First Name |
lastName |
Cognigy.AI SSO | Last Name |
role |
Cognigy.AI SSO | Role |
- Click Finish, then activate the app for the users or organizational units that must have access to Cognigy.AI through Google SSO.
1
Set Up an App
- Log in to your Okta Admin Console with an administrator account.
- In the left-side menu, go to Applications > Applications and click Create New App.
- Select SAML 2.0 as the sign-on method and click Create.
- On the General Settings tab, enter an app name, for example,
Cognigy.AI SSO, optionally upload a logo, and click Next. - On the Configure SAML tab, configure the following: - Single sign-on URL — enter
https://<api-base-url>/auth/saml/login/<organization-id>.- Use this for Recipient URL and Destination URL — keep selected.
- Audience URI (SP Entity ID) — enter
https://<api-base-url>/auth/saml/login/<organization-id>. - Name ID format — select Unspecified.
- Application username — select Email.
- Update application username on — select Create and update.
- (Optional) To encrypt SAML requests, click Show Advanced Settings, set Assertion Encryption to Encrypted, and upload an X.509 certificate. You will need the private key of this certificate to register the IdP in Cognigy.AI.
2
Map User Attributes
- In the Attribute Statements section, add the following attributes so that Cognigy.AI receives the user profile data it needs to create the account:
| Name | Name format | Value |
|---|---|---|
email |
Unspecified | user.email |
firstName |
Unspecified | user.firstName |
lastName |
Unspecified | user.lastName |
Click Next. 2. On the Feedback tab, select I’m an Okta customer adding an internal app and click Finish.
3
Get Authentication Data
- On the Applications page, select the app you created, then open the Sign On tab.
- In the SAML Signing Certificates section, click View Setup Instructions or Identity Provider metadata and copy the following values: - Identity Provider Single Sign-On URL — used in the
idpLoginEndpointparameter in the request payload.- Identity Provider Issuer — used in the
idpIssuerparameter in the request payload. - X.509 Certificate — used in the
idpCertificateparameter in the request payload. You need to encode the certificate as base64 without newlines beforehand. If you enabled SAML request encryption, you also need to encode the private key as base64. You can use the following terminal command:
- Identity Provider Issuer — used in the
PowerShell
Bash (macOS)
Bash (Linux)
[Convert]::ToBase64String([IO.File]::ReadAllBytes(".\path-to-certificate-or-private-key"))
```
base64 -i ./<path-to-certificate-or-private-key> | tr -d '\n'
```
cat ./<path-to-certificate-or-private-key> | base64 -w0
```
4
Assign Users
1. In the left-side menu of the Admin Console, go to **Directory > Profile Editor**, and click **Profile** next to the profile of the app you created.
2. Click **Add Attribute** and configure the following: - **Data type** — select **string**.
- **Display name** — enter `Role`.
- **Variable name** — enter `role`.
- _(Optional)_ **Description** — enter a relevant description, for example, `The role of the user in Cognigy.AI`.
- **Enum** — activate **Define enumerated list of values**.
- **Attribute member** — enter a display name and the value of the role to which SSO applies, for example, `Base Role` and `base_role`. The value must match a [role in Cognigy.AI](https://docs.cognigy.com/ai/administer/access/admin-center/access-control).
- **Attribute required** — select this option.
Click **Save**.
3. Go to **Applications > Applications** and select the app you created.
4. On the **Assignments** tab, click the Edit button next to the user’s name and assign the role they should have in Cognigy.AI. This user can log in via SSO.
1
Set Up an App
1. Log in to your OneLogin Administration portal with an administrator account.
2. In the top bar, click **Applications**, then click **Add App** in the upper-right corner.
3. Search for `SAML Custom Connector (Advanced)` in the search field and select this option.
4. In the **Display Name** field, enter a name that identifies the integration, for example, `Cognigy.AI SSO`. Optionally upload icons. Click **Save**.
5. On the new app page, click the **Configuration** tab in the left-side menu and configure the following:
- **ACS (Consumer) URL** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
- **ACS (Consumer) URL Validator** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
- _(Optional)_ **Single Logout URL** — enter `https://<cognigy-url>/slo/<organization-id>`.
Click **Save**.
6. In the left-side menu, go to the **Parameters** tab, configure the following parameter and value pairs, and select the **Include in SAML assertion** option for each pair:
| Parameter | Value |
| --- | --- |
| `email` | Email |
| `firstName` | First Name |
| `lastName` | Last Name |
| `role` | User Role |
Click **Save** to apply the attribute mapping.
2
Get Authentication Data
1. In the left-side menu, go to the **SSO** tab and copy the following values: - **X.509 Certificate** — click **View Details** and download the certificate. You will use its contents as `idpCertificate`. You need to encode the certificate as base64 without newlines beforehand. You can use the following terminal command:
PowerShell
Bash (macOS)
Bash (Linux)
[Convert]::ToBase64String([IO.File]::ReadAllBytes(".\path-to-certificate"))
```
base64 -i ./<path-to-certificate> | tr -d '\n'
```
cat ./<path-to-certificate> | base64 -w0
```
- Issuer URL — used in the
idpIssuerparameter in the request payload.- SAML 2.0 Endpoint (HTTP) — used in the
idpLoginEndpointparameter in the request payload. - (Optional) SLO Endpoint — used in the
idpLogoutEndpointparameter in the request payload.
- SAML 2.0 Endpoint (HTTP) — used in the
- In the left-side menu, navigate to Users > Roles and click New Role.
- Add the following Cognigy.AI roles one by one, select the app you created, and click Save: -
adminapiKeysbase_rolelivechatodataprojectManageruserManager
- In the left-side menu, go to the Users tab and select a user for SSO. On the user page, enter the role they have in Cognigy.AI in the role field. The role must match the roles you configured in step 3.
- Click Save. The user can log in via SSO.
Configure SSO in Cognigy.AI
After the SSO app is ready, use the POST /v2.0/identityprovider/configure method to register the SSO configuration in Cognigy.AI.
Auth0 - OpenID Connect
Auth0 - SAML 2.0
Microsoft Entra ID
Google
Okta
OneLogin
Send the API request with the following parameters:
X-API-Keyheader — the API key from the Prerequisites section.idpIssuer— the Domain value you copied earlier from the Settings tab.idpClientId— the Client ID value you copied earlier from the Settings tab.idpClientSecret— the Client Secret value you copied earlier from the Settings tab.
API Request Example
-X POST \
JavaScript
Python
-H "Content-Type: application/json" \
-H "X-API-Key: <your-api-token>" \
-d '{
"idpType": "oidc",
"idpIssuer": "<DOMAIN>",
"idpClientId": "<CLIENT ID>",
"idpClientSecret": "<CLIENT SECRET>",
"idpIdTokenSignedResponseAlg": "RS256",
"idpTokenEndpointAuthMethod": "client_secret_basic"
}'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
body: JSON.stringify({
idpType: "oidc",
idpIssuer: "<DOMAIN>", // Domain from the Auth0 app Settings tab
idpClientId: "<CLIENT ID>", // Client ID from the Auth0 app Settings tab
idpClientSecret: "<CLIENT SECRET>", // Client Secret from the Auth0 app Settings tab
idpIdTokenSignedResponseAlg: "RS256",
idpTokenEndpointAuthMethod: "client_secret_basic"
})
});
import requests
response = requests.post(
"https://<api-base-url>/v2.0/identityprovider/configure",
headers={
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
json={
"idpType": "oidc",
"idpIssuer": "<DOMAIN>", # Domain from the Auth0 app Settings tab
"idpClientId": "<CLIENT ID>", # Client ID from the Auth0 app Settings tab
"idpClientSecret": "<CLIENT SECRET>", # Client Secret from the Auth0 app Settings tab
"idpIdTokenSignedResponseAlg": "RS256",
"idpTokenEndpointAuthMethod": "client_secret_basic"
}
)
Send the API request with the following data:
X-API-Keyheader — the API key from the Prerequisites section.idpIssuer— the Issuer value you copied earlier from the Usage tab of the SAML2 Web App add-on.idpLoginEndpoint— the Identity Provider Login URL you copied earlier from the Usage tab of the SAML2 Web App add-on.idpCertificate— the Identity Provider Certificate you copied earlier from the Usage tab of the SAML2 Web App add-on, including theBEGIN/ENDmarkers, with real line breaks replaced by\n.idpIdentifierFormat— the NameID format. Useurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressto match the add-on settings.
API Request Example
-X POST \
JavaScript
Python
-H "Content-Type: application/json" \
-H "X-API-Key: <your-api-token>" \
-d '{
"idpType": "saml",
"idpIssuer": "urn:<your-tenant>.auth0.com",
"idpLoginEndpoint": "https://<your-tenant>.auth0.com/samlp/<client-id>",
"idpCertificate": "-----BEGIN CERTIFICATE-----\n<certificate-body>\n-----END CERTIFICATE-----",
"idpIdentifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
}'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
body: JSON.stringify({
idpType: "saml",
idpLoginEndpoint: "https://<your-tenant>.auth0.com/samlp/<client-id>", // Identity Provider Login URL from the Auth0 add-on Usage tab
idpIssuer: "urn:<your-tenant>.auth0.com", // Identity Provider Issuer from the Auth0 add-on Usage tab
idpCertificate: "-----BEGIN CERTIFICATE-----\n<certificate-body>\n-----END CERTIFICATE-----", // Identity Provider Certificate from the Auth0 add-on Usage tab
idpIdentifierFormat: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
})
});
import requests
response = requests.post(
"https://<api-base-url>/v2.0/identityprovider/configure",
headers={
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
json={
"idpType": "saml",
"idpLoginEndpoint": "https://<your-tenant>.auth0.com/samlp/<client-id>", # Identity Provider Login URL from the Auth0 add-on Usage tab
"idpIssuer": "urn:<your-tenant>.auth0.com", # Identity Provider Issuer from the Auth0 add-on Usage tab
"idpCertificate": "-----BEGIN CERTIFICATE-----\n<certificate-body>\n-----END CERTIFICATE-----", # Identity Provider Certificate from the Auth0 add-on Usage tab
"idpIdentifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
}
)
Send the API request with the following data:
X-API-Keyheader — the API key from the Prerequisites section.idpIssuer— usehttps://<api-base-url>/auth/saml/login/<organization-id>.idpLoginEndpoint— the Login URL you copied earlier from the Set up<application name>section.idpCertificate— the certificate you downloaded earlier from the SAML Signing Certificate section, base64-encoded as a single continuous string with no line breaks.- (Optional)
idpLogoutUrl— the Logout URL you copied earlier from the Set up<application name>section.
API Request Example
cURL
JavaScript
Python
curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
-H "Content-Type: application/json" \
-H "X-API-Key: <your-api-token>" \
-d '{
"idpType": "saml",
"idpIssuer": "https://<api-base-url>/auth/saml/login/<organization-id>",
"idpLoginEndpoint": "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2",
"idpCertificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...",
"idpLogoutUrl": "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2"
}'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
body: JSON.stringify({
idpType: "saml",
idpIssuer: "https://<api-base-url>/auth/saml/login/<organization-id>", // Cognigy.AI SSO URL
idpLoginEndpoint: "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2", // Login URL from the Set up `<application name>` section
idpCertificate: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...", // Base64-encoded contents of the downloaded .crt file
idpLogoutUrl: "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2" // Logout URL from the Set up `<application name>` section (optional, for SP-initiated SLO)
})
});
import requests
response = requests.post(
"https://<api-base-url>/v2.0/identityprovider/configure",
headers={
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
json={
"idpType": "saml",
"idpIssuer": "https://<api-base-url>/auth/saml/login/<organization-id>", # Cognigy.AI SSO URL
"idpLoginEndpoint": "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2", # Login URL from the Set up `<application name>` section
"idpCertificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...", # Base64-encoded contents of the downloaded .crt file
"idpLogoutUrl": "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2" # Logout URL from the Set up `<application name>` section (optional, for SP-initiated SLO)
}
)
Don’t include the PEM header, the PEM footer, or any newline characters in the certificate you submit to Cognigy.AI. A malformed certificate string causes the SSO configuration request to fail.
Send the API request with the following data:
X-API-Keyheader — the API key from the Prerequisites section.idpIssuer— usehttps://<api-base-url>/auth/saml/login/<organization-id>.idpLoginEndpoint— the SSO URL value you copied earlier from the Google IdP Information dialog.idpCertificate— the certificate you downloaded earlier from the Google IdP Information dialog, base64-encoded as a single continuous string with no line breaks.
API Request Example
cURL
JavaScript
Python
curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
-H "Content-Type: application/json" \
-H "X-API-Key: <your-api-token>" \
-d '{
"idpType": "saml",
"idpIssuer": "https://<api-base-url>/auth/saml/login/<organization-id>",
"idpLoginEndpoint": "https://accounts.google.com/o/saml2/idp?idpid=XXXX",
"idpCertificate": "MIIDdTCCAl2gAwIBAgIJAKx..."
}'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
body: JSON.stringify({
idpType: "saml",
idpIssuer: "https://<api-base-url>/auth/saml/login/<organization-id>", // Cognigy.AI SSO URL
idpLoginEndpoint: "https://accounts.google.com/o/saml2/idp?idpid=XXXX", // SSO URL from Google IdP Information dialog in the Google Admin console
idpCertificate: "MIIDdTCCAl2gAwIBAgIJAKx..." // Certificate from Google IdP Information dialog, base64-encoded on a single line
})
});
import requests
response = requests.post(
"https://<api-base-url>/v2.0/identityprovider/configure",
headers={
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
json={
"idpType": "saml",
"idpIssuer": "https://<api-base-url>/auth/saml/login/<organization-id>", # Cognigy.AI SSO URL
"idpLoginEndpoint": "https://accounts.google.com/o/saml2/idp?idpid=XXXX", # SSO URL from Google IdP Information dialog in the Google Admin console
"idpCertificate": "MIIDdTCCAl2gAwIBAgIJAKx..." # Certificate from Google IdP Information dialog, base64-encoded on a single line
}
)
Send the API request with the following data:
X-API-Keyheader — the API key from the Prerequisites section.idpIssuer— the Identity Provider Issuer value you copied from the SAML setup instructions page.idpLoginEndpoint— the Identity Provider Single Sign-On URL value you copied from the SAML setup instructions page.- For encrypted SAML requests:
idpCertificate— X.509 Certificate you uploaded under Show Advanced Settings > Assertion Encryption, base64-encoded as a single continuous string with no line breaks.decryptionPrivateKey— the private key matching the X.509 certificate you uploaded under Show Advanced Settings > Assertion Encryption and base64-encoded as a single continuous string.
API Request Example (Without Encryption)
cURL
JavaScript
Python
curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
-H "Content-Type: application/json" \
-H "X-API-Key: <your-api-token>" \
-d '{
"idpType": "saml",
"idpIssuer": "<OKTA_IDENTITY_PROVIDER_ISSUER>",
"idpLoginEndpoint": "<OKTA_SINGLE_SIGN_ON_URL>"
}'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
body: JSON.stringify({
idpType: "saml",
idpIssuer: "<OKTA_IDENTITY_PROVIDER_ISSUER>", // Identity Provider Issuer from the Okta Sign On tab
idpLoginEndpoint: "<OKTA_SINGLE_SIGN_ON_URL>" // Identity Provider Single Sign-On URL from the Okta Sign On tab
})
});
import requests
response = requests.post(
"https://<api-base-url>/v2.0/identityprovider/configure",
headers={
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
json={
"idpType": "saml",
"idpIssuer": "<OKTA_IDENTITY_PROVIDER_ISSUER>", # Identity Provider Issuer from the Okta Sign On tab
"idpLoginEndpoint": "<OKTA_SINGLE_SIGN_ON_URL>" # Identity Provider Single Sign-On URL from the Okta Sign On tab
}
)
API Request Example (With Encryption)
cURL
JavaScript
Python
curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
-H "Content-Type: application/json" \
-H "X-API-Key: <your-api-token>" \
-d '{
"idpType": "saml",
"idpIssuer": "<OKTA_IDENTITY_PROVIDER_ISSUER>",
"idpLoginEndpoint": "<OKTA_SINGLE_SIGN_ON_URL>",
"idpCertificate": "<OKTA_X509_CERTIFICATE>",
"decryptionPrivateKey": "<OKTA_PRIVATE_KEY>"
}'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
body: JSON.stringify({
idpType: "saml",
idpIssuer: "<OKTA_IDENTITY_PROVIDER_ISSUER>", // Identity Provider Issuer from the Okta Sign On tab
idpLoginEndpoint: "<OKTA_SINGLE_SIGN_ON_URL>", // Identity Provider Single Sign-On URL from the Okta Sign On tab
idpCertificate: "<OKTA_X509_CERTIFICATE>", // X.509 Certificate from the Okta Sign On tab, base64-encoded on a single line
decryptionPrivateKey: "<OKTA_PRIVATE_KEY>" // Private key matching the certificate uploaded to Okta, base64-encoded on a single line
})
});
import requests
response = requests.post(
"https://<api-base-url>/v2.0/identityprovider/configure",
headers={
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
json={
"idpType": "saml",
"idpIssuer": "<OKTA_IDENTITY_PROVIDER_ISSUER>", # Identity Provider Issuer from the Okta Sign On tab
"idpLoginEndpoint": "<OKTA_SINGLE_SIGN_ON_URL>", # Identity Provider Single Sign-On URL from the Okta Sign On tab
"idpCertificate": "<OKTA_X509_CERTIFICATE>", # X.509 Certificate from the Okta Sign On tab, base64-encoded on a single line
"decryptionPrivateKey": "<OKTA_PRIVATE_KEY>" # Private key matching the certificate uploaded to Okta, base64-encoded on a single line
}
)
Send the API request with the following data:
X-API-Keyheader — the API key from the Prerequisites section.idpIssuer— the Issuer URL you copied earlier from the SSO tab of the OneLogin app.idpLoginEndpoint— the SAML 2.0 Endpoint (HTTP) you copied earlier from the SSO tab of the OneLogin app.idpCertificate— the X.509 Certificate you encoded earlier from the SSO tab of the OneLogin app, base64-encoded as a single continuous string with no line breaks.- (Optional)
idpLogoutEndpoint— the SLO Endpoint you copied earlier from the SSO tab of the OneLogin app.
API Request Example
cURL
JavaScript
Python
curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
-H "Content-Type: application/json" \
-H "X-API-Key: <your-api-token>" \
-d '{
"idpType": "saml",
"idpIssuer": "https://app.onelogin.com/saml/metadata/<id>",
"idpLoginEndpoint": "https://<subdomain>.onelogin.com/trust/saml2/http-post/sso/<id>",
"idpCertificate": "<ONELOGIN_X509_CERTIFICATE>",
"idpLogoutEndpoint": "https://<subdomain>.onelogin.com/trust/saml2/http-redirect/slo/<id>"
}'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
body: JSON.stringify({
idpType: "saml",
idpIssuer: "https://app.onelogin.com/saml/metadata/<id>", // Issuer URL from the OneLogin SSO tab
idpLoginEndpoint: "https://<subdomain>.onelogin.com/trust/saml2/http-post/sso/<id>", // SAML 2.0 Endpoint (HTTP) from the OneLogin SSO tab
idpCertificate: "<ONELOGIN_X509_CERTIFICATE>", // X.509 Certificate from the OneLogin SSO tab, base64-encoded on a single line
idpLogoutEndpoint: "https://<subdomain>.onelogin.com/trust/saml2/http-redirect/slo/<id>" // SLO Endpoint from the OneLogin SSO tab (optional)
})
});
import requests
response = requests.post(
"https://<api-base-url>/v2.0/identityprovider/configure",
headers={
"Content-Type": "application/json",
"X-API-Key": "<your-api-token>"
},
json={
"idpType": "saml",
"idpIssuer": "https://app.onelogin.com/saml/metadata/<id>", # Issuer URL from the OneLogin SSO tab
"idpLoginEndpoint": "https://<subdomain>.onelogin.com/trust/saml2/http-post/sso/<id>", # SAML 2.0 Endpoint (HTTP) from the OneLogin SSO tab
"idpCertificate": "<ONELOGIN_X509_CERTIFICATE>", # X.509 Certificate from the OneLogin SSO tab, base64-encoded on a single line
"idpLogoutEndpoint": "https://<subdomain>.onelogin.com/trust/saml2/http-redirect/slo/<id>" # SLO Endpoint from the OneLogin SSO tab (optional)
}
)
A successful request returns a confirmation that the identity provider configuration has been saved. The Cognigy.AI login page now displays a Log in with SSO button for users in your organization.
Test the SSO Login
- On the Cognigy.AI login page, enter the email address of a user assigned to the IdP app and click Log in with SSO. Cognigy.AI redirects you to the IdP and prompts you for your IdP credentials. After authentication, the IdP redirects back to Cognigy.AI and logs you in.
- Click the user icon in the lower-left corner and select Logout.
- Enter your email address again and click Log in with SSO. This time, you are logged in directly without re-entering your IdP credentials.
More Information
- Single Sign-on with SAML 2.0
- Auth0: OpenID Connect Protocol
- Auth0: SAML Configuration
- Microsoft Entra ID: Enable SAML single sign-on for an enterprise application
- Google Workspace: Set up your own custom SAML app
- Okta: Create SAML app integrations
- OneLogin: SAML Custom Connector (Advanced)
Last modified onJune 9, 2026
Previous\ \ Single Sign-on using SAML 2.0
Ctrl+I
ChatVoice
Click the call button to start talking.
Cognigy AssistantReady to talk
Connecting…
Powered by Cognigy.AI
Cookie Consent
We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for.
With your consent, you're helping us make our documentation better. To find out more about the cookies we use, see our Privacy Policy.
Accept
'; cachedClickBlockingCode = css + js; return cachedClickBlockingCode; }
// Helper: Inject click-blocking code into iframe function injectClickBlocking(iframeDoc) { if (!iframeDoc || !iframeDoc.head) return false;
try { const style = iframeDoc.createElement('style'); style.textContent = 'a,button,input,select,textarea,[onclick],[role="button"],input[type="button"],input[type="submit"],[href],[tabindex]:not([tabindex="-1"]){pointer-events:none!important;cursor:default!important}a:hover,button:hover{cursor:default!important;opacity:0.7!important}'; iframeDoc.head.appendChild(style);
const script = iframeDoc.createElement('script'); script.textContent = '(function(){var preventClick=function(e){e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();return false;};document.addEventListener("click",preventClick,true);document.addEventListener("mousedown",preventClick,true);document.addEventListener("mouseup",preventClick,true);document.addEventListener("touchstart",preventClick,true);document.addEventListener("touchend",preventClick,true);document.addEventListener("contextmenu",preventClick,true);window.addEventListener("beforeunload",function(e){e.preventDefault();e.returnValue="";return "";},true);try{Object.defineProperty(window,"location",{value:{href:"#",replace:function(){},assign:function(){}},writable:false,configurable:false});}catch(e){}var originalPushState=history.pushState;history.pushState=function(){return false;};var originalReplaceState=history.replaceState;history.replaceState=function(){return false;};var originalOpen=window.open;window.open=function(){return null;};var allElements=document.querySelectorAll("*");for(var i=0;i 0 || replacedViewers.length > 0) { const currentUrl = window.location.href; if (!processedUrls.has(currentUrl)) { processedUrls.add(currentUrl); lastUrl = currentUrl; } return; }
const currentUrl = window.location.href; if (currentUrl === lastUrl && lastUrl !== null && processedUrls.has(currentUrl)) { return; }
// Optimized: Use cached viewer query const viewers = getUninitializedViewers();
if (viewers.length === 0) { if (periodicCheckInterval) { clearInterval(periodicCheckInterval); periodicCheckInterval = null; } return; }
// Final safety check if (viewers.length > 0) { if (currentUrl === lastUrl && lastUrl !== null && processedUrls.has(currentUrl)) { viewers.forEach(function(viewer) { viewer.setAttribute('data-initialized', 'true'); }); return; }
if (currentUrl === lastUrl && lastUrl !== null) { if (existingContainers.length > 0) { processedUrls.add(currentUrl); viewers.forEach(function(viewer) { viewer.setAttribute('data-initialized', 'true'); }); return; } } }
isInitializing = true; let processedAny = false;
// Optimized: Process viewers in batch viewers.forEach(function(viewer) { if (processedElements.has(viewer) || viewer.hasAttribute('data-initialized')) { return; }
if (viewer.closest('.single-page-viewer-container')) { return; }
if (!viewer.parentNode) { return; }
const isInContentArea = contentArea === document.body || contentArea.contains(viewer); if (!isInContentArea) { return; }
const src = viewer.getAttribute('data-src'); const hint = viewer.getAttribute('data-hint') || ''; const defaultZoom = parseFloat(viewer.getAttribute('data-zoom')) || DEFAULT_ZOOM; const dataHeight = viewer.getAttribute('data-height'); const height = dataHeight ? parseFloat(dataHeight) : null;
if (!src) { // Don't set isInitializing = false here - it affects all viewers in the loop return; }
processedElements.add(viewer); viewer.setAttribute('data-initialized', 'true');
let zoom = defaultZoom;
// Optimized: Use document fragment for DOM construction const fragment = document.createDocumentFragment(); const container = document.createElement('div'); container.className = 'single-page-viewer-container';
const wrapper = document.createElement('div'); wrapper.className = 'single-page-viewer-wrapper';
const controlsBar = document.createElement('div'); controlsBar.className = 'single-page-viewer-controls';
if (hint) { const hintDiv = document.createElement('div'); hintDiv.className = 'single-page-viewer-hint'; hintDiv.innerHTML = '' + hint + ''; controlsBar.appendChild(hintDiv); }
const iframeWrapper = document.createElement('div'); iframeWrapper.className = 'single-page-viewer-iframe-wrapper'; iframeWrapper._scrollHandlerAdded = false;
// Optimized: Get height without temporary DOM manipulation const baseHeightFromCSS = height || NORMAL_HEIGHT;
const scaleDiv = document.createElement('div'); scaleDiv.className = 'single-page-viewer-scale'; const initialHeight = baseHeightFromCSS; scaleDiv.style.cssText = 'transform: scale(' + zoom + '); transform-origin: top left; width: ' + (100 / zoom) + '%; height: ' + initialHeight + 'px; transition: transform 0.2s ease;';
const iframe = document.createElement('iframe'); let iframeSrc = src; if (iframeSrc && !iframeSrc.startsWith('http') && !iframeSrc.startsWith('//')) { if (!iframeSrc.startsWith('/')) { iframeSrc = '/' + iframeSrc; } }
iframe.title = 'Single Page Viewer'; iframe.className = 'single-page-viewer-iframe'; iframe.style.width = '100%'; iframe.style.height = initialHeight + 'px'; iframe.loading = 'lazy'; iframe.setAttribute('scrolling', 'no'); iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-modals');
const clickBlocker = document.createElement('div'); clickBlocker.className = 'single-page-viewer-click-blocker';
if (iframeSrc.endsWith('.html') && !iframeSrc.startsWith('http')) { fetch(iframeSrc) .then(function(response) { return response.text().then(function(htmlContent) { if (htmlContent.includes('Page Not Found') || (htmlContent.includes('__next') && htmlContent.includes('mintlify') && !htmlContent.includes('Page saved with SingleFile'))) { throw new Error('Received 404 page instead of HTML file.'); } const blockingCode = getClickBlockingCode(); if (htmlContent.includes('')) { htmlContent = htmlContent.replace('', blockingCode + ''); } else if (htmlContent.includes('')) { htmlContent = htmlContent.replace('', '' + blockingCode); } else { if (htmlContent.includes('')) { htmlContent = htmlContent.replace('', '' + blockingCode + ''); } else { htmlContent = blockingCode + htmlContent; } } iframe.srcdoc = htmlContent; }); }); } else { iframe.src = iframeSrc; }
iframe.addEventListener('load', function() { setTimeout(function() { updateIframeDimensions(iframeWrapper, scaleDiv, iframe, zoom); }, 100);
try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; injectClickBlocking(iframeDoc); } catch (e) { // Cross-origin iframe - cannot block clicks } }, { once: true });
iframe.addEventListener('error', function() { // Iframe failed to load - error handling is silent }, { once: true });
scaleDiv.appendChild(iframe); iframeWrapper.appendChild(scaleDiv); if (!iframeSrc.endsWith('.html') || iframeSrc.startsWith('http')) { iframeWrapper.appendChild(clickBlocker); }
if (hint) { wrapper.appendChild(controlsBar); } wrapper.appendChild(iframeWrapper); container.appendChild(wrapper); fragment.appendChild(container);
try { if (viewer.parentNode) { if (viewer === contentArea || viewer.id === 'content-area') { isInitializing = false; return; }
const viewerId = 'viewer-' + Date.now() + '-' + Math.random().toString(36).slice(2, 11); viewer.id = viewerId; viewer.setAttribute('data-viewer-id', viewerId); container.setAttribute('data-viewer-id', viewerId);
// Optimized: Single DOM insertion using fragment if (viewer.nextSibling) { viewer.parentNode.insertBefore(fragment, viewer.nextSibling); } else { viewer.parentNode.appendChild(fragment); }
// Batch style updates Object.assign(viewer.style, { display: 'none', visibility: 'hidden', position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', opacity: '0', pointerEvents: 'none', margin: '0', padding: '0' });
viewer.setAttribute('data-initialized', 'true'); viewer.setAttribute('data-viewer-replaced', 'true');
processedAny = true; invalidateQueryCache();
requestAnimationFrame(function() { updateIframeDimensions(iframeWrapper, scaleDiv, iframe, zoom); }); } } catch (e) { isInitializing = false; return; } });
if (processedAny) { const currentUrl = window.location.href; processedUrls.add(currentUrl); lastUrl = currentUrl; invalidateQueryCache(); }
isInitializing = false; }
// Wait for content area to be available function waitForContentArea(callback, maxAttempts, attempt) { attempt = attempt || 0; maxAttempts = maxAttempts || 20;
const contentArea = document.querySelector('#content-area') || document.querySelector('.mdx-content');
if (contentArea && document.contains(contentArea)) { cachedContentArea = contentArea; callback(); } else if (attempt < maxAttempts) { setTimeout(function() { waitForContentArea(callback, maxAttempts, attempt + 1); }, 500); } else { cachedContentArea = document.body; callback(); } }
// Optimized: Initialize with better state management function initialize() { const currentUrl = window.location.href; if (lastUrl !== currentUrl && lastUrl !== null) { lastUrl = currentUrl; processedUrls.clear(); invalidateQueryCache(); }
if (typeof window !== 'undefined') { window.__lastFullUrl = window.location.href; }
if (lastThemeClass === null && document.documentElement) { lastThemeClass = document.documentElement.className; }
if (!themeObserver && document.documentElement) { themeObserver = new MutationObserver(function(mutations) { for (let i = 0; i < mutations.length; i++) { const mutation = mutations[i]; if (mutation.type === 'attributes' && mutation.attributeName === 'class') { const currentThemeClass = document.documentElement.className; if (lastThemeClass !== null && lastThemeClass !== currentThemeClass) { themeToggleInProgress = true;
// Optimized: Lock containers synchronously const contentArea = getContentArea(); if (contentArea) { const containers = getContainers(true); containers.forEach(function(container) { if (container.getAttribute('data-position-locked') !== 'true') { const rect = container.getBoundingClientRect(); Object.assign(container.style, { position: 'fixed', top: rect.top + 'px', left: rect.left + 'px', width: rect.width + 'px', zIndex: '9999', margin: '0' }); container.setAttribute('data-position-locked', 'true'); containerPositions.set(container, { top: rect.top, left: rect.left, width: rect.width, height: rect.height }); } }); }
let repositionAttempts = 0; const maxRepositionAttempts = 30;
const repositionInterval = setInterval(function() { repositionAttempts++; repositionContainersAfterThemeToggleCore(); // Use core function for immediate repositioning if (repositionAttempts >= maxRepositionAttempts) { clearInterval(repositionInterval); } }, 100);
setTimeout(function() { clearInterval(repositionInterval); themeToggleInProgress = false; repositionContainersAfterThemeToggleCore(); // Use core function for final repositioning unlockContainers(); }, 3000); } lastThemeClass = currentThemeClass; } } });
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); }
if (typeof MutationObserver !== 'undefined') { setupObserver(); }
waitForContentArea(function() { setTimeout(function() { initSinglePageViewers(); setTimeout(function() { const remaining = getUninitializedViewers(true); if (remaining.length > 0) { initSinglePageViewers(); } }, INIT_DELAY); }, INIT_DELAY); }); }
// Optimized: Lock/unlock containers function lockContainersInPlace() { const contentArea = getContentArea(); if (!contentArea) return;
const containers = getContainers(true); containerPositions.clear();
requestAnimationFrame(function() { containers.forEach(function(container) { if (container.getAttribute('data-position-locked') !== 'true') { const rect = container.getBoundingClientRect(); Object.assign(container.style, { position: 'fixed', top: rect.top + 'px', left: rect.left + 'px', width: rect.width + 'px', zIndex: '9999', margin: '0' }); container.setAttribute('data-position-locked', 'true'); containerPositions.set(container, { top: rect.top, left: rect.left, width: rect.width, height: rect.height }); } }); }); }
function unlockContainers() { const contentArea = getContentArea(); if (!contentArea) return;
const containers = Array.from( contentArea.querySelectorAll('.single-page-viewer-container[data-position-locked="true"]') );
containers.forEach(function(container) { Object.assign(container.style, { position: '', top: '', left: '', width: '', zIndex: '', margin: '' }); container.removeAttribute('data-position-locked'); // Clean up container position from Map to prevent memory leak containerPositions.delete(container); });
containerPositions.clear(); }
// Optimized: Reposition containers with better DOM operations // Core repositioning function (not debounced for immediate use) function repositionContainersAfterThemeToggleCore() { const contentArea = getContentArea(); if (!contentArea) return;
const containers = getContainers(true);
containers.forEach(function(container) { const viewerId = container.getAttribute('data-viewer-id'); if (!viewerId) return;
let viewer = document.getElementById(viewerId);
if (!viewer) { const iframe = container.querySelector('iframe'); if (iframe && iframe.src) { const allViewers = contentArea.querySelectorAll('[data-single-page-viewer]'); for (let i = 0; i < allViewers.length; i++) { const v = allViewers[i]; if (v.getAttribute('data-src') === iframe.src) { viewer = v; viewer.id = viewerId; viewer.setAttribute('data-viewer-id', viewerId); Object.assign(viewer.style, { display: 'none', visibility: 'hidden', position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', opacity: '0', pointerEvents: 'none', margin: '0', padding: '0' }); viewer.setAttribute('data-initialized', 'true'); viewer.setAttribute('data-viewer-replaced', 'true'); break; } } } }
if (viewer && viewer.parentNode) { if (container.getAttribute('data-position-locked') === 'true') { unlockContainers(); }
const viewerIndex = Array.from(viewer.parentNode.children).indexOf(viewer); const containerIndex = Array.from(viewer.parentNode.children).indexOf(container);
if (containerIndex !== viewerIndex + 1) { // CRITICAL: Check if container is actually a child before removing if (container.parentNode && container.parentNode.contains(container)) { try { container.parentNode.removeChild(container); } catch (e) { return; // Skip repositioning if removal failed } } if (viewer.nextSibling) { viewer.parentNode.insertBefore(container, viewer.nextSibling); } else { viewer.parentNode.appendChild(container); } } else if (container.parentNode !== viewer.parentNode) { // CRITICAL: Check if container is actually a child before removing if (container.parentNode && container.parentNode.contains(container)) { try { container.parentNode.removeChild(container); } catch (e) { return; // Skip repositioning if removal failed } } if (viewer.nextSibling) { viewer.parentNode.insertBefore(container, viewer.nextSibling); } else { viewer.parentNode.appendChild(container); } } } });
invalidateQueryCache(); }
// Debounced version for non-critical repositioning const repositionContainersAfterThemeToggle = debounce(repositionContainersAfterThemeToggleCore, REPOSITION_DEBOUNCE);
// Optimized: Observer setup with better filtering let observer = null; let containerPositionObserver = null; let fallbackObserver = null; // Track fallback observer for cleanup
function setupContainerPositionObserver() { if (typeof MutationObserver === 'undefined') return;
const contentArea = getContentArea(); if (!contentArea || !document.contains(contentArea)) { setTimeout(setupContainerPositionObserver, 500); return; }
if (containerPositionObserver) { containerPositionObserver.disconnect(); }
containerPositionObserver = new MutationObserver(function(mutations) { if (themeToggleInProgress) { // Use core function (not debounced) for immediate repositioning during theme toggle requestAnimationFrame(repositionContainersAfterThemeToggleCore); return; }
let needsReposition = false; for (let i = 0; i < mutations.length && !needsReposition; i++) { if (mutations[i].type === 'childList') { needsReposition = true; } }
if (needsReposition) { clearTimeout(containerPositionObserver._timeout); containerPositionObserver._timeout = setTimeout(repositionContainersAfterThemeToggle, REPOSITION_DEBOUNCE); } });
containerPositionObserver.observe(contentArea, { childList: true, subtree: true, attributes: false, characterData: false });
containerPositionObserver._timeout = null; }
// Optimized: Main observer with better early exits function setupObserver() { if (typeof MutationObserver === 'undefined') return;
const contentArea = getContentArea(); if (!contentArea || !document.contains(contentArea)) { setTimeout(setupObserver, 500); return; }
if (contentArea === document.documentElement || contentArea === document.body) { return; }
if (observer) { const currentObserved = observer._observedElement; if (currentObserved === contentArea && document.contains(contentArea)) { return; } if (currentObserved) { observer.disconnect(); } observer = null; }
observer = new MutationObserver(function(mutations) { if (themeToggleInProgress) return;
let hasNewViewer = false; for (let i = 0; i < mutations.length && !hasNewViewer; i++) { const mutation = mutations[i]; if (mutation.addedNodes && mutation.addedNodes.length > 0) { for (let j = 0; j < mutation.addedNodes.length && !hasNewViewer; j++) { const node = mutation.addedNodes[j]; if (node.nodeType === 1 && node.hasAttribute && node.hasAttribute('data-single-page-viewer') && !node.hasAttribute('data-initialized') && !node.hasAttribute('data-viewer-replaced')) { hasNewViewer = true; } } } }
if (hasNewViewer) { // Use core function for immediate repositioning when new viewer detected repositionContainersAfterThemeToggleCore(); setTimeout(repositionContainersAfterThemeToggleCore, 10); setTimeout(repositionContainersAfterThemeToggleCore, 50); setTimeout(repositionContainersAfterThemeToggleCore, 200); }
clearTimeout(observerTimeout); observerTimeout = setTimeout(function() { if (themeToggleInProgress) return;
let hasNewViewer = false; for (let i = 0; i < mutations.length && !hasNewViewer; i++) { const mutation = mutations[i];
if (mutation.target === document.documentElement || mutation.target === document.body) { continue; }
if (mutation.type === 'attributes' && mutation.attributeName === 'class' && mutation.target === document.documentElement) { continue; }
if (mutation.type === 'attributes' && mutation.attributeName === 'data-initialized') { const target = mutation.target; if (target && target.hasAttribute && target.hasAttribute('data-single-page-viewer') && !target.hasAttribute('data-initialized')) { hasNewViewer = true; } }
if (mutation.addedNodes && mutation.addedNodes.length > 0) { for (let j = 0; j < mutation.addedNodes.length && !hasNewViewer; j++) { const node = mutation.addedNodes[j]; if (node.nodeType === 1 && node.hasAttribute && node.hasAttribute('data-single-page-viewer') && !node.hasAttribute('data-initialized')) { hasNewViewer = true; } } } }
if (hasNewViewer) { invalidateQueryCache(); initSinglePageViewers(); } }, OBSERVER_DEBOUNCE); });
observer.observe(contentArea, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-initialized'], characterData: false });
observer._observedElement = contentArea; setupContainerPositionObserver(); }
function start() { setupObserver(); initialize(); }
function checkAndInitialize() { if (isInitializing) return;
const currentUrl = window.location.href; if (currentUrl === lastUrl && lastUrl !== null) { return; }
const contentArea = document.querySelector('#content-area') || document.querySelector('.mdx-content'); if (contentArea) { const viewers = getUninitializedViewers(true); if (viewers.length > 0) { cachedContentArea = contentArea; setupObserver(); initialize(); } } }
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { setTimeout(start, 100); }
if (typeof window !== 'undefined' && window.next) { let initialLoadAttempts = 0; const maxInitialAttempts = 4; const attemptDelays = [200, 500, 1000, 2000]; const initialUrl = window.location.href;
attemptDelays.forEach(function(delay) { setTimeout(function() { if (initialLoadAttempts < maxInitialAttempts) { initialLoadAttempts++; if (window.location.href === initialUrl) { checkAndInitialize(); } } }, delay); }); }
window.addEventListener('popstate', function(e) { const currentUrl = window.location.href; if (currentUrl !== lastUrl) { handleNavigation(); } });
if (typeof window !== 'undefined') { if (window.next && window.next.router && window.next.router.events) { window.next.router.events.on('routeChangeComplete', function(url) { lastUrl = window.location.href; isInitializing = false; pendingInitCall = false; if (navigationTimeout) { clearTimeout(navigationTimeout); navigationTimeout = null; } invalidateContentAreaCache(); invalidateQueryCache(); setTimeout(function() { setupObserver(); initialize(); }, 300); });
window.next.router.events.on('routeChangeStart', function(url) { isInitializing = false; pendingInitCall = false; }); }
if (!window.next || !window.next.router) { const originalPushState = history.pushState; const originalReplaceState = history.replaceState;
history.pushState = function() { originalPushState.apply(history, arguments); setTimeout(function() { if (window.location.href !== lastUrl) { handleNavigation(); } }, 0); };
history.replaceState = function() { originalReplaceState.apply(history, arguments); setTimeout(function() { if (window.location.href !== lastUrl) { handleNavigation(); } }, 0); }; } }
document.addEventListener('visibilitychange', function() { if (!document.hidden) { isInitializing = false; startPeriodicCheck(); if (!pendingInitCall) { pendingInitCall = true; setTimeout(function() { pendingInitCall = false; initSinglePageViewers(); }, INIT_DELAY / 2); } } });
function startPeriodicCheck() { if (periodicCheckInterval) return;
periodicCheckInterval = setInterval(function() { const uninitialized = getUninitializedViewers(true); if (uninitialized.length === 0) { clearInterval(periodicCheckInterval); periodicCheckInterval = null; } else if (!isInitializing) { initSinglePageViewers(); } }, 2000); }
setTimeout(startPeriodicCheck, INIT_DELAY * 2);
window.__singlePageViewerInit = function() { const currentUrl = window.location.href; if (currentUrl === lastUrl) { return; }
isInitializing = false; pendingInitCall = false; lastUrl = currentUrl; invalidateContentAreaCache(); invalidateQueryCache(); setupObserver(); initialize(); };
window.__singlePageViewerCheck = function() { invalidateQueryCache(); initSinglePageViewers(); }; })();
// Optimized: Second IIFE for navigation handling (function() { 'use strict';
let lastPathname = window.location.pathname; let lastInitializedPathname = window.location.pathname;
function checkAndInit() { const currentPathname = window.location.pathname; if (currentPathname === lastInitializedPathname) { return; }
lastInitializedPathname = currentPathname; if (typeof window !== 'undefined') { window.__lastFullUrl = window.location.href; }
if (window.__singlePageViewerInit) { window.__singlePageViewerInit(); } }
function runInit() { if (window.__singlePageViewerInit) { setTimeout(checkAndInit, 100); } else { let attempts = 0; const maxAttempts = 50; const checkInterval = setInterval(function() { attempts++; if (window.__singlePageViewerInit) { clearInterval(checkInterval); setTimeout(checkAndInit, 100); } else if (attempts >= maxAttempts) { clearInterval(checkInterval); } }, 100); } }
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', runInit); } else { setTimeout(runInit, 100); }
if (typeof window !== 'undefined' && window.next && window.next.router && window.next.router.events) { window.next.router.events.on('routeChangeComplete', function(url) { lastPathname = window.location.pathname; setTimeout(checkAndInit, 300); }); }
setInterval(function() { const currentPathname = window.location.pathname; if (currentPathname !== lastPathname) { lastPathname = currentPathname; setTimeout(checkAndInit, 300); } }, 500);
if (typeof MutationObserver !== 'undefined') { let fallbackObserverTimeout = null; // Store in outer scope for cleanup tracking fallbackObserver = new MutationObserver(function(mutations) { if (fallbackObserverTimeout) { clearTimeout(fallbackObserverTimeout); } fallbackObserverTimeout = setTimeout(function() { const contentArea = document.querySelector('#content-area'); if (contentArea) { const uninitialized = contentArea.querySelectorAll( '[data-single-page-viewer]:not([data-initialized]):not(.single-page-viewer-container [data-single-page-viewer])' ); if (uninitialized.length > 0) { const currentPathname = window.location.pathname; if (currentPathname !== lastInitializedPathname) { setTimeout(checkAndInit, 200); } } } }, 500); });
if (document.body) { fallbackObserver.observe(document.body, { childList: true, subtree: true }); } else { document.addEventListener('DOMContentLoaded', function() { if (document.body) { fallbackObserver.observe(document.body, { childList: true, subtree: true }); } }); } } })();