Merge remote-tracking branch 'origin/feat/premiere-installer'

This commit is contained in:
Zac Gaetano 2026-05-24 12:03:46 -04:00
commit 543248b8c2
29 changed files with 792 additions and 173 deletions

3
.gitignore vendored
View file

@ -14,6 +14,9 @@ yarn-error.log*
# Build output # Build output
dist/ dist/
build/ build/
# ...but the Premiere panel's packaging pipeline lives at build/ — keep it tracked.
!services/premiere-plugin/build/
!services/premiere-plugin/build/**
# OS # OS
.DS_Store .DS_Store

View file

@ -29,15 +29,28 @@ and the panel relinks Premiere to the hi-res original.
## Premiere panel install ## Premiere panel install
``` Grab the latest release artifact and run it — the installer handles the file
# macOS copy, registry/plist debug-mode flip, and removes any legacy
rsync -a services/premiere-plugin/ ~/Library/Application\ Support/Adobe/CEP/extensions/com.wilddragon.mam.panel/ `com.wilddragon.mam.panel` install:
# Windows - **Windows:** `dragonflight-premiere-panel-<version>-windows-setup.exe`
robocopy services\premiere-plugin %APPDATA%\Adobe\CEP\extensions\com.wilddragon.mam.panel /MIR - **macOS / Win:** `dragonflight-premiere-panel-<version>.zxp` — install via
[Anastasiy's ZXP Installer](https://install.anastasiy.com/) (free GUI)
Releases live at
<https://forge.wilddragon.net/zgaetano/dragonflight/releases>.
Building locally (requires Windows for the `.exe`, any OS for the `.zxp`):
```
cd services/premiere-plugin/build
npm install
powershell -File build-all.ps1 # or: node build-zxp.mjs
``` ```
Enable CEP debug: The Windows installer takes care of `PlayerDebugMode`. If you installed the
ZXP and the panel does not appear in **Window → Extensions**, enable debug
mode manually:
``` ```
# macOS # macOS

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ExtensionList> <ExtensionList>
<Extension Id="com.wilddragon.mam.panel"> <Extension Id="net.wilddragon.dragonflight.panel">
<HostList> <HostList>
<Host Name="PPRO" Port="7737"/> <Host Name="PPRO" Port="7737"/>
</HostList> </HostList>

View file

@ -1,71 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ExtensionManifest xmlns="http://ns.adobe.com/CSXS/manifest" <ExtensionManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ExtensionBundleId="net.wilddragon.dragonflight.panel"
xsi:schemaLocation="http://ns.adobe.com/CSXS/manifest http://ns.adobe.com/CSXS/manifest"> ExtensionBundleName="Wild Dragon MAM"
<ExtensionBundleVersion>1.0.0</ExtensionBundleVersion> ExtensionBundleVersion="1.0.1"
<ExtensionBundleId>com.wilddragon.mam.panel</ExtensionBundleId> Version="7.0">
<Author>Wild Dragon MAM</Author> <ExtensionList>
<Extension Id="net.wilddragon.dragonflight.panel" Version="1.0" />
<License> </ExtensionList>
<Text>Copyright 2026 Wild Dragon. All rights reserved.</Text> <ExecutionEnvironment>
</License> <HostList>
<Host Name="PPRO" Version="[22.0,99.9]" />
<AbstractionLayers> </HostList>
<AbstractionLayerRequest Name="PProPanel"/> <LocaleList>
</AbstractionLayers> <Locale Code="All" />
</LocaleList>
<Extension Id="com.wilddragon.mam.panel"> <RequiredRuntimeList>
<Type>Panel</Type> <RequiredRuntime Name="CSXS" Version="10.0" />
<HostList> </RequiredRuntimeList>
<Host Name="PPRO" Version="[22.0,99.9]"/> </ExecutionEnvironment>
</HostList> <DispatchInfoList>
<Extension Id="net.wilddragon.dragonflight.panel">
<LocalizedStrings> <DispatchInfo>
<LocalizedString locale="en_US">
<Token name="ExtensionWindowTitle">Wild Dragon MAM</Token>
<Token name="ExtensionShortDescription">Media Asset Management for Premiere Pro</Token>
<Token name="ExtensionLongDescription">Browse, search, and import proxy files from Wild Dragon MAM directly into your Premiere Pro project</Token>
</LocalizedString>
</LocalizedStrings>
<UI>
<Type>Panel</Type>
<Geometry>
<Size>
<Width>400</Width>
<Height>700</Height>
</Size>
<MinSize>
<Width>350</Width>
<Height>500</Height>
</MinSize>
</Geometry>
</UI>
<!--
Resources:
- MainPath: HTML entry point for the CEP panel UI
- ScriptPath: ExtendScript (.jsx) auto-loaded into the Premiere Pro host
- --enable-nodejs: unlocks Node.js require() for binary file downloads
- --mixed-context: allows Node.js and DOM to share the same V8 context
-->
<Resources> <Resources>
<MainPath>index.html</MainPath> <MainPath>./index.html</MainPath>
<ScriptPath>jsx/premiere.jsx</ScriptPath> <ScriptPath>./jsx/premiere.jsx</ScriptPath>
<CEFCommandLine> <CEFCommandLine>
<Parameter>--enable-nodejs</Parameter> <Parameter>--enable-nodejs</Parameter>
<Parameter>--mixed-context</Parameter> <Parameter>--mixed-context</Parameter>
</CEFCommandLine> <Parameter>--allow-file-access-from-files</Parameter>
<Parameter>--allow-file-access</Parameter>
</CEFCommandLine>
</Resources> </Resources>
<Lifecycle>
<CEPVersion>11.0</CEPVersion> <AutoVisible>true</AutoVisible>
<RequiredRuntimeVersion>11.0</RequiredRuntimeVersion> </Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>Wild Dragon MAM</Menu>
<Geometry>
<Size>
<Height>700</Height>
<Width>400</Width>
</Size>
<MinSize>
<Height>500</Height>
<Width>385</Width>
</MinSize>
<MaxSize>
<Height>2000</Height>
<Width>800</Width>
</MaxSize>
</Geometry>
</UI>
</DispatchInfo>
</Extension> </Extension>
</DispatchInfoList>
<ExtensionList/>
<ExecutionEnvironment>
<HostList>
<Host Name="PPRO" Version="[22.0,99.9]"/>
</HostList>
</ExecutionEnvironment>
</ExtensionManifest> </ExtensionManifest>

View file

@ -9,7 +9,7 @@ FILE STRUCTURE AND PURPOSES
CSXS/manifest.xml CSXS/manifest.xml
----------------- -----------------
- CEP extension manifest file required by Adobe - CEP extension manifest file required by Adobe
- Defines extension ID: com.wilddragon.mam.panel - Defines extension ID: net.wilddragon.dragonflight.panel
- Specifies Premiere Pro (PPRO) version compatibility: 22.0 to 99.9 - Specifies Premiere Pro (PPRO) version compatibility: 22.0 to 99.9
- Sets panel dimensions: 400x700 (responsive 350-500px width) - Sets panel dimensions: 400x700 (responsive 350-500px width)
- Declares CEP version 11.0 requirement - Declares CEP version 11.0 requirement
@ -173,20 +173,23 @@ PREMIERE PRO REQUIREMENTS
DEPLOYMENT INSTRUCTIONS DEPLOYMENT INSTRUCTIONS
======================= =======================
1. Copy entire com.wilddragon.mam.panel directory to CEP extensions folder: Use the installer artifacts from the latest release:
Windows: C:\Users\USERNAME\AppData\Roaming\Adobe\CEP\extensions\ https://forge.wilddragon.net/zgaetano/dragonflight/releases
macOS: ~/Library/Application Support/Adobe/CEP/extensions/
2. Enable unsigned extension debugging (Windows Registry): Windows: dragonflight-premiere-panel-<version>-windows-setup.exe
HKEY_CURRENT_USER\Software\Adobe\CSXS.11 Double-click. Installer copies bundle to
Create DWORD: PlayerDebugMode = 1 %APPDATA%\Adobe\CEP\extensions\net.wilddragon.dragonflight.panel\,
sets PlayerDebugMode=1 for CSXS 8..13, and removes any legacy
com.wilddragon.mam.panel install.
3. Enable unsigned extension debugging (macOS): macOS: dragonflight-premiere-panel-<version>.zxp
defaults write /Library/Preferences/com.adobe.CSXS.11 PlayerDebugMode 1 Install with Anastasiy's ZXP Installer
(https://install.anastasiy.com/), then run once in Terminal:
defaults write com.adobe.CSXS.11 PlayerDebugMode 1
4. Restart Premiere Pro Restart Premiere Pro, then: Window > Extensions > Wild Dragon MAM
5. Access panel via Window > Extensions > Wild Dragon MAM To build the installers locally, see services/premiere-plugin/build/README.md.
DEVELOPMENT NOTES DEVELOPMENT NOTES
================= =================

View file

@ -1,30 +1,40 @@
# Wild Dragon MAM - Premiere Pro Panel Quick Start # Wild Dragon MAM - Premiere Pro Panel Quick Start
## Installation (5 minutes) ## Installation (1 minute)
Grab the installer from
<https://forge.wilddragon.net/zgaetano/dragonflight/releases>.
### Windows ### Windows
``` ```
1. Copy folder to: 1. Download dragonflight-premiere-panel-<version>-windows-setup.exe
C:\Users\<USERNAME>\AppData\Roaming\Adobe\CEP\extensions\com.wilddragon.mam.panel\ 2. Double-click it. Next -> Finish.
3. Restart Premiere Pro.
2. Open Registry Editor (regedit.exe)
Navigate to: HKEY_CURRENT_USER\Software\Adobe\CSXS.11
Create DWORD: PlayerDebugMode = 1
3. Restart Premiere Pro
``` ```
The installer copies the bundle to `%APPDATA%\Adobe\CEP\extensions\`,
sets `PlayerDebugMode=1` for CSXS 8..13, and offers to remove any legacy
`com.wilddragon.mam.panel` install.
### macOS ### macOS
``` ```
1. Copy folder to: 1. Download dragonflight-premiere-panel-<version>.zxp
~/Library/Application Support/Adobe/CEP/extensions/com.wilddragon.mam.panel/ 2. Open it with Anastasiy's ZXP Installer (https://install.anastasiy.com/)
3. Run once in Terminal:
2. Open Terminal and run: defaults write com.adobe.CSXS.11 PlayerDebugMode 1
defaults write /Library/Preferences/com.adobe.CSXS.11 PlayerDebugMode 1 4. Restart Premiere Pro.
3. Restart Premiere Pro
``` ```
### Building from source
```
cd services/premiere-plugin/build
npm install
powershell -File build-all.ps1
```
See [`build/README.md`](build/README.md).
## Access the Panel ## Access the Panel
Window > Extensions > Wild Dragon MAM Window > Extensions > Wild Dragon MAM
@ -91,8 +101,9 @@ services/premiere-plugin/
│ └── main.js Panel logic │ └── main.js Panel logic
├── jsx/ ├── jsx/
│ └── premiere.jsx Premiere integration │ └── premiere.jsx Premiere integration
├── build/ Installer build pipeline (.zxp + .exe)
├── README.md Full docs ├── README.md Full docs
└── QUICK_START.md This file └── QUICK_START.md This file
``` ```
## Troubleshooting ## Troubleshooting

View file

@ -16,30 +16,41 @@ A professional media asset management (MAM) plugin for Adobe Premiere Pro that a
## Installation ## Installation
### Windows Grab the installer for your platform from the
[latest release](https://forge.wilddragon.net/zgaetano/dragonflight/releases):
1. Copy the entire `com.wilddragon.mam.panel` directory to: | File | Platform | How to install |
``` |------|----------|----------------|
C:\Users\<YourUsername>\AppData\Roaming\Adobe\CEP\extensions\com.wilddragon.mam.panel\ | `dragonflight-premiere-panel-<version>-windows-setup.exe` | Windows | Double-click, Next → Finish. Copies the bundle, sets `PlayerDebugMode`, and removes any legacy `com.wilddragon.mam.panel` install. |
``` | `dragonflight-premiere-panel-<version>.zxp` | macOS + Windows | Install with [Anastasiy's ZXP Installer](https://install.anastasiy.com/) (free GUI). |
2. For unsigned extensions, enable debug mode in the Windows Registry: ### macOS ZXP — one extra step
- Open Registry Editor (`regedit.exe`)
- Navigate to: `HKEY_CURRENT_USER\Software\Adobe\CSXS.11`
- Create a new DWORD value named `PlayerDebugMode` and set it to `1`
- Restart Premiere Pro
### macOS The `.zxp` is self-signed, so macOS editors must enable CEP debug mode once
before the panel will load:
1. Copy the entire `com.wilddragon.mam.panel` directory to: ```
``` defaults write com.adobe.CSXS.11 PlayerDebugMode 1
~/Library/Application Support/Adobe/CEP/extensions/com.wilddragon.mam.panel/ ```
```
2. For unsigned extensions, enable debug mode in the plist: Restart Premiere Pro afterward. The Windows `.exe` installer does this
- Open Terminal automatically for every CEP version Premiere might use (CSXS 8 through 13).
- Run: `defaults write /Library/Preferences/com.adobe.CSXS.11 PlayerDebugMode 1`
- Restart Premiere Pro ### Building from source
If you do not want to install the release artifact, you can run the panel
straight from the source tree. See [`build/README.md`](build/README.md) for
the build pipeline, or copy the directory yourself:
```
# Windows (PowerShell, as the same user that runs Premiere)
robocopy . "$env:APPDATA\Adobe\CEP\extensions\net.wilddragon.dragonflight.panel" /MIR /XD build
# macOS
rsync -a --exclude=build/ ./ ~/Library/Application\ Support/Adobe/CEP/extensions/net.wilddragon.dragonflight.panel/
```
Then enable `PlayerDebugMode` as above.
### Access the Panel ### Access the Panel
@ -47,6 +58,22 @@ A professional media asset management (MAM) plugin for Adobe Premiere Pro that a
2. Go to **Window** > **Extensions** > **Wild Dragon MAM** 2. Go to **Window** > **Extensions** > **Wild Dragon MAM**
3. The panel will open as a floating window on the right side 3. The panel will open as a floating window on the right side
### Upgrading from `com.wilddragon.mam.panel`
The bundle ID changed to `net.wilddragon.dragonflight.panel` as part of the
wild-dragon → dragonflight rename. The Windows installer offers to remove
the old folder for you (checkbox on the install wizard, default on). For ZXP
installs or manual copies, delete the legacy folder yourself or you will see
two panels in **Window → Extensions**:
```
# Windows
Remove-Item -Recurse -Force "$env:APPDATA\Adobe\CEP\extensions\com.wilddragon.mam.panel"
# macOS
rm -rf ~/Library/Application\ Support/Adobe/CEP/extensions/com.wilddragon.mam.panel
```
## Usage ## Usage
### Connect to MAM Server ### Connect to MAM Server
@ -102,7 +129,7 @@ To change the server URL:
## File Structure ## File Structure
``` ```
com.wilddragon.mam.panel/ net.wilddragon.dragonflight.panel/ # installed bundle
├── CSXS/ ├── CSXS/
│ └── manifest.xml # CEP extension manifest │ └── manifest.xml # CEP extension manifest
├── css/ ├── css/
@ -113,8 +140,15 @@ com.wilddragon.mam.panel/
├── jsx/ ├── jsx/
│ └── premiere.jsx # Premiere Pro ExtendScript │ └── premiere.jsx # Premiere Pro ExtendScript
├── index.html # Main panel UI ├── index.html # Main panel UI
├── .debug # CEP debug configuration ├── .debug # CEP debug configuration
└── README.md # This file └── README.md # This file
build/ # not shipped — installer/.zxp build pipeline
├── README.md # how to build .zxp + .exe locally
├── build-all.ps1 # build both artifacts
├── build-zxp.mjs # ZXP signer
├── installer.iss # Inno Setup script
└── ...
``` ```
## API Integration ## API Integration

View file

@ -0,0 +1,4 @@
node_modules/
dist/
stage/
*.log

View file

@ -0,0 +1,86 @@
# Premiere panel installer — build pipeline
Produces two artifacts from `services/premiere-plugin/`:
| File | Platform | How an editor installs it |
|------|----------|----------------------------|
| `dist/dragonflight-premiere-panel-<version>.zxp` | macOS + Windows | Drag into [Anastasiy's ZXP Installer](https://install.anastasiy.com/) |
| `dist/dragonflight-premiere-panel-<version>-windows-setup.exe` | Windows | Double-click, Next → Finish |
The version is read from `CSXS/manifest.xml`'s `<ExtensionBundleVersion>`. To
cut a new release: bump that one number, rebuild, attach the two files to a
Forgejo release tag.
## Prerequisites
| Building | Requires |
|----------|----------|
| `.zxp` only | Node 18+, runs on macOS / Linux / Windows |
| `.exe` | Windows + [Inno Setup 6](https://jrsoftware.org/isinfo.php) on `PATH` (`ISCC.exe`) |
| Both via `build-all.ps1` | Windows + Node 18+ + Inno Setup 6 |
Install Inno Setup with winget:
```
winget install --id JRSoftware.InnoSetup
```
## Build
```
cd services/premiere-plugin/build
npm install
powershell -File build-all.ps1
```
Artifacts land in `services/premiere-plugin/build/dist/`.
To build just one:
```
node build-zxp.mjs # cross-platform .zxp
powershell -File build-installer.ps1 # Windows .exe (needs ISCC.exe)
```
## Signing cert (.zxp)
The first `node build-zxp.mjs` run generates a self-signed cert at
`cert/dragonflight-selfsigned.p12` + passphrase at `cert/cert-passphrase.txt`.
**Commit both files** — they need to be stable across builds so Adobe accepts
ZXP upgrades in place. If you regenerate the cert, every editor who already
installed an older `.zxp` has to uninstall + reinstall.
The cert is self-signed → editors still need `PlayerDebugMode=1`. The Windows
`.exe` installer sets this automatically. ZXP installs on macOS need a manual
`defaults write com.adobe.CSXS.11 PlayerDebugMode 1`.
See `cert/README.md` for cert details + how to regenerate.
## What the installers do
Both install the bundle as `net.wilddragon.dragonflight.panel`. The Windows
`.exe` additionally:
1. Removes any legacy `com.wilddragon.mam.panel` folder (with a consent
checkbox, default on) — avoids editors seeing two panels in
**Window → Extensions**.
2. Sets `HKCU\Software\Adobe\CSXS.{8..13}\PlayerDebugMode = "1"` so unsigned
CEP extensions load.
3. Registers itself in Add/Remove Programs so uninstall reverses both.
## Layout
```
build/
├── README.md ← this file
├── package.json ← devDep: zxp-sign-cmd
├── .gitignore ← ignores dist/ + node_modules/
├── build-all.ps1 ← runs both builders
├── build-zxp.mjs ← Node: generates cert (first run), signs, packages
├── build-installer.ps1 ← wraps ISCC.exe with version + bundle-id flags
├── installer.iss ← Inno Setup script
└── cert/
├── README.md ← how to regenerate
├── dragonflight-selfsigned.p12 ← generated on first build (commit it)
└── cert-passphrase.txt ← generated on first build (commit it)
```

View file

@ -0,0 +1,34 @@
# Dragonflight Premiere panel — build everything
#
# Produces both artifacts in dist/:
# - dragonflight-premiere-panel-<version>.zxp (Mac + Win)
# - dragonflight-premiere-panel-<version>-windows-setup.exe (Win)
#
# Requires: Node 18+, Inno Setup 6 (ISCC.exe on PATH).
# Usage: pwsh -File build-all.ps1
$ErrorActionPreference = 'Stop'
Set-Location $PSScriptRoot
if (-not (Test-Path 'node_modules')) {
Write-Host 'Installing npm deps...'
npm install --no-audit --no-fund
if ($LASTEXITCODE -ne 0) { throw 'npm install failed' }
}
Write-Host ''
Write-Host '=== Building .zxp ==='
node build-zxp.mjs
if ($LASTEXITCODE -ne 0) { throw 'ZXP build failed' }
Write-Host ''
Write-Host '=== Building Windows .exe ==='
& (Join-Path $PSScriptRoot 'build-installer.ps1')
if ($LASTEXITCODE -ne 0) { throw 'Installer build failed' }
Write-Host ''
Write-Host 'All artifacts:'
Get-ChildItem -Path 'dist' | ForEach-Object {
$size = [math]::Round($_.Length / 1KB, 1)
Write-Host (' {0,8} KB {1}' -f $size, $_.Name)
}

View file

@ -0,0 +1,60 @@
# Dragonflight Premiere panel — Windows installer build
#
# Parses the version from ../CSXS/manifest.xml and hands it to ISCC.exe.
# Requires Inno Setup 6 on PATH (winget install JRSoftware.InnoSetup).
#
# Usage: pwsh -File build-installer.ps1
$ErrorActionPreference = 'Stop'
Set-Location $PSScriptRoot
$manifestPath = Join-Path $PSScriptRoot '..\CSXS\manifest.xml'
if (-not (Test-Path $manifestPath)) {
throw "Manifest not found at $manifestPath"
}
# Regex over XML because [xml] is strict about things Adobe CEP tolerates.
# Accept either the element form (<ExtensionBundleVersion>X.Y.Z</...>) or the
# attribute form (ExtensionBundleVersion="X.Y.Z") used by CSXS 7.0+ manifests.
$manifestRaw = Get-Content -Raw -Path $manifestPath
$version = $null
if ($manifestRaw -match '<ExtensionBundleVersion>([^<]+)</ExtensionBundleVersion>') {
$version = $Matches[1].Trim()
} elseif ($manifestRaw -match 'ExtensionBundleVersion\s*=\s*"([^"]+)"') {
$version = $Matches[1].Trim()
}
if (-not $version) {
throw 'Could not read ExtensionBundleVersion from manifest.xml'
}
Write-Host "Dragonflight Premiere panel - Windows installer build v$version"
$iscc = Get-Command 'ISCC.exe' -ErrorAction SilentlyContinue
if (-not $iscc) {
# Common Inno Setup 6 install locations. winget user-scope drops it in
# %LOCALAPPDATA%\Programs; machine-wide installs land in Program Files.
$fallbacks = @(
"${env:LOCALAPPDATA}\Programs\Inno Setup 6\ISCC.exe",
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
"${env:ProgramFiles}\Inno Setup 6\ISCC.exe"
)
foreach ($p in $fallbacks) {
if (Test-Path $p) { $iscc = Get-Command $p; break }
}
if (-not $iscc) {
throw "ISCC.exe not found. Install Inno Setup 6: winget install JRSoftware.InnoSetup"
}
}
$distDir = Join-Path $PSScriptRoot 'dist'
New-Item -ItemType Directory -Force -Path $distDir | Out-Null
& $iscc.Source "/DMyAppVersion=$version" "installer.iss"
if ($LASTEXITCODE -ne 0) {
throw "ISCC failed with exit code $LASTEXITCODE"
}
$output = Join-Path $distDir "dragonflight-premiere-panel-$version-windows-setup.exe"
if (-not (Test-Path $output)) {
throw "Expected output not found: $output"
}
$size = [math]::Round((Get-Item $output).Length / 1KB, 1)
Write-Host "Built $output ($size KB)"

View file

@ -0,0 +1,102 @@
#!/usr/bin/env node
// Build a signed .zxp for the Dragonflight Premiere panel.
//
// - Reads version from ../CSXS/manifest.xml (<ExtensionBundleVersion>)
// - Generates a self-signed cert on first run (cert/dragonflight-selfsigned.p12)
// - Stages the panel bundle into stage/ (excludes build/ + dev cruft)
// - Calls zxp-sign-cmd to sign + package into dist/
//
// Usage: node build-zxp.mjs
// Output: dist/dragonflight-premiere-panel-<version>.zxp
import { mkdirSync, existsSync, readFileSync, writeFileSync, rmSync, cpSync, readdirSync, statSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import { randomBytes } from 'node:crypto';
import zxp from 'zxp-sign-cmd';
const HERE = dirname(fileURLToPath(import.meta.url));
const PANEL_DIR = resolve(HERE, '..');
const MANIFEST = join(PANEL_DIR, 'CSXS', 'manifest.xml');
const CERT_DIR = join(HERE, 'cert');
const CERT_FILE = join(CERT_DIR, 'dragonflight-selfsigned.p12');
const PASS_FILE = join(CERT_DIR, 'cert-passphrase.txt');
const STAGE_DIR = join(HERE, 'stage');
const DIST_DIR = join(HERE, 'dist');
// Top-level entries to exclude from the staged bundle.
const EXCLUDE = new Set(['build', 'install-windows.ps1', '.git', '.gitignore', 'node_modules']);
function readVersion() {
const xml = readFileSync(MANIFEST, 'utf8');
// Accept either <ExtensionBundleVersion>X.Y.Z</ExtensionBundleVersion> (the
// namespace-style manifest) or ExtensionBundleVersion="X.Y.Z" (the attribute
// form used by the CSXS 7.0+ schema).
const m = xml.match(/<ExtensionBundleVersion>([^<]+)<\/ExtensionBundleVersion>/)
|| xml.match(/\bExtensionBundleVersion\s*=\s*"([^"]+)"/);
if (!m) throw new Error(`Could not find ExtensionBundleVersion in ${MANIFEST}`);
return m[1].trim();
}
async function ensureCert() {
mkdirSync(CERT_DIR, { recursive: true });
if (existsSync(CERT_FILE) && existsSync(PASS_FILE)) {
return readFileSync(PASS_FILE, 'utf8').trim();
}
console.log('No signing cert found — generating self-signed cert (one-time)…');
const passphrase = randomBytes(24).toString('base64url');
writeFileSync(PASS_FILE, passphrase + '\n', { mode: 0o600 });
await zxp.selfSignedCert({
country: 'US',
province: 'WA',
org: 'Wild Dragon LLC',
name: 'Wild Dragon LLC',
password: passphrase,
output: CERT_FILE,
validityDays: 365 * 25,
});
console.log(` wrote ${CERT_FILE}`);
console.log(` wrote ${PASS_FILE}`);
console.log(' >> COMMIT both files so future builds reuse them. <<');
return passphrase;
}
function stageBundle() {
if (existsSync(STAGE_DIR)) rmSync(STAGE_DIR, { recursive: true, force: true });
mkdirSync(STAGE_DIR, { recursive: true });
for (const entry of readdirSync(PANEL_DIR)) {
if (EXCLUDE.has(entry)) continue;
const src = join(PANEL_DIR, entry);
const dst = join(STAGE_DIR, entry);
cpSync(src, dst, { recursive: true });
}
}
async function signZxp(version, passphrase) {
mkdirSync(DIST_DIR, { recursive: true });
const output = join(DIST_DIR, `dragonflight-premiere-panel-${version}.zxp`);
if (existsSync(output)) rmSync(output);
await zxp.sign({
input: STAGE_DIR,
output,
cert: CERT_FILE,
password: passphrase,
});
const bytes = statSync(output).size;
console.log(`Built ${output} (${(bytes / 1024).toFixed(1)} KB)`);
return output;
}
async function main() {
const version = readVersion();
console.log(`Dragonflight Premiere panel — ZXP build v${version}`);
const passphrase = await ensureCert();
stageBundle();
await signZxp(version, passphrase);
rmSync(STAGE_DIR, { recursive: true, force: true });
}
main().catch((err) => {
console.error('ZXP build failed:', err);
process.exit(1);
});

View file

@ -0,0 +1,45 @@
# Self-signed cert for ZXP signing
The `.zxp` package format requires a signature. We use a self-signed cert so
there is no Certificate Authority cost; the trade-off is editors must enable
`PlayerDebugMode` for the panel to load (the Windows `.exe` installer does
this automatically).
## Files
| File | What it is | Commit to git? |
|------|------------|----------------|
| `dragonflight-selfsigned.p12` | PKCS#12 keystore containing the signing cert + private key | **yes** |
| `cert-passphrase.txt` | Passphrase for the `.p12` | **yes** |
Both are auto-generated on the first `node build-zxp.mjs` run. They MUST be
committed and reused across builds: Adobe's ZXP signature continuity rule
means a re-signed package with a different cert fingerprint will not install
over an existing version — editors would have to uninstall the panel first.
## Why is committing a private key OK here?
- The panel is proprietary internal tooling, not a public distribution.
- The cert chains to nothing — a leak lets an attacker sign a fake
`net.wilddragon.dragonflight.panel` bundle, which would still require
`PlayerDebugMode=1` to load and physical access to the editor's machine to
install. Threat model: low.
- The alternative (rotating the cert on every build or keeping it in a
secrets manager) would break upgrade-in-place for every editor on every
build.
If you want a real codesigning cert later, drop a CA-issued `.p12` over the
self-signed one with the same filename and update `cert-passphrase.txt`. The
build script will reuse them.
## Regenerating
Delete both files. Next `node build-zxp.mjs` run will create a fresh
self-signed cert (valid for 25 years). Commit the new pair. **Heads up:**
every editor with the old `.zxp` installed must uninstall first before the
new one will install.
The build script handles regeneration automatically — just delete both
files and re-run `node build-zxp.mjs`. If you need to invoke Adobe's
`ZXPSignCmd` directly (e.g. to inspect the generated cert), it ships inside
`node_modules/zxp-provider/bin/<version>/` after `npm install`.

View file

@ -0,0 +1 @@
k_rdrajiNn_qQcW2Oc9Z2Kc0rG4AP8vA

View file

@ -0,0 +1,79 @@
; Dragonflight Premiere Pro CEP panel — Windows installer
; Build: iscc installer.iss /DMyAppVersion=1.0.0
; (build-installer.ps1 parses CSXS/manifest.xml and passes the version)
#ifndef MyAppVersion
#define MyAppVersion "0.0.0-dev"
#endif
#define MyAppName "Dragonflight Premiere Panel"
#define MyAppPublisher "Wild Dragon LLC"
#define MyAppURL "https://forge.wilddragon.net/zgaetano/dragonflight"
#define BundleId "net.wilddragon.dragonflight.panel"
#define LegacyBundleId "com.wilddragon.mam.panel"
#define PanelSrcDir "..\"
#define OutDir "dist"
[Setup]
AppId={{8E2C1F6D-1B9A-4D7E-9C3F-9F2E5A4C7B11}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}/issues
AppUpdatesURL={#MyAppURL}/releases
DefaultDirName={userappdata}\Adobe\CEP\extensions\{#BundleId}
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
DisableDirPage=yes
PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=
OutputDir={#OutDir}
OutputBaseFilename=dragonflight-premiere-panel-{#MyAppVersion}-windows-setup
Compression=lzma
SolidCompression=yes
WizardStyle=modern
UninstallDisplayName={#MyAppName} {#MyAppVersion}
UninstallDisplayIcon={app}\index.html
SetupLogging=yes
[Languages]
Name: "en"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "removelegacy"; Description: "Remove legacy {#LegacyBundleId} install if present"; GroupDescription: "Migration:"; Check: LegacyPanelExists
[Files]
; Copy the entire panel bundle, minus the build/ pipeline and the
; deprecated install-windows.ps1 (this installer supersedes it).
Source: "{#PanelSrcDir}*"; DestDir: "{app}"; Excludes: "build,install-windows.ps1,*.log"; \
Flags: ignoreversion recursesubdirs createallsubdirs
[Registry]
; Enable PlayerDebugMode for every CEP version Premiere Pro might use.
; Adobe stores this as REG_SZ "1" (a STRING, not a DWORD). Intentionally
; no uninsdeletevalue flag — other CEP panels need this key too.
Root: HKCU; Subkey: "Software\Adobe\CSXS.8"; ValueType: string; ValueName: "PlayerDebugMode"; ValueData: "1"
Root: HKCU; Subkey: "Software\Adobe\CSXS.9"; ValueType: string; ValueName: "PlayerDebugMode"; ValueData: "1"
Root: HKCU; Subkey: "Software\Adobe\CSXS.10"; ValueType: string; ValueName: "PlayerDebugMode"; ValueData: "1"
Root: HKCU; Subkey: "Software\Adobe\CSXS.11"; ValueType: string; ValueName: "PlayerDebugMode"; ValueData: "1"
Root: HKCU; Subkey: "Software\Adobe\CSXS.12"; ValueType: string; ValueName: "PlayerDebugMode"; ValueData: "1"
Root: HKCU; Subkey: "Software\Adobe\CSXS.13"; ValueType: string; ValueName: "PlayerDebugMode"; ValueData: "1"
[InstallDelete]
; Wipe the legacy folder before installing the new one (only if user opted in).
Type: filesandordirs; Name: "{userappdata}\Adobe\CEP\extensions\{#LegacyBundleId}"; Tasks: removelegacy
[UninstallDelete]
; Strip the install dir clean on uninstall. App lives entirely in {app}.
Type: filesandordirs; Name: "{app}"
[Code]
function LegacyPanelExists: Boolean;
begin
Result := DirExists(ExpandConstant('{userappdata}\Adobe\CEP\extensions\{#LegacyBundleId}'));
end;
[Run]
Filename: "{app}\README.md"; Description: "Open README"; Flags: shellexec postinstall skipifsilent unchecked

View file

@ -0,0 +1,43 @@
{
"name": "dragonflight-premiere-panel-build",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dragonflight-premiere-panel-build",
"version": "0.0.0",
"devDependencies": {
"zxp-sign-cmd": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/zxp-provider": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/zxp-provider/-/zxp-provider-2.0.0.tgz",
"integrity": "sha512-ja2YZwDnDrTdq5Q0EebOaHQK5f4tOf5488mKV4sVC/mKyNiXHyJlyKwLWB4SGIrvqqWWkDk/QCfsWms2jTQ/Tw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/zxp-sign-cmd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/zxp-sign-cmd/-/zxp-sign-cmd-2.0.0.tgz",
"integrity": "sha512-BzWNvp6kSL4RFmxWp8MkVtJ4NIuRq1238W0ojHWLgeAqWMaptFdY8Nh2Uguf7Fka8KyIinrf0+tTgCeGlWPMoA==",
"dev": true,
"license": "MIT",
"dependencies": {
"zxp-provider": "^2.0.0"
},
"engines": {
"node": ">=12.0.0",
"npm": ">=6.0.0"
}
}
}
}

View file

@ -0,0 +1,18 @@
{
"name": "dragonflight-premiere-panel-build",
"version": "0.0.0",
"private": true,
"description": "Build pipeline for the Dragonflight Premiere Pro CEP panel — produces a signed .zxp and a Windows .exe installer.",
"type": "module",
"scripts": {
"build:zxp": "node build-zxp.mjs",
"build:exe": "powershell -NoProfile -ExecutionPolicy Bypass -File build-installer.ps1",
"build": "powershell -NoProfile -ExecutionPolicy Bypass -File build-all.ps1"
},
"devDependencies": {
"zxp-sign-cmd": "^2.0.0"
},
"engines": {
"node": ">=18"
}
}

View file

@ -0,0 +1,33 @@
# Dragonflight Premiere Panel — v1.0.0
Built 2026-05-23 from `feat/premiere-installer` (initial installer
release).
## Artifacts
| File | Size | Install path |
|------|------|--------------|
| [dragonflight-premiere-panel-1.0.0-windows-setup.exe](dragonflight-premiere-panel-1.0.0-windows-setup.exe) | 2.0 MB | Windows — double-click, Next → Finish |
| [dragonflight-premiere-panel-1.0.0.zxp](dragonflight-premiere-panel-1.0.0.zxp) | 35 KB | macOS + Windows — drag into [Anastasiy's ZXP Installer](https://install.anastasiy.com/) |
Bundle ID: `net.wilddragon.dragonflight.panel`. Replaces any prior
`com.wilddragon.mam.panel` install (the `.exe` offers to remove it via a
wizard checkbox; for the `.zxp`, delete the old folder manually).
## Reproducing
```
cd services/premiere-plugin/build
npm install
powershell -File build-all.ps1
```
Requires Node 18+ and Inno Setup 6 (`winget install JRSoftware.InnoSetup`).
See [`../README.md`](../../README.md) for the full build pipeline.
## ZXP signing cert
Signed with the self-signed cert at `../../cert/dragonflight-selfsigned.p12`
(CN: `Wild Dragon LLC`, valid until 2051-05-17). Editors installing the
`.zxp` must enable `PlayerDebugMode=1` once for CEP to accept the unsigned
chain. The `.exe` installer handles this automatically for CSXS 8..13.

View file

@ -0,0 +1,43 @@
# Dragonflight Premiere Panel — v1.0.1
Built 2026-05-23 from `feat/premiere-installer`. First release that
actually loads under Premiere Pro 2025 (CEP 12) and connects to an
auth-enabled mam-api.
## What changed since v1.0.0
| Problem | Fix |
|---------|-----|
| `manifest.xml` had `--` inside an XML comment → CEP 12 refused to parse it | Comment rewritten without double hyphens |
| `manifest.xml` lacked a `Version="X.Y"` attribute on the root and used a non-standard `AbstractionLayers`/`ExtensionList` structure → CEP rejected with `Unsupported Manifest version ''` | Rewritten to the standard CSXS 7.0 schema (`ExtensionList` + `DispatchInfoList`), matching the working AMPP panel template |
| `main.js` re-declared `const csInterface` at top-level — CSInterface.js already declared the same binding. CEP 12 shares script-realm lexical scope across `<script>` tags, so main.js threw `Identifier 'csInterface' has already been declared` and never ran. The click handler for Connect was never attached → "nothing happens." | Removed the duplicate declaration; main.js now uses the binding set up by CSInterface.js |
| No way to authenticate against mam-api when `AUTH_ENABLED=true` (panel only sent `credentials: 'include'`, but it has no session cookie) | Added an "API Token" password field next to Server URL; persists to localStorage; `window.fetch` is monkey-patched to inject `Authorization: Bearer <token>` on every request to the configured server |
| Server URL field had a `change`-event listener that only fired on blur — quick "type URL → click Connect" cycles never saved the URL | Switched to `input` event so every keystroke commits. Also normalizes any trailing slash on restore so old saved `http://host/` values stop producing `//api/v1` 404s |
| New token input was `type="password"`, but the CSS selector was `input[type="text"].server-url` → unstyled, invisible against the dark theme. The 3-column grid layout also crammed the new row into a button-width slot | Connection bar restructured into a `.connection-controls--stacked` flex column of two `.server-input-row` flex rows; CSS input selector generalized to `input.server-url` |
| Build scripts' version-extraction regex only matched the old `<ExtensionBundleVersion>X</...>` element form | Now matches both element and attribute forms |
## Artifacts
| File | Size | Install |
|------|------|---------|
| [dragonflight-premiere-panel-1.0.1-windows-setup.exe](dragonflight-premiere-panel-1.0.1-windows-setup.exe) | 2.0 MB | Windows — double-click |
| [dragonflight-premiere-panel-1.0.1.zxp](dragonflight-premiere-panel-1.0.1.zxp) | 35 KB | macOS + Windows — [Anastasiy's ZXP Installer](https://install.anastasiy.com/) |
## Configuring after install
1. Open Premiere → **Window → Extensions → Wild Dragon MAM**
2. **Server URL**: e.g. `http://10.0.0.25:47434`
3. **API token**: generate one via
```
curl -c cookies.txt -X POST $API/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"USER","password":"PASS"}'
curl -b cookies.txt -X POST $API/api/v1/tokens \
-H 'Content-Type: application/json' \
-d '{"name":"premiere-panel"}'
```
Paste the returned `token` value (starts with `wd_`) into the panel.
4. Click **Connect** — status dot turns green and projects populate the dropdown.
If your `mam-api` runs with `AUTH_ENABLED` unset/false, the token field can be left empty.

View file

@ -79,7 +79,19 @@ html, body {
gap: 8px; gap: 8px;
} }
input[type="text"].server-url { .connection-controls--stacked {
display: flex;
flex-direction: column;
gap: 8px;
}
.server-input-row {
display: flex;
align-items: center;
gap: 8px;
}
input.server-url {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
background-color: var(--bg-tertiary); background-color: var(--bg-tertiary);
@ -91,13 +103,13 @@ input[type="text"].server-url {
transition: border-color 0.2s; transition: border-color 0.2s;
} }
input[type="text"].server-url:focus { input.server-url:focus {
outline: none; outline: none;
border-color: var(--accent); border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.1); box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.1);
} }
input[type="text"].server-url::placeholder { input.server-url::placeholder {
color: var(--text-secondary); color: var(--text-secondary);
} }

View file

@ -10,18 +10,28 @@
<div id="panel-container"> <div id="panel-container">
<!-- Connection Bar --> <!-- Connection Bar -->
<div class="connection-bar"> <div class="connection-bar">
<div class="connection-controls"> <div class="connection-controls connection-controls--stacked">
<div class="server-input-group"> <div class="server-input-row">
<input <input
type="text" type="text"
id="server-url" id="server-url"
class="server-url" class="server-url"
placeholder="http://localhost:7434" placeholder="http://10.0.0.25:47434"
title="MAM server URL" title="MAM server URL"
> >
<div class="status-indicator" id="status-indicator"></div> <div class="status-indicator" id="status-indicator"></div>
</div> </div>
<button id="connect-btn" class="connect-btn">Connect</button> <div class="server-input-row">
<input
type="password"
id="api-token"
class="server-url"
placeholder="API token (wd_…)"
title="API token — create with POST /api/v1/tokens"
autocomplete="off"
>
<button id="connect-btn" class="connect-btn">Connect</button>
</div>
</div> </div>
</div> </div>

View file

@ -1,39 +0,0 @@
# Wild Dragon MAM - Premiere Pro plugin installer (Windows)
# Run from PowerShell as the same user that runs Premiere Pro (NOT as admin)
#
# Usage:
# 1. Open this folder in PowerShell
# 2. Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# 3. .\install-windows.ps1
$ErrorActionPreference = 'Stop'
$src = $PSScriptRoot
$dest = Join-Path $env:APPDATA 'Adobe\CEP\extensions\com.wilddragon.mam.panel'
Write-Host "Source: $src"
Write-Host "Target: $dest"
if (Test-Path $dest) {
Write-Host "Removing existing extension at $dest"
Remove-Item -Recurse -Force $dest
}
New-Item -ItemType Directory -Force -Path $dest | Out-Null
Copy-Item -Recurse -Force "$src\*" $dest -Exclude 'install-windows.ps1'
Write-Host 'Files copied.'
# Enable CEP debug mode for the supported CEP versions (Premiere Pro uses CSXS.11+).
foreach ($v in 8..13) {
$regPath = "HKCU:\Software\Adobe\CSXS.$v"
if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
Set-ItemProperty -Path $regPath -Name 'PlayerDebugMode' -Value 1 -Type String
Write-Host "Set PlayerDebugMode=1 on $regPath"
}
Write-Host ''
Write-Host 'Done. Restart Premiere Pro, then:'
Write-Host ' Window -> Extensions -> Wild Dragon MAM'
Write-Host ''
Write-Host 'Server URL inside the panel should be the MAM web-UI, e.g.:'
Write-Host ' http://10.0.0.25:47434'

View file

@ -32,7 +32,7 @@ class CSInterface {
constructor() { constructor() {
this.requestIdCount = 1; this.requestIdCount = 1;
this.requestMap = new Map(); this.requestMap = new Map();
this.extensionId = "com.wilddragon.mam.panel"; this.extensionId = "net.wilddragon.dragonflight.panel";
// Initialize event listener for messages from ExtendScript // Initialize event listener for messages from ExtendScript
if (window.__adobe_cep__ !== undefined) { if (window.__adobe_cep__ !== undefined) {

View file

@ -3,8 +3,10 @@
* Main JavaScript file for the CEP panel * Main JavaScript file for the CEP panel
*/ */
// Adobe CEP interface — must be instantiated before any host (ExtendScript) calls // Adobe CEP interface — CSInterface.js already declared `const csInterface`
const csInterface = new CSInterface(); // at the script-realm scope (top-level `const` is shared across non-module
// <script> tags), so re-declaring it here throws SyntaxError under CEP 12.
// We just use the binding that CSInterface.js provides.
// ============================================================================ // ============================================================================
// State Management // State Management
@ -12,6 +14,7 @@ const csInterface = new CSInterface();
const state = { const state = {
serverUrl: localStorage.getItem('mam_server_url') || 'http://localhost:7434', serverUrl: localStorage.getItem('mam_server_url') || 'http://localhost:7434',
apiToken: localStorage.getItem('mam_api_token') || '',
isConnected: false, isConnected: false,
isConnecting: false, isConnecting: false,
selectedAsset: null, selectedAsset: null,
@ -41,6 +44,7 @@ let elements = {};
function initDOMElements() { function initDOMElements() {
elements = { elements = {
serverUrlInput: document.getElementById('server-url'), serverUrlInput: document.getElementById('server-url'),
apiTokenInput: document.getElementById('api-token'),
connectBtn: document.getElementById('connect-btn'), connectBtn: document.getElementById('connect-btn'),
statusIndicator: document.getElementById('status-indicator'), statusIndicator: document.getElementById('status-indicator'),
searchInput: document.getElementById('search-input'), searchInput: document.getElementById('search-input'),
@ -126,11 +130,17 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
function setupEventListeners() { function setupEventListeners() {
elements.serverUrlInput.addEventListener('change', (e) => { // Commit serverUrl on every keystroke so clicking Connect right after
// typing works even before the input loses focus.
elements.serverUrlInput.addEventListener('input', (e) => {
state.serverUrl = e.target.value.trim().replace(/\/$/, ''); state.serverUrl = e.target.value.trim().replace(/\/$/, '');
localStorage.setItem('mam_server_url', state.serverUrl); localStorage.setItem('mam_server_url', state.serverUrl);
state.thumbCache = {}; state.thumbCache = {};
}); });
elements.apiTokenInput.addEventListener('input', (e) => {
state.apiToken = e.target.value.trim();
localStorage.setItem('mam_api_token', state.apiToken);
});
elements.connectBtn.addEventListener('click', connectToServer); elements.connectBtn.addEventListener('click', connectToServer);
elements.searchInput.addEventListener('input', debounce(handleSearch, 300)); elements.searchInput.addEventListener('input', debounce(handleSearch, 300));
elements.projectFilter.addEventListener('change', handleProjectFilter); elements.projectFilter.addEventListener('change', handleProjectFilter);
@ -147,9 +157,36 @@ function setupEventListeners() {
} }
function restoreSettings() { function restoreSettings() {
// Normalize any trailing slash that the old `change`-event listener may
// have missed on previous sessions — otherwise fetches produce //api/v1
// and nginx 404s.
state.serverUrl = state.serverUrl.replace(/\/+$/, '');
localStorage.setItem('mam_server_url', state.serverUrl);
elements.serverUrlInput.value = state.serverUrl; elements.serverUrlInput.value = state.serverUrl;
elements.apiTokenInput.value = state.apiToken;
} }
// ============================================================================
// Auth: monkey-patch fetch to inject Bearer token on every same-origin call
// to the configured MAM server. Mam-api requires this when AUTH_ENABLED=true.
// ============================================================================
const _originalFetch = window.fetch.bind(window);
window.fetch = function (input, init) {
init = init || {};
const url = typeof input === 'string' ? input : (input && input.url) || '';
// Only attach the token for requests aimed at the configured MAM server.
// Signed S3 download URLs etc. must NOT get our Authorization header.
if (state.apiToken && state.serverUrl && url.startsWith(state.serverUrl)) {
const headers = new Headers(init.headers || {});
if (!headers.has('Authorization')) {
headers.set('Authorization', 'Bearer ' + state.apiToken);
}
init.headers = headers;
}
return _originalFetch(input, init);
};
// ============================================================================ // ============================================================================
// Server Connection // Server Connection
// ============================================================================ // ============================================================================