Skip to content

Latest commit

 

History

History
168 lines (135 loc) · 6.01 KB

File metadata and controls

168 lines (135 loc) · 6.01 KB

Script-source package format, version 1

Purpose

A .funplugin is a UTF-8 JSON declaration consumed by a host-provided adapter. It declares one public Discourse provider and display metadata. It is not an extension runtime and cannot contain executable behavior.

The current contract is:

apiVersion = 1
engine     = discourse-script-source-v1

The JSON Schemas are available under packages/sdk/schema, but they are editor and object-shape aids rather than conformance authorities. Standard JSON Schema cannot compare normalized values across fields or inspect decoded PNG chunks. Only validateManifest or funplugin validate makes a complete v1 conformance decision. Schema success alone must never be presented as package validity.

Unsigned package

An unsigned .funplugin is exactly one strict manifest object. The package is valid only when it is valid UTF-8 JSON and contains no unknown fields.

{
  "id": "example.community",
  "version": "1.0.0",
  "apiVersion": 1,
  "engine": "discourse-script-source-v1",
  "baseUrl": "https://community.example.com",
  "allowedOrigins": ["https://community.example.com"],
  "metadata": {
    "displayName": "Example Community",
    "description": "Search the example community for scripts.",
    "homepage": "https://community.example.com/about",
    "publisher": {
      "name": "Example Publisher",
      "url": "https://publisher.example.org/"
    },
    "permissions": [
      { "capability": "authentication", "reason": "Sign in to the community." },
      { "capability": "search", "reason": "Search public script topics." },
      { "capability": "script-download", "reason": "Download a selected script." }
    ],
    "searchQuerySuffix": "#scripts"
  }
}

Unsigned serialization produced by the SDK is two-space indented JSON followed by a newline. Consumers must validate the parsed value rather than treating formatting as identity. The package SHA-256 is calculated over the emitted UTF-8 artifact, including its terminal newline.

Manifest fields

Field Requirement
id 3–80 lowercase letters, digits, dots, and hyphens; valid semantic namespace form; no reserved Windows device prefix.
version Semantic Versioning 2.0 string.
apiVersion Integer 1.
engine Exact string discourse-script-source-v1.
baseUrl Public HTTPS origin on port 443, no path, credentials, query, or fragment.
allowedOrigins Array containing exactly one value, exactly equal to baseUrl.
metadata Strict metadata object described below.

baseUrl must use a public DNS hostname. Literal IP addresses and local, loopback, private, link-local, test, and internal-style hostnames are rejected.

Metadata

Required fields are displayName, description, homepage, publisher, and permissions. icon and searchQuerySuffix are optional. All strings are trimmed and exclude control characters.

  • displayName: 1–80 characters.
  • description: 1–500 characters.
  • homepage: public HTTPS URL on the provider origin.
  • publisher.name: 1–100 characters; verification marks are prohibited.
  • publisher.url: optional public HTTPS URL.
  • searchQuerySuffix: optional 1–200 character search text appended by the adapter.
  • icon: optional non-animated PNG. Its canonical padded base64 data is at most 32 KiB decoded; dimensions are at most 512×512 and 262,144 pixels.

Capabilities

v1 requires this exact set, each exactly once, with a 1–240 character reason:

authentication
search
script-download

The declarations describe the fixed adapter behavior; they do not grant a package arbitrary runtime access. Optional or additional capabilities are not part of v1.

Fixed provider behavior

The discourse-script-source-v1 adapter uses only the declared origin and these provider paths:

Operation Path
Search /search.json?q=...
Topic detail /t/{topicId}.json
Current session /session/current.json
Sign-in page /login

Search and topic responses are JSON. The conformance probe accepts bounded JSON responses, follows at most three redirects that remain on the declared origin, and checks Discourse-shaped topic data. The default Node.js probe resolves the provider once, rejects any non-public result, and pins the HTTPS socket lookup to the validated addresses. A per-request deadline remains active until the bounded response body is complete. It reports diagnostics only; the host remains responsible for runtime enforcement.

Eligible download URLs are HTTPS, have exactly the declared origin, and end in .funscript (case-insensitive). A package cannot declare URL templates, selectors, arbitrary request headers, alternate origins, archive handling, media handling, or an asset-opening capability.

Signed package envelope

A signed release wraps the same manifest in a canonical JSON envelope:

{
  "signed": {
    "type": "scriptplayerplus.script-source.release",
    "formatVersion": 1,
    "publisherId": "example.publisher",
    "keyId": "ed25519-sha256:base64url-sha256-of-spki",
    "manifest": { "...": "the v1 manifest" }
  },
  "signature": "base64url-ed25519-signature"
}

The signed payload is canonical JSON with lexicographically ordered object keys. The signing input is UTF-8 bytes of:

ScriptPlayerPlus.funplugin.v1\0 + canonical-json(signed payload)

The signature is Ed25519 and uses unpadded base64url. keyId is ed25519-sha256: followed by the base64url SHA-256 digest of the public key's SPKI DER encoding. Signed artifact JSON must itself be canonical, with only an optional final newline.

A valid signature proves control of the corresponding private key for that artifact. It does not prove that a host release admits the publisher, the key, or the package.

Non-goals

v1 does not support executable modules, custom adapters, custom HTTP request templates, multi-origin packages, package-managed updates, arbitrary file downloads, archive extraction, media opening, or host trust decisions in the public SDK.