Framer Plugin Workaround

Framer is currently having issues with their package management service, and some users may face errors when using our Framer plugin. To unblock developers while the Framer team is looking into a solution, we have published this workaround guide.

The Issue

The Framer plugin utilizes our React SDK to embed the flow component and handle session management. In certain Framer projects, the Framer package manager is unable to resolve the import of our React SDK and also resolve the other components on the page.

The Workaround

Although it requires some manual configuration, you can follow these steps to add Descope authentication to their Framer site, utilizing our WebJS and Web Component SDKs instead of React. Make sure to follow all of the steps completely:

1. Create Component File

First, you will create a new code file in your Framer project. Call it DescopeWebComponent.tsx and select "New Component".

create code file

After creating the file, delete the contents of the file and paste the following instead:

import { useEffect, useRef } from "react"
 
const DescopeLogin = () => {
    const containerRef = useRef(null)
    const projectId = "<your-project-id>" // replace with your Descope Project ID
    const flowId = "<your-flow-id>" // replace with your Descope Flow ID
 
    useEffect(() => {
        // Dynamically load Descope scripts
        const loadScripts = async () => {
            const loadScript = (src) =>
                new Promise((resolve, reject) => {
                    const script = document.createElement("script")
                    script.src = src
                    script.async = true
                    script.onload = resolve
                    script.onerror = reject
                    document.head.appendChild(script)
                })
 
            try {
                await loadScript(
                    "https://descopecdn.com/npm/@descope/web-component@3.21.0/dist/index.js"
                )
                await loadScript(
                    "https://descopecdn.com/npm/@descope/web-js-sdk@1.16.0/dist/index.umd.js"
                )
 
                const sdk = window.Descope({
                    projectId: projectId,
                    persistTokens: true,
                    autoRefresh: true,
                })
 
                const sessionToken = sdk.getSessionToken()
                const notValidToken = sessionToken
                    ? sdk.isJwtExpired(sessionToken)
                    : true
 
                if (!sessionToken || notValidToken) {
                    const container = containerRef.current
                    if (container && !container.querySelector("descope-wc")) {
                        container.innerHTML = `<descope-wc project-id="${projectId}" flow-id="${flowId}"></descope-wc>`
                        const wcElement = container.querySelector("descope-wc")
 
                        wcElement.addEventListener("success", () => {
                            sdk.refresh()
                        })
 
                        wcElement.addEventListener("error", (err) => {
                            console.error("Descope error:", err)
                        })
                    }
                }
            } catch (error) {
                console.error("Error loading Descope scripts", error)
            }
        }
        loadScripts()
    }, [])
 
    return (
        <div>
            <div ref={containerRef}></div>
        </div>
    )
}
 
import React from "react"
function App() {
    return (
        <div className="App">
            <DescopeLogin />
        </div>
    )
}
 
export default App

Replace <your-project-id> and <your-flow-id> with your Descope Project and Flow IDs, and save the file.

2. Insert Flow Component

Now you can drag and drop your flow component from the Project Assets toolbar onto anywhere you'd like on your page.

drag component

3. Create Overrides File

Now that you've added authentication to your page, you need a way to manage the authenticated user. We will achieve this using Framer Overrides.

You will create a new code file in your Framer project. Call it DescopeOverrides.tsx and select "New Override".

overrides file

After creating the file, delete the contents of the file and paste the following instead:

"use client"
 
import { Override } from "framer"
 
import React, { useState, useEffect, useCallback } from "react"
import type { ComponentType } from "react"
 
declare global {
    interface Window {
        Descope: any
    }
}
 
const projectId = "<your-project-id>" // replace with your Descope Project ID
const redirectPage = "<your-redirect-url>" // replace with your Redirect URL
 
const loadDescopeScripts = (): Promise<void> => {
    return new Promise((resolve, reject) => {
        if (typeof window === "undefined") return reject("Window undefined")
 
        if (document.getElementById("descope-webjs-sdk")) {
            resolve()
            return
        }
 
        const webJsScript = document.createElement("script")
        webJsScript.id = "descope-webjs-sdk"
        webJsScript.src =
            "https://descopecdn.com/npm/@descope/web-js-sdk@1.16.0/dist/index.umd.js"
        webJsScript.async = true
 
        const webComponentScript = document.createElement("script")
        webComponentScript.id = "descope-web-component-sdk"
        webComponentScript.src =
            "https://descopecdn.com/npm/@descope/web-component@3.21.0/dist/index.js"
        webComponentScript.async = true
        webComponentScript.type = "module"
 
        webJsScript.onload = () => {
            document.head.appendChild(webComponentScript)
            webComponentScript.onload = () => resolve()
            webComponentScript.onerror = (e) => reject(e)
        }
        webJsScript.onerror = (e) => reject(e)
 
        document.head.appendChild(webJsScript)
    })
}
 
let sdkInstance: any = null
 
const initSDK = () => {
    if (typeof window === "undefined") return null
    if (!window.Descope) return null
    if (!sdkInstance) {
        sdkInstance = window.Descope({
            projectId,
            persistTokens: true,
            autoRefresh: true,
        })
    }
    return sdkInstance
}
 
const logout = async () => {
    if (!sdkInstance) return
    await sdkInstance.logoutAll()
    window.location.reload()
}
 
const ClientSideWrapper: React.FC<{ children: React.ReactNode }> = ({
    children,
}) => {
    const [isClient, setIsClient] = useState(false)
    useEffect(() => {
        setIsClient(true)
    }, [])
    return isClient ? <>{children}</> : null
}
 
const AuthWrapperContent = ({
    Component,
    hidden,
    redirect,
    redirectURL,
    ...props
}: {
    Component: ComponentType<any>
    hidden: boolean
    redirect: boolean
    redirectURL: string
    [key: string]: any
}) => {
    const [sdk, setSdk] = useState<any>(null)
    const [isReady, setIsReady] = useState(false)
    const [isAuthenticated, setIsAuthenticated] = useState(false)
 
    useEffect(() => {
        const init = async () => {
            try {
                await loadDescopeScripts()
                const sdkInstance = initSDK()
                setSdk(sdkInstance)
 
                if (sdkInstance) {
                    const sessionToken = sdkInstance.getSessionToken()
                    const isExpired = sessionToken
                        ? sdkInstance.isJwtExpired(sessionToken)
                        : true
 
                    if (!sessionToken || isExpired) {
                        try {
                            await sdkInstance.refresh()
                        } catch (e) {
                            console.warn("Token refresh failed", e)
                            setIsAuthenticated(false)
                            setIsReady(true)
                            return
                        }
                    }
 
                    const freshToken = sdkInstance.getSessionToken()
                    const stillExpired = freshToken
                        ? sdkInstance.isJwtExpired(freshToken)
                        : true
 
                    setIsAuthenticated(!!freshToken && !stillExpired)
                } else {
                    setIsAuthenticated(false)
                }
            } catch (err) {
                console.error("Descope init failed", err)
                setIsAuthenticated(false)
            } finally {
                setIsReady(true)
            }
        }
 
        init()
    }, [])
 
    useEffect(() => {
        if (!isReady) return
 
        if (hidden && !isAuthenticated && redirect) {
            window.location.href = redirectURL
        } else if (!hidden && isAuthenticated && redirect) {
            window.location.href = redirectURL
        }
    }, [isReady, isAuthenticated, hidden, redirect, redirectURL])
 
    if (!isReady) return <div>Loading...</div>
 
    if (hidden) {
        return isAuthenticated ? <Component {...props} /> : null
    } else {
        return !isAuthenticated ? <Component {...props} /> : null
    }
}
 
const LogoutButtonContent = ({
    Component,
    ...props
}: {
    Component: ComponentType<any>
}) => {
    const [isReady, setIsReady] = useState(false)
 
    useEffect(() => {
        loadDescopeScripts()
            .then(() => {
                initSDK()
                setIsReady(true)
            })
            .catch(() => {})
    }, [])
 
    const handleLogout = useCallback(() => {
        logout()
    }, [])
 
    if (!isReady) return null
    return <Component {...props} onClick={handleLogout} />
}
 
export const protectedComponent = (
    Component: ComponentType<any>
): ComponentType<any> => {
    return (props) => (
        <ClientSideWrapper>
            <React.StrictMode>
                <AuthWrapperContent
                    Component={Component}
                    hidden={true}
                    redirect={false}
                    redirectURL=""
                    {...props}
                />
            </React.StrictMode>
        </ClientSideWrapper>
    )
}
 
export const unprotectedComponent = (
    Component: ComponentType<any>
): ComponentType<any> => {
    return (props) => (
        <ClientSideWrapper>
            <React.StrictMode>
                <AuthWrapperContent
                    Component={Component}
                    hidden={false}
                    redirect={false}
                    redirectURL=""
                    {...props}
                />
            </React.StrictMode>
        </ClientSideWrapper>
    )
}
 
export const protectedPage = (
    Component: ComponentType<any>
): ComponentType<any> => {
    return (props) => (
        <ClientSideWrapper>
            <React.StrictMode>
                <AuthWrapperContent
                    Component={Component}
                    hidden={true}
                    redirect={true}
                    redirectURL={redirectPage}
                    {...props}
                />
            </React.StrictMode>
        </ClientSideWrapper>
    )
}
 
export const unprotectedPage = (
    Component: ComponentType<any>
): ComponentType<any> => {
    return (props) => (
        <ClientSideWrapper>
            <React.StrictMode>
                <AuthWrapperContent
                    Component={Component}
                    hidden={false}
                    redirect={true}
                    redirectURL={redirectPage}
                    {...props}
                />
            </React.StrictMode>
        </ClientSideWrapper>
    )
}
 
export const logoutButton = (
    Component: ComponentType<any>
): ComponentType<any> => {
    return (props) => (
        <ClientSideWrapper>
            <React.StrictMode>
                <LogoutButtonContent Component={Component} {...props} />
            </React.StrictMode>
        </ClientSideWrapper>
    )
}
export function RefreshSession(): Override {
    initSDK() // Call this at the top of the page via the override
    return {}
}

Replace <your-project-id> with your DescopeD and <your-redirect-url> with the URL of the page you want unauthenticated users to be redirected to (usually your login page), and save the file.

4. Apply Overrides

You will have to apply a refresh override on each page of your Framer project so that the user's session token is correctly refreshed.

To apply the refresh override:

  1. Select a page
  2. Navigate to "Code Overrides" at the bottom of the toolbar on the right
  3. Select "DescopeOverrides" as the file, and "RefreshSession" as the override.

The Descope Overrides file also includes overrides that you can utilize to protect your pages and components from unauthenticated users. You can learn more about those overrides here.

Was this helpful?

On this page