Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions data/screenly_inject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// JavaScript injected into the remote entrypoint on every load.
//
// Edge App settings and secrets are available as `screenly_settings`.
// Use this to authenticate the remote page without baking credentials
// into the script.
//
// This script runs AFTER the page has fully loaded — DOMContentLoaded /
// window.load have already fired, so manipulate the DOM directly. Don't
// wrap your code in `document.addEventListener('DOMContentLoaded', ...)`
// (the listener will be registered too late and never fire).
//
// ---- Helpers --------------------------------------------------------------

// Set an input's value and notify listeners.
function setValue(selector, value) {
const el = document.querySelector(selector);
if (!el) return false;
el.value = value;
el.dispatchEvent(new Event('change', { bubbles: true }));
return true;
}

// Set an input's value through the native setter so frameworks like React
// pick it up. Use this when `setValue` doesn't take effect.
function setReactValue(selector, value) {
const el = document.querySelector(selector);
if (!el) return false;
const setter = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
'value'
).set;
setter.call(el, value);
el.dispatchEvent(new Event('input', { bubbles: true }));
return true;
}

// Set a cookie scoped to `domain` and reload the page. No-ops if already set.
function setCookie(key, value, domain) {
if (document.cookie.split('; ').some(c => c.startsWith(key + '='))) return;
document.cookie = `${key}=${value}; path=/; domain=${domain}`;
location.reload();
}

// Run `fn` only when the current path matches `path`.
function onPath(path, fn) {
if (location.pathname === path) fn();
}

// ---- Examples (uncomment one) --------------------------------------------

// Form-fill login on /login:
//
// onPath('/login', () => {
// if (setValue('input[name="username"]', screenly_settings.username) &&
// setValue('input[name="password"]', screenly_settings.password)) {
// document.querySelector('button[type="submit"]').click();
// } else {
// setTimeout(arguments.callee, 1000); // retry until the form is rendered
// }
// });

// SSO via cookie:
//
// setCookie('session_id', screenly_settings.session_id, '.example.com');

// Override fetch to attach a Bearer token to every request the page makes:
//
// const token = screenly_settings.api_token;
// const originalFetch = window.fetch;
// window.fetch = (input, init = {}) => {
// init.headers = { ...(init.headers || {}), Authorization: `Bearer ${token}` };
// return originalFetch(input, init);
// };

// ---- Default: log what's available ---------------------------------------

console.log('screenly_settings:', screenly_settings);
1 change: 1 addition & 0 deletions docs/CommandLineHelp.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ Creates an Edge App in the store
* `-n`, `--name <NAME>` — Edge App name
* `-p`, `--path <PATH>` — Path to the directory with the manifest. Defaults to the current working directory
* `-i`, `--in-place` — Use an existing Edge App directory with the manifest and index.html
* `-e`, `--entrypoint <ENTRYPOINT>` — Remote entrypoint URL. When set, the created app uses entrypoint.type = remote-global with this URL and a starter `screenly_inject.js` is dropped next to `screenly.yml`. The inject file is shipped with each deploy and the player runs it on every load



Expand Down
49 changes: 49 additions & 0 deletions docs/EdgeApps.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,55 @@ When you run the screenly edge-app create command, two files will be created in
- `screenly.yml`
- `index.html`

#### Remote entrypoint (`--entrypoint`)

If your Edge App should load a remote URL instead of a bundled `index.html`,
pass the URL at create time:

```shell
$ screenly edge-app create --name hello-remote --entrypoint https://example.com/app
```

This sets `entrypoint.type: remote-global` and `entrypoint.uri` in
`screenly.yml`, skips writing the `index.html` stub, and drops a starter
`screenly_inject.js` next to the manifest.

The main use case for remote entrypoints is **JS injection** — a snippet
of JavaScript that the player executes against the loaded remote page on
every load. The most common reason to reach for this is **authenticating
to the remote page**: overriding `fetch`/`XHR` to attach `Authorization`
headers, setting cookies or `localStorage` tokens before the page boots,
auto-filling and submitting login forms, etc.

To use it, edit `screenly_inject.js` and run `screenly edge-app deploy`.
The file is bundled as part of the Edge App's revision; the player picks
it up automatically on remote-entrypoint Edge Apps. If you don't want JS
injection, delete the file before deploying.

To pass credentials securely into the injected script, define them as
Edge App **settings** or **secrets** (see [Settings](#settings) below)
and read them as `screenly_settings.<key>` inside `screenly_inject.js`.
The player provides `screenly_settings` to the script at runtime, so
secret values don't appear in the page source or in the injection
script you commit to source control.

Example `screenly_inject.js` that adds a Bearer token to every outbound
request from the remote page:

```js
const token = screenly_settings.api_token;
const originalFetch = window.fetch;
window.fetch = (input, init = {}) => {
init.headers = { ...(init.headers || {}), Authorization: `Bearer ${token}` };
return originalFetch(input, init);
};
```

Only `remote-global` (one URL shared across all instances) can be configured
via `--entrypoint`. For `remote-local` (per-instance URLs), set
`entrypoint.type: remote-local` in `screenly.yml` and `entrypoint_uri` per
instance in `instance.yml`.

`screenly.yml` contains the metadata. In this file, you can define settings, secrets, and various other metadata. In our "Hello World" example, we have a single setting called `greeting`, which is used in the Edge App.

`index.html` is our entry point. It is what the client (i.e., the player) will load. This particular file is very simple and just includes some styling and various metadata examples.
Expand Down
26 changes: 19 additions & 7 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,13 @@ pub enum EdgeAppCommands {
/// Use an existing Edge App directory with the manifest and index.html.
#[arg(short, long, action = clap::ArgAction::SetTrue)]
in_place: Option<bool>,
/// Remote entrypoint URL. When set, the created app uses
/// entrypoint.type = remote-global with this URL and a starter
/// `screenly_inject.js` is dropped next to `screenly.yml`. The
/// inject file is shipped with each deploy and the player runs it
/// on every load.
#[arg(short, long)]
entrypoint: Option<String>,
},

/// Lists your Edge Apps.
Expand Down Expand Up @@ -908,13 +915,8 @@ pub fn handle_cli_edge_app_command(command: &EdgeAppCommands) {
name,
path,
in_place,
entrypoint,
} => {
let create_func = if in_place.unwrap_or(false) {
commands::edge_app::EdgeAppCommand::create_in_place
} else {
commands::edge_app::EdgeAppCommand::create
};

let manifest_path = match transform_edge_app_path_to_manifest(path) {
Ok(path) => path,
Err(e) => {
Expand All @@ -923,7 +925,17 @@ pub fn handle_cli_edge_app_command(command: &EdgeAppCommands) {
}
};

match create_func(&edge_app_command, name, manifest_path.as_path()) {
let result = if in_place.unwrap_or(false) {
if entrypoint.is_some() {
eprintln!("--entrypoint cannot be used with --in-place.");
std::process::exit(1);
}
edge_app_command.create_in_place(name, manifest_path.as_path())
} else {
edge_app_command.create(name, manifest_path.as_path(), entrypoint.clone())
};

match result {
Ok(()) => {
println!("Edge App successfully created.");
}
Expand Down
Loading
Loading