Skip to content

New App - Klipfolio Power Metrics Dashboard with Passcode Access#238

Draft
salmanfarisvp wants to merge 1 commit intomasterfrom
new/klipfolio-power-metrics-dashboard-edge-app
Draft

New App - Klipfolio Power Metrics Dashboard with Passcode Access#238
salmanfarisvp wants to merge 1 commit intomasterfrom
new/klipfolio-power-metrics-dashboard-edge-app

Conversation

@salmanfarisvp
Copy link
Collaborator

@salmanfarisvp salmanfarisvp commented Apr 30, 2025

User description

Access Privated Passcode enabled - Klipfolio Power Metrics Dashboard over Edge App.

image

ToDo:

  • Fix CORS Issue
  • Fix IFrame issue
  • Adde Sentry for logs

PR Type

Enhancement


Description

Add Klipfolio dashboard HTML with iframe
Implement passcode injection and proxy logic
Provide full-screen CSS styling
Add Screenly manifest and instance configurations


Changes walkthrough 📝

Relevant files
Formatting
styles.css
Add full-screen styling CSS                                                           

edge-apps/klipfolio-power-metrics-dashboard/styles.css

  • Added placeholder CSS file for reference
  • Defined full-screen body and html styles
  • Styled iframe to occupy full screen
  • +17/-0   
    Enhancement
    script.js
    Implement iframe URL and passcode injection                           

    edge-apps/klipfolio-power-metrics-dashboard/script.js

  • Initialize dashboard iframe using settings
  • Use Screenly CORS proxy or fallback URL
  • Inject passcode into iframe input and submit
  • Handle load timing and cross-origin errors
  • +68/-0   
    index.html
    Add HTML skeleton with iframe                                                       

    edge-apps/klipfolio-power-metrics-dashboard/index.html

  • Added HTML skeleton for Klipfolio dashboard
  • Included iframe and linked script and CSS
  • +14/-0   
    Configuration changes
    instance.yml
    Add app instance configuration                                                     

    edge-apps/klipfolio-power-metrics-dashboard/instance.yml

  • Added instance configuration for edge app
  • Defined syntax, id, and name fields
  • +4/-0     
    screenly.yml
    Add Screenly manifest settings                                                     

    edge-apps/klipfolio-power-metrics-dashboard/screenly.yml

  • Defined Screenly manifest for edge app
  • Configured dashboard_url and passcode settings
  • +15/-0   

    Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @github-actions
    Copy link

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Cross-Origin Access

    Injecting content into an external iframe will fail under cross-origin policy. Ensure the dashboard and host share the same origin or use a messaging API.

    let iframeDoc;
    try {
        iframeDoc = iframe.contentDocument || iframe.contentWindow.document
    } catch (e) {
        console.error("Cannot access iframe content due to cross-origin restrictions", e)
        return
    }
    Missing Null Check

    The code assumes the submit button always exists. Add a null check before calling click() to avoid runtime errors.

    const button = iframeDoc.getElementById('submit')
    button.click()
    console.log("Clicked submit button")
    Race Conditions

    Using fixed timeouts for loading screenly.js and the iframe can lead to unpredictable behavior. Consider event-based or promise-based loading.

    setTimeout(function() {
        // Get the dashboard iframe
        const dashboard = document.getElementById('dashboard')
    
        // Construct the URL after screenly.js has loaded
        let dashboardUrl
        try {
            // Try to use Screenly's CORS proxy
            dashboardUrl = screenly.cors_proxy_url + encodeURIComponent(dashboardUrlRaw)
        } catch (e) {
            // Fallback to direct URL if screenly is not available
            console.error("Error accessing screenly.cors_proxy_url, using direct URL", e)
            dashboardUrl = dashboardUrlRaw;
        }
    
        // Set dashboard URL
        dashboard.src = dashboardUrl
    
        // Attempt to inject passcode after iframe loads
        dashboard.onload = function() {
            setTimeout(injectPasscodeIntoIframe, 1000)
        };
    }, 500) // 500ms delay for screenly.js to load

    @github-actions
    Copy link

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Validate dashboard URL

    Use an empty default for the dashboard URL and bail out early if it’s not provided,
    avoiding loading an invalid placeholder string into the iframe.

    edge-apps/klipfolio-power-metrics-dashboard/script.js [4]

    -const dashboardUrlRaw = screenly.settings.dashboard_url || 'Dashboard URL not set'
    +const dashboardUrlRaw = screenly.settings.dashboard_url;
    +if (!dashboardUrlRaw) {
    +    console.error("Dashboard URL not set");
    +    return;
    +}
    Suggestion importance[1-10]: 6

    __

    Why: Bailing early on a missing URL prevents loading an invalid placeholder string into the iframe, improving robustness.

    Low
    Check iframe existence

    Add a guard after retrieving the dashboard iframe to ensure the element exists
    before setting properties on it. This prevents null reference errors if the iframe
    ID is incorrect or missing.

    edge-apps/klipfolio-power-metrics-dashboard/script.js [12]

    -const dashboard = document.getElementById('dashboard')
    +const dashboard = document.getElementById('dashboard');
    +if (!dashboard) {
    +    console.error("Dashboard iframe element not found");
    +    return;
    +}
    Suggestion importance[1-10]: 5

    __

    Why: The guard ensures dashboard is non-null before using it, preventing potential runtime errors when setting properties.

    Low
    Verify submit button exists

    Add a check to verify the submit button exists before calling .click() to prevent
    runtime errors if the button ID changes or is missing.

    edge-apps/klipfolio-power-metrics-dashboard/script.js [59-60]

    -const button = iframeDoc.getElementById('submit')
    -button.click()
    +const button = iframeDoc.getElementById('submit');
    +if (button) {
    +    button.click();
    +    console.log("Clicked submit button");
    +} else {
    +    console.error("Submit button not found");
    +}
    Suggestion importance[1-10]: 5

    __

    Why: Checking for the submit button before calling .click() prevents errors if the button is missing or renamed.

    Low
    General
    Use load event listener

    Replace the assignment to onload with an event listener to avoid overwriting other
    load handlers and ensure better event management.

    edge-apps/klipfolio-power-metrics-dashboard/script.js [29-31]

    -dashboard.onload = function() {
    -    setTimeout(injectPasscodeIntoIframe, 1000)
    -};
    +dashboard.addEventListener('load', () => {
    +    setTimeout(injectPasscodeIntoIframe, 1000);
    +});
    Suggestion importance[1-10]: 4

    __

    Why: Using addEventListener avoids overwriting other load handlers and is a more flexible event management pattern.

    Low

    Copy link
    Contributor

    Copilot AI left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Pull request overview

    This PR introduces a new edge app for Screenly that embeds a Klipfolio Power Metrics dashboard with passcode-protected access. The app attempts to load a dashboard via iframe and automatically inject a passcode to bypass the authentication prompt.

    Changes:

    • New Klipfolio edge app with HTML, JavaScript, and CSS files for iframe-based dashboard embedding
    • Configuration files (screenly.yml and instance.yml) to define app settings and metadata
    • JavaScript logic to handle CORS proxy URLs and attempt automated passcode injection into the iframe

    Reviewed changes

    Copilot reviewed 5 out of 5 changed files in this pull request and generated 11 comments.

    Show a summary per file
    File Description
    index.html HTML skeleton with iframe element and script/style links
    script.js JavaScript for iframe initialization, CORS proxy handling, and passcode injection attempt
    styles.css Full-screen CSS styling for iframe display
    screenly.yml App manifest defining settings for dashboard URL and passcode
    instance.yml Instance configuration with app ID and name

    💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

    Comment on lines +7 to +12
    <link rel="stylesheet" href="styles.css">
    </head>
    <body>
    <iframe id="dashboard" title="Klipfolio Dashboard"></iframe>
    <script src="screenly.js?version=1" defer></script>
    <script src="script.js"></script>
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    The CSS and JavaScript files should be organized in a static/ folder structure following the codebase convention. All other edge apps in this repository (iframe, clock, weather, etc.) use static/css/, static/js/, and static/images/ subdirectories rather than placing assets in the root directory. This improves organization and maintainability.

    Suggested change
    <link rel="stylesheet" href="styles.css">
    </head>
    <body>
    <iframe id="dashboard" title="Klipfolio Dashboard"></iframe>
    <script src="screenly.js?version=1" defer></script>
    <script src="script.js"></script>
    <link rel="stylesheet" href="static/css/styles.css">
    </head>
    <body>
    <iframe id="dashboard" title="Klipfolio Dashboard"></iframe>
    <script src="static/js/screenly.js?version=1" defer></script>
    <script src="static/js/script.js"></script>

    Copilot uses AI. Check for mistakes.
    <body>
    <iframe id="dashboard" title="Klipfolio Dashboard"></iframe>
    <script src="screenly.js?version=1" defer></script>
    <script src="script.js"></script>
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    The custom script should be loaded with the defer attribute. Following the codebase convention seen in other edge apps, scripts that depend on screenly.js should use defer to ensure proper loading order and prevent race conditions where the script tries to access the screenly object before it's available.

    Suggested change
    <script src="script.js"></script>
    <script src="script.js" defer></script>

    Copilot uses AI. Check for mistakes.
    Comment on lines +1 to +2
    /* This file is no longer needed as all styles are defined in the HTML */
    /* The file is kept for reference in case styles need to be extracted later */
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    The inline comment states this CSS file is no longer needed and kept for reference, but the file is still linked in index.html. Either remove the misleading comment and use this file properly, or remove the file and the link tag. Having a "placeholder" file that claims to not be used creates confusion and technical debt.

    Suggested change
    /* This file is no longer needed as all styles are defined in the HTML */
    /* The file is kept for reference in case styles need to be extracted later */
    /* Styles for the Klipfolio Power Metrics dashboard layout */
    /* Defines full-screen behavior for the page and embedded dashboard iframe */

    Copilot uses AI. Check for mistakes.
    Comment on lines +60 to +61
    button.click()
    console.log("Clicked submit button")
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Missing null check for the button element before calling click(). If the submit button with id 'submit' doesn't exist in the iframe document (line 59), the code will throw an error when trying to call button.click() on line 60. Add a null check similar to the one used for the input field.

    Suggested change
    button.click()
    console.log("Clicked submit button")
    if (button) {
    button.click()
    console.log("Clicked submit button")
    } else {
    console.log("Submit button not found");
    }

    Copilot uses AI. Check for mistakes.

    // Attempt to inject passcode after iframe loads
    dashboard.onload = function() {
    setTimeout(injectPasscodeIntoIframe, 1000)
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    The script does not call screenly.signalReadyForRendering() to indicate when the app is ready for display. When ready_signal is set to true in the manifest, the app should call this method after the iframe loads successfully. This helps Screenly know when to display the content. Reference the iframe edge app implementation which calls this method in the iframe's onload handler.

    Suggested change
    setTimeout(injectPasscodeIntoIframe, 1000)
    setTimeout(function() {
    // First, try to inject the passcode
    injectPasscodeIntoIframe()
    // Then signal to Screenly that the app is ready for rendering
    try {
    if (typeof screenly !== 'undefined' &&
    typeof screenly.signalReadyForRendering === 'function') {
    screenly.signalReadyForRendering()
    }
    } catch (e) {
    console.error("Error calling screenly.signalReadyForRendering:", e)
    }
    }, 1000)

    Copilot uses AI. Check for mistakes.
    Comment on lines +9 to +14
    optional: true
    help_text: The URL for the dashboard
    dashboard_passcode:
    type: secret
    title: Dashboard Passcode
    optional: true
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    The dashboard_url and dashboard_passcode settings are marked as optional, but the code does not handle the case where they are not provided. In script.js lines 4-5, fallback messages are set ('Dashboard URL not set', 'Passcode not set'), but these will be used as actual values rather than triggering an error. Since the app cannot function without these values, they should be marked as 'optional: false' in the manifest.

    Suggested change
    optional: true
    help_text: The URL for the dashboard
    dashboard_passcode:
    type: secret
    title: Dashboard Passcode
    optional: true
    optional: false
    help_text: The URL for the dashboard
    dashboard_passcode:
    type: secret
    title: Dashboard Passcode
    optional: false

    Copilot uses AI. Check for mistakes.
    Comment on lines +36 to +68
    function injectPasscodeIntoIframe() {
    try {
    const iframe = document.getElementById('dashboard')

    // Try to access the iframe document
    let iframeDoc;
    try {
    iframeDoc = iframe.contentDocument || iframe.contentWindow.document
    } catch (e) {
    console.error("Cannot access iframe content due to cross-origin restrictions", e)
    return
    }

    // Get passcode input
    const input = iframeDoc.getElementById('passcode')

    // If passcode input exists, inject passcode and submit
    if (input) {
    console.log("Found passcode field, injecting code");
    input.value = passcode
    input.dispatchEvent(new Event('input', { bubbles: true }))

    // Get the submit button and click it
    const button = iframeDoc.getElementById('submit')
    button.click()
    console.log("Clicked submit button")
    } else {
    console.log("Passcode input field not found");
    }
    } catch (e) {
    console.error("Error in injectPasscodeIntoIframe:", e)
    }
    } No newline at end of file
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    The approach of injecting passcode into an iframe will not work due to cross-origin security restrictions. Lines 43-46 attempt to catch cross-origin errors, but even if the iframe loads through the CORS proxy, the Same-Origin Policy prevents accessing or manipulating the iframe's DOM from the parent window. This fundamental security restriction means the passcode injection functionality (lines 36-68) cannot work as designed. The TODO comment in the PR description acknowledges CORS and iframe issues that need to be resolved.

    Copilot uses AI. Check for mistakes.
    Comment on lines +9 to +32
    // Wait a short time for screenly.js to load
    setTimeout(function() {
    // Get the dashboard iframe
    const dashboard = document.getElementById('dashboard')

    // Construct the URL after screenly.js has loaded
    let dashboardUrl
    try {
    // Try to use Screenly's CORS proxy
    dashboardUrl = screenly.cors_proxy_url + encodeURIComponent(dashboardUrlRaw)
    } catch (e) {
    // Fallback to direct URL if screenly is not available
    console.error("Error accessing screenly.cors_proxy_url, using direct URL", e)
    dashboardUrl = dashboardUrlRaw;
    }

    // Set dashboard URL
    dashboard.src = dashboardUrl

    // Attempt to inject passcode after iframe loads
    dashboard.onload = function() {
    setTimeout(injectPasscodeIntoIframe, 1000)
    };
    }, 500) // 500ms delay for screenly.js to load
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    The 500ms delay for screenly.js to load is arbitrary and may cause race conditions. The screenly.js script is loaded with the defer attribute, which means it will execute after the DOM is parsed but before the load event. Using a setTimeout with a fixed delay is unreliable. Instead, listen for a screenly-specific event or check for the existence of the screenly object, or move the script loading to use proper defer attributes as seen in other edge apps.

    Suggested change
    // Wait a short time for screenly.js to load
    setTimeout(function() {
    // Get the dashboard iframe
    const dashboard = document.getElementById('dashboard')
    // Construct the URL after screenly.js has loaded
    let dashboardUrl
    try {
    // Try to use Screenly's CORS proxy
    dashboardUrl = screenly.cors_proxy_url + encodeURIComponent(dashboardUrlRaw)
    } catch (e) {
    // Fallback to direct URL if screenly is not available
    console.error("Error accessing screenly.cors_proxy_url, using direct URL", e)
    dashboardUrl = dashboardUrlRaw;
    }
    // Set dashboard URL
    dashboard.src = dashboardUrl
    // Attempt to inject passcode after iframe loads
    dashboard.onload = function() {
    setTimeout(injectPasscodeIntoIframe, 1000)
    };
    }, 500) // 500ms delay for screenly.js to load
    // Get the dashboard iframe
    const dashboard = document.getElementById('dashboard')
    // Construct the URL after screenly.js has loaded
    let dashboardUrl
    try {
    // Try to use Screenly's CORS proxy
    dashboardUrl = screenly.cors_proxy_url + encodeURIComponent(dashboardUrlRaw)
    } catch (e) {
    // Fallback to direct URL if screenly is not available
    console.error("Error accessing screenly.cors_proxy_url, using direct URL", e)
    dashboardUrl = dashboardUrlRaw;
    }
    // Set dashboard URL
    dashboard.src = dashboardUrl
    // Attempt to inject passcode after iframe loads
    dashboard.onload = function() {
    setTimeout(injectPasscodeIntoIframe, 1000)
    };

    Copilot uses AI. Check for mistakes.
    Comment on lines +4 to +5
    const dashboardUrlRaw = screenly.settings.dashboard_url || 'Dashboard URL not set'
    const passcode = screenly.settings.dashboard_passcode || 'Passcode not set'
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    The settings are accessed at the module level (lines 4-5) before the window load event, which may execute before screenly.js has fully initialized. This could result in undefined values or errors. The settings should be accessed inside the event handler after ensuring screenly is available, similar to how they're accessed on line 18 within the try-catch block.

    Copilot uses AI. Check for mistakes.
    Comment on lines +1 to +2
    /* global screenly, Sentry */
    /* eslint-disable-next-line no-unused-vars, no-useless-catch */
    Copy link

    Copilot AI Feb 23, 2026

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    The eslint-disable comment on line 2 references 'no-unused-vars' and 'no-useless-catch', but there are no try-catch blocks that simply rethrow errors in this file, making 'no-useless-catch' unnecessary. Additionally, the Sentry global is declared but never used in the code. Either remove the unused eslint-disable rules or implement the Sentry logging mentioned in the PR TODO list.

    Copilot uses AI. Check for mistakes.
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants