diff --git a/README.md b/README.md
index 1fee5e9..86ef69a 100644
--- a/README.md
+++ b/README.md
@@ -165,11 +165,19 @@ Example:
"schemaVersion": 1,
"id": "com.example.notes",
"name": "Notes",
- "version": "0.0.0",
- "runtime": "route-source",
- "permissions": [
- "storage:readWrite"
- ],
+ "version": "0.1.0",
+ "runtime": "standalone",
+ "runtimeConfig": {
+ "engine": "react"
+ },
+ "permissions": ["storage:readWrite"],
+ "launch": {
+ "path": "/apps/com.example.notes"
+ },
+ "extensionPoints": {
+ "launcher": true,
+ "sidebar": true
+ },
"compatibility": {
"minPlatformVersion": "0.1.0"
}
@@ -183,21 +191,106 @@ Example:
Current runtime types:
```txt
-route-source
-iframe-local
-iframe-remote
-external
+standalone
+dom
+iframe
```
-Initial versions of Sovereign focus on:
+## `standalone`
+
+Apps loaded directly by the Sovereign platform.
+
+Use this for trusted plugins that should be served or bundled by the platform itself.
+
+Supported engines:
```txt
-route-source
+react
+html
+```
+
+Example:
+
+```json
+{
+ "runtime": "standalone",
+ "runtimeConfig": {
+ "engine": "react"
+ }
+}
+```
+
+Standalone HTML apps use a plugin-local entrypoint:
+
+```json
+{
+ "runtime": "standalone",
+ "runtimeConfig": {
+ "engine": "html",
+ "entrypoint": "index.html"
+ }
+}
```
-apps built directly into the platform runtime.
+## `dom`
-Sandboxed runtimes will be introduced later.
+Apps served by a DOM application server and embedded by Sovereign.
+
+Use this for Vite, React, or other DOM app development servers.
+
+Example:
+
+```json
+{
+ "runtime": "dom",
+ "runtimeConfig": {
+ "engine": "react",
+ "host": "localhost",
+ "port": "4000"
+ }
+}
+```
+
+## `iframe`
+
+Apps hosted in an iframe. The iframe can point at a local plugin entrypoint, a local dev server, or a remote host.
+
+Local HTML entrypoint:
+
+```json
+{
+ "runtime": "iframe",
+ "runtimeConfig": {
+ "engine": "html",
+ "entrypoint": "iframe/index.html"
+ }
+}
+```
+
+Local or remote host:
+
+```json
+{
+ "runtime": "iframe",
+ "runtimeConfig": {
+ "engine": "*",
+ "host": "example.com",
+ "https": true,
+ "uri": "/#test"
+ }
+}
+```
+
+The current manifest schema uses flat `runtimeConfig` fields:
+
+```txt
+engine
+entrypoint
+host
+port
+https
+uri
+```
---
@@ -209,10 +302,16 @@ Examples:
```txt
auth:profile
-storage:readWrite
+auth:read
+auth:write
+storage:read
+storage:write
notifications:send
-files:pick
+notifications:recieve
+fs:read
+fs:write
events:publish
+events:subscribe
```
Apps interact with platform features through the Sovereign SDK.
@@ -249,7 +348,7 @@ yarn generate
## Validate App Manifest
```bash
-yarn validate:manifest plugins/com.sovereign.launcher/manifest.json
+yarn validate:manifest plugins/com.sovereign-demo.iframe-html/manifest.json
```
---
@@ -272,4 +371,4 @@ Current focus areas:
# License
-AGPL-3.0
\ No newline at end of file
+AGPL-3.0
diff --git a/packages/manifest/schema/manifest.schema.json b/packages/manifest/schema/manifest.schema.json
index 1461854..4ddb633 100644
--- a/packages/manifest/schema/manifest.schema.json
+++ b/packages/manifest/schema/manifest.schema.json
@@ -20,7 +20,7 @@
"version": { "type": "string" },
"runtime": {
"type": "string",
- "enum": ["internal", "route-source", "iframe-local", "iframe-remote", "external"]
+ "enum": ["standalone", "dom", "iframe"]
},
"permissions": {
"type": "array",
@@ -42,40 +42,35 @@
"properties": {
"engine": {
"type": "string",
- "enum": ["vite:react-ts"]
+ "enum": ["react", "html", "*"]
},
- "iframeLocal": {
- "type": "object",
- "required": ["entrypoint"],
- "properties": {
- "entrypoint": {
- "type": "string",
- "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"
- }
- },
- "additionalProperties": false
+ "host": {
+ "type": "string",
+ "minLength": 1
},
- "iframeRemote": {
- "type": "object",
- "required": ["url"],
- "properties": {
- "url": {
+ "port": {
+ "oneOf": [
+ {
"type": "string",
- "pattern": "^https://.+"
+ "pattern": "^[0-9]+$"
+ },
+ {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 65535
}
- },
- "additionalProperties": false
+ ]
},
- "external": {
- "type": "object",
- "required": ["url"],
- "properties": {
- "url": {
- "type": "string",
- "pattern": "^https://.+"
- }
- },
- "additionalProperties": false
+ "https": {
+ "type": "boolean"
+ },
+ "uri": {
+ "type": "string",
+ "pattern": "^/"
+ },
+ "entrypoint": {
+ "type": "string",
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"
}
},
"additionalProperties": false
@@ -107,7 +102,7 @@
{
"if": {
"properties": {
- "runtime": { "const": "internal" }
+ "runtime": { "const": "standalone" }
},
"required": ["runtime"]
},
@@ -116,7 +111,12 @@
"properties": {
"runtimeConfig": {
"type": "object",
- "required": ["engine"]
+ "required": ["engine"],
+ "properties": {
+ "engine": {
+ "enum": ["react", "html"]
+ }
+ }
}
}
}
@@ -124,7 +124,7 @@
{
"if": {
"properties": {
- "runtime": { "const": "iframe-local" }
+ "runtime": { "const": "dom" }
},
"required": ["runtime"]
},
@@ -133,7 +133,7 @@
"properties": {
"runtimeConfig": {
"type": "object",
- "required": ["iframeLocal"]
+ "required": ["engine", "host", "port"]
}
}
}
@@ -141,16 +141,22 @@
{
"if": {
"properties": {
- "runtime": { "const": "iframe-remote" }
+ "runtime": { "const": "standalone" },
+ "runtimeConfig": {
+ "type": "object",
+ "properties": {
+ "engine": { "const": "html" }
+ },
+ "required": ["engine"]
+ }
},
- "required": ["runtime"]
+ "required": ["runtime", "runtimeConfig"]
},
"then": {
- "required": ["runtimeConfig"],
"properties": {
"runtimeConfig": {
"type": "object",
- "required": ["iframeRemote"]
+ "required": ["entrypoint"]
}
}
}
@@ -158,16 +164,36 @@
{
"if": {
"properties": {
- "runtime": { "const": "external" }
+ "runtime": { "const": "iframe" },
+ "runtimeConfig": {
+ "type": "object",
+ "required": ["entrypoint"]
+ }
},
- "required": ["runtime"]
+ "required": ["runtime", "runtimeConfig"]
},
"then": {
- "required": ["runtimeConfig"],
"properties": {
"runtimeConfig": {
"type": "object",
- "required": ["external"]
+ "required": ["engine"]
+ }
+ }
+ },
+ "else": {
+ "if": {
+ "properties": {
+ "runtime": { "const": "iframe" }
+ },
+ "required": ["runtime"]
+ },
+ "then": {
+ "required": ["runtimeConfig"],
+ "properties": {
+ "runtimeConfig": {
+ "type": "object",
+ "required": ["engine", "host"]
+ }
}
}
}
diff --git a/packages/manifest/src/index.ts b/packages/manifest/src/index.ts
index 518f71b..c3ab932 100644
--- a/packages/manifest/src/index.ts
+++ b/packages/manifest/src/index.ts
@@ -4,11 +4,14 @@ export * from "./validate";
export * from "./permissions";
export type SovereignRuntime =
- | "internal"
- | "route-source"
- | "iframe-local"
- | "iframe-remote"
- | "external";
+ | "standalone"
+ | "dom"
+ | "iframe";
+
+export type SovereignRuntimeEngine =
+ | "react"
+ | "html"
+ | "*";
export interface SovereignAppManifest {
schemaVersion: 1;
@@ -21,16 +24,12 @@ export interface SovereignAppManifest {
path: string;
};
runtimeConfig?: {
- engine?: "vite:react-ts";
- iframeLocal?: {
- entrypoint: string;
- };
- iframeRemote?: {
- url: string;
- };
- external?: {
- url: string;
- };
+ engine?: SovereignRuntimeEngine;
+ host?: string;
+ port?: string | number;
+ https?: boolean;
+ uri?: string;
+ entrypoint?: string;
};
extensionPoints?: {
launcher?: boolean;
diff --git a/packages/manifest/src/permissions.ts b/packages/manifest/src/permissions.ts
index f07472d..ed657d2 100644
--- a/packages/manifest/src/permissions.ts
+++ b/packages/manifest/src/permissions.ts
@@ -1,5 +1,7 @@
export const SovereignPermissions = {
AuthProfile: "auth:profile",
+ AuthRead: "auth:read",
+ AuthWrite: "auth:write",
StorageReadWrite: "storage:readWrite",
EventsPublish: "events:publish",
NotificationsSend: "notifications:send",
diff --git a/platform/app/api/apps/[appId]/iframe/[[...assetPath]]/route.ts b/platform/app/api/apps/[appId]/iframe/[[...assetPath]]/route.ts
index f588946..5a490ab 100644
--- a/platform/app/api/apps/[appId]/iframe/[[...assetPath]]/route.ts
+++ b/platform/app/api/apps/[appId]/iframe/[[...assetPath]]/route.ts
@@ -26,11 +26,11 @@ export async function GET(_request: Request, { params }: IframeAssetRouteProps)
const { appId, assetPath = [] } = await params;
const app = resolveApp(appId);
- if (app?.runtime !== "iframe-local") {
+ if (!app || !app.runtimeConfig?.entrypoint) {
notFound();
}
- const entrypoint = app.runtimeConfig?.iframeLocal?.entrypoint;
+ const entrypoint = app.runtimeConfig?.entrypoint;
if (!entrypoint || !isSafeRelativePath(entrypoint)) {
notFound();
diff --git a/platform/src/launcher/get-installed-apps.ts b/platform/src/launcher/get-installed-apps.ts
index 6b2ee26..fda72cf 100644
--- a/platform/src/launcher/get-installed-apps.ts
+++ b/platform/src/launcher/get-installed-apps.ts
@@ -1,6 +1,7 @@
import { installedApps } from "../../generated/apps.generated";
+import type { InstalledSovereignApp } from "../runtime";
-export function getInstalledApps() {
+export function getInstalledApps(): readonly InstalledSovereignApp[] {
return installedApps;
}
diff --git a/platform/src/runtime/external-app-runtime.tsx b/platform/src/runtime/external-app-runtime.tsx
deleted file mode 100644
index d50558e..0000000
--- a/platform/src/runtime/external-app-runtime.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { InstalledSovereignApp } from "./types";
-
-interface ExternalAppRuntimeProps {
- app: InstalledSovereignApp;
-}
-
-export function ExternalAppRuntime({ app }: ExternalAppRuntimeProps) {
- const external = app.runtimeConfig?.external;
-
- if (!external || !isHttpsUrl(external.url)) {
- return
External runtime URL not configured.
;
- }
-
- return (
-
- );
-}
-
-function isHttpsUrl(input: string) {
- try {
- return new URL(input).protocol === "https:";
- } catch {
- return false;
- }
-}
diff --git a/platform/src/runtime/iframe-local-runtime.tsx b/platform/src/runtime/iframe-local-runtime.tsx
index a066ee3..0f162fd 100644
--- a/platform/src/runtime/iframe-local-runtime.tsx
+++ b/platform/src/runtime/iframe-local-runtime.tsx
@@ -7,13 +7,31 @@ interface IframeLocalRuntimeProps {
}
export function IframeLocalRuntime({ app, appPath }: IframeLocalRuntimeProps) {
- const iframeLocal = app.runtimeConfig?.iframeLocal;
+ const entrypoint = app.runtimeConfig?.entrypoint;
- if (!iframeLocal) {
- return Iframe runtime entrypoint not configured.
;
+ if (!entrypoint) {
+ const remoteUrl = getRemoteIframeUrl(app);
+
+ if (!remoteUrl) {
+ return Iframe runtime URL not configured.
;
+ }
+
+ return (
+
+ );
}
- const entrypointFileName = iframeLocal.entrypoint.split("/").at(-1);
+ const entrypointFileName = entrypoint.split("/").at(-1);
if (!entrypointFileName) {
return Iframe runtime entrypoint not configured.
;
@@ -29,6 +47,27 @@ export function IframeLocalRuntime({ app, appPath }: IframeLocalRuntimeProps) {
);
}
+function getRemoteIframeUrl(app: InstalledSovereignApp) {
+ const config = app.runtimeConfig;
+
+ if (!config) {
+ return null;
+ }
+
+ if (!config.host) {
+ return null;
+ }
+
+ const protocol = config.https ? "https:" : "http:";
+ const baseUrl = `${protocol}//${config.host}`;
+ const url = new URL(config.uri ?? "/", baseUrl);
+
+ if (config.port !== undefined) {
+ url.port = String(config.port);
+ }
+
+ return url.toString();
+}
function toAppPath(appPath: readonly string[]) {
if (appPath.length === 0) {
return "/";
diff --git a/platform/src/runtime/iframe-remote-runtime.tsx b/platform/src/runtime/iframe-remote-runtime.tsx
deleted file mode 100644
index 2789a46..0000000
--- a/platform/src/runtime/iframe-remote-runtime.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { InstalledSovereignApp } from "./types";
-
-interface IframeRemoteRuntimeProps {
- app: InstalledSovereignApp;
-}
-
-export function IframeRemoteRuntime({ app }: IframeRemoteRuntimeProps) {
- const iframeRemote = app.runtimeConfig?.iframeRemote;
-
- if (!iframeRemote || !isHttpsUrl(iframeRemote.url)) {
- return Remote iframe runtime URL not configured.
;
- }
-
- return (
-
- );
-}
-
-function isHttpsUrl(input: string) {
- try {
- return new URL(input).protocol === "https:";
- } catch {
- return false;
- }
-}
diff --git a/platform/src/runtime/index.ts b/platform/src/runtime/index.ts
index 16c87db..a1382fe 100644
--- a/platform/src/runtime/index.ts
+++ b/platform/src/runtime/index.ts
@@ -1,7 +1,5 @@
export * from "./app-runtime-shell";
-export * from "./external-app-runtime";
export * from "./iframe-local-frame";
export * from "./iframe-local-runtime";
-export * from "./iframe-remote-runtime";
export * from "./render-app-runtime";
export * from "./types";
diff --git a/platform/src/runtime/render-app-runtime.tsx b/platform/src/runtime/render-app-runtime.tsx
index 5b58312..5747797 100644
--- a/platform/src/runtime/render-app-runtime.tsx
+++ b/platform/src/runtime/render-app-runtime.tsx
@@ -1,9 +1,6 @@
import { Suspense, type ComponentType } from "react";
-import { createAppSdk } from "../sdk";
-import { ExternalAppRuntime } from "./external-app-runtime";
import { IframeLocalRuntime } from "./iframe-local-runtime";
-import { IframeRemoteRuntime } from "./iframe-remote-runtime";
import type { InstalledSovereignApp } from "./types";
interface RenderAppRuntimeProps {
@@ -16,49 +13,29 @@ export async function RenderAppRuntime({
appPath = [],
}: RenderAppRuntimeProps) {
switch (app.runtime) {
- case "internal": {
- if (!app.module) {
- return App module not found.
;
+ case "standalone": {
+ if (app.runtimeConfig?.engine === "html") {
+ return ;
}
- const AppModule = await app.module();
- const AppComponent = AppModule.default as ComponentType;
-
- return (
- Loading app...}>
-
-
- );
- }
-
- case "route-source": {
if (!app.module) {
return App module not found.
;
}
const AppModule = await app.module();
- const AppComponent = AppModule.default;
-
- const sdk = createAppSdk({
- appId: app.id,
- });
+ const AppComponent = AppModule.default as ComponentType;
return (
Loading app...}>
-
+
);
}
- case "iframe-local":
+ case "dom":
+ case "iframe":
return ;
- case "iframe-remote":
- return ;
-
- case "external":
- return ;
-
default:
return null;
}
diff --git a/platform/src/runtime/types.ts b/platform/src/runtime/types.ts
index d67be8c..2e980db 100644
--- a/platform/src/runtime/types.ts
+++ b/platform/src/runtime/types.ts
@@ -3,11 +3,11 @@ import type { ComponentType } from "react";
import type { SovereignAppManifest } from "../../../packages/manifest/src";
import type { SovereignAppProps } from "../../../packages/sdk/src";
-export type RouteSourceAppModule = () => Promise<{
+export type StandaloneAppModule = () => Promise<{
default: ComponentType;
}>;
export type InstalledSovereignApp = SovereignAppManifest & {
pluginDirectory: string;
- module?: RouteSourceAppModule;
+ module?: StandaloneAppModule;
};
diff --git a/platform/src/sdk/create-app-sdk.ts b/platform/src/sdk/create-app-sdk.ts
index d5ac494..08e755a 100644
--- a/platform/src/sdk/create-app-sdk.ts
+++ b/platform/src/sdk/create-app-sdk.ts
@@ -11,7 +11,11 @@ export function createAppSdk(input: CreateAppSdkInput) {
const permissions = getAppPermissions(input.appId);
return {
- auth: hasPermission(permissions, SovereignPermissions.AuthProfile)
+ auth: hasAnyPermission(permissions, [
+ SovereignPermissions.AuthProfile,
+ SovereignPermissions.AuthRead,
+ SovereignPermissions.AuthWrite,
+ ])
? sovereign.auth
: undefined,
@@ -28,3 +32,12 @@ export function createAppSdk(input: CreateAppSdkInput) {
: undefined,
};
}
+
+function hasAnyPermission(
+ permissions: readonly string[],
+ allowed: readonly string[]
+) {
+ return allowed.some((permission) =>
+ hasPermission(permissions, permission)
+ );
+}
diff --git a/plugins/com.sovereign-demo.vite-react/.gitignore b/plugins/com.sovereign-demo.dom-react/.gitignore
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/.gitignore
rename to plugins/com.sovereign-demo.dom-react/.gitignore
diff --git a/plugins/com.sovereign-demo.vite-react/README.md b/plugins/com.sovereign-demo.dom-react/README.md
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/README.md
rename to plugins/com.sovereign-demo.dom-react/README.md
diff --git a/plugins/com.sovereign-demo.vite-react/eslint.config.js b/plugins/com.sovereign-demo.dom-react/eslint.config.js
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/eslint.config.js
rename to plugins/com.sovereign-demo.dom-react/eslint.config.js
diff --git a/plugins/com.sovereign-demo.vite-react/index.html b/plugins/com.sovereign-demo.dom-react/index.html
similarity index 87%
rename from plugins/com.sovereign-demo.vite-react/index.html
rename to plugins/com.sovereign-demo.dom-react/index.html
index 46a52e7..8b6e512 100644
--- a/plugins/com.sovereign-demo.vite-react/index.html
+++ b/plugins/com.sovereign-demo.dom-react/index.html
@@ -4,7 +4,7 @@
- com.sovereign-demo.vite-react
+ com.sovereign-demo.dom-react
diff --git a/plugins/com.sovereign-demo.vite-react/manifest.json b/plugins/com.sovereign-demo.dom-react/manifest.json
similarity index 54%
rename from plugins/com.sovereign-demo.vite-react/manifest.json
rename to plugins/com.sovereign-demo.dom-react/manifest.json
index 7d92f60..321affe 100644
--- a/plugins/com.sovereign-demo.vite-react/manifest.json
+++ b/plugins/com.sovereign-demo.dom-react/manifest.json
@@ -1,20 +1,22 @@
{
"schemaVersion": 1,
- "id": "com.sovereign-demo.vite-react",
+ "id": "com.sovereign-demo.dom-react",
"name": "Vite + React Demo",
- "version": "0.0.0",
- "runtime": "internal",
- "permissions": ["auth:profile"],
+ "version": "0.1.0",
+ "runtime": "dom",
+ "runtimeConfig": {
+ "engine": "react",
+ "host": "localhost",
+ "port": "4000"
+ },
+ "permissions": ["auth:read", "auth:write"],
"author": "Sovereign Core Team",
"license": "AGPL-3.0",
"compatibility": {
"minPlatformVersion": "0.1.0"
},
"launch": {
- "path": "/apps/com.sovereign-demo.vite-react"
- },
- "runtimeConfig": {
- "engine": "vite:react-ts"
+ "path": "/apps/com.sovereign-demo.dom-react"
},
"extensionPoints": {
"launcher": true,
diff --git a/plugins/com.sovereign-demo.vite-react/package.json b/plugins/com.sovereign-demo.dom-react/package.json
similarity index 95%
rename from plugins/com.sovereign-demo.vite-react/package.json
rename to plugins/com.sovereign-demo.dom-react/package.json
index 4d0c4ba..6a7e445 100644
--- a/plugins/com.sovereign-demo.vite-react/package.json
+++ b/plugins/com.sovereign-demo.dom-react/package.json
@@ -1,5 +1,5 @@
{
- "name": "com.sovereign-demo.vite-react",
+ "name": "com.sovereign-demo.dom-react",
"private": true,
"version": "0.0.0",
"type": "module",
diff --git a/plugins/com.sovereign-demo.vite-react/public/favicon.svg b/plugins/com.sovereign-demo.dom-react/public/favicon.svg
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/public/favicon.svg
rename to plugins/com.sovereign-demo.dom-react/public/favicon.svg
diff --git a/plugins/com.sovereign-demo.vite-react/public/icons.svg b/plugins/com.sovereign-demo.dom-react/public/icons.svg
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/public/icons.svg
rename to plugins/com.sovereign-demo.dom-react/public/icons.svg
diff --git a/plugins/com.sovereign-demo.vite-react/src/App.css b/plugins/com.sovereign-demo.dom-react/src/App.css
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/src/App.css
rename to plugins/com.sovereign-demo.dom-react/src/App.css
diff --git a/plugins/com.sovereign-demo.vite-react/src/App.tsx b/plugins/com.sovereign-demo.dom-react/src/App.tsx
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/src/App.tsx
rename to plugins/com.sovereign-demo.dom-react/src/App.tsx
diff --git a/plugins/com.sovereign-demo.vite-react/src/assets/hero.png b/plugins/com.sovereign-demo.dom-react/src/assets/hero.png
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/src/assets/hero.png
rename to plugins/com.sovereign-demo.dom-react/src/assets/hero.png
diff --git a/plugins/com.sovereign-demo.vite-react/src/assets/react.svg b/plugins/com.sovereign-demo.dom-react/src/assets/react.svg
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/src/assets/react.svg
rename to plugins/com.sovereign-demo.dom-react/src/assets/react.svg
diff --git a/plugins/com.sovereign-demo.vite-react/src/assets/vite.svg b/plugins/com.sovereign-demo.dom-react/src/assets/vite.svg
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/src/assets/vite.svg
rename to plugins/com.sovereign-demo.dom-react/src/assets/vite.svg
diff --git a/plugins/com.sovereign-demo.vite-react/src/index.css b/plugins/com.sovereign-demo.dom-react/src/index.css
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/src/index.css
rename to plugins/com.sovereign-demo.dom-react/src/index.css
diff --git a/plugins/com.sovereign-demo.vite-react/src/main.tsx b/plugins/com.sovereign-demo.dom-react/src/main.tsx
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/src/main.tsx
rename to plugins/com.sovereign-demo.dom-react/src/main.tsx
diff --git a/plugins/com.sovereign-demo.vite-react/tsconfig.app.json b/plugins/com.sovereign-demo.dom-react/tsconfig.app.json
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/tsconfig.app.json
rename to plugins/com.sovereign-demo.dom-react/tsconfig.app.json
diff --git a/plugins/com.sovereign-demo.vite-react/tsconfig.json b/plugins/com.sovereign-demo.dom-react/tsconfig.json
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/tsconfig.json
rename to plugins/com.sovereign-demo.dom-react/tsconfig.json
diff --git a/plugins/com.sovereign-demo.vite-react/tsconfig.node.json b/plugins/com.sovereign-demo.dom-react/tsconfig.node.json
similarity index 100%
rename from plugins/com.sovereign-demo.vite-react/tsconfig.node.json
rename to plugins/com.sovereign-demo.dom-react/tsconfig.node.json
diff --git a/plugins/com.sovereign-demo.vite-react/vite.config.ts b/plugins/com.sovereign-demo.dom-react/vite.config.ts
similarity index 90%
rename from plugins/com.sovereign-demo.vite-react/vite.config.ts
rename to plugins/com.sovereign-demo.dom-react/vite.config.ts
index d1203cd..19c5f29 100644
--- a/plugins/com.sovereign-demo.vite-react/vite.config.ts
+++ b/plugins/com.sovereign-demo.dom-react/vite.config.ts
@@ -8,4 +8,7 @@ export default defineConfig({
react(),
babel({ presets: [reactCompilerPreset()] })
],
+ server: {
+ port: 4000
+ }
})
diff --git a/plugins/com.sovereign-demo.iframe-local/iframe/index.html b/plugins/com.sovereign-demo.iframe-html/iframe/index.html
similarity index 100%
rename from plugins/com.sovereign-demo.iframe-local/iframe/index.html
rename to plugins/com.sovereign-demo.iframe-html/iframe/index.html
diff --git a/plugins/com.sovereign-demo.iframe-local/iframe/runtime.js b/plugins/com.sovereign-demo.iframe-html/iframe/runtime.js
similarity index 100%
rename from plugins/com.sovereign-demo.iframe-local/iframe/runtime.js
rename to plugins/com.sovereign-demo.iframe-html/iframe/runtime.js
diff --git a/plugins/com.sovereign-demo.iframe-local/iframe/style.css b/plugins/com.sovereign-demo.iframe-html/iframe/style.css
similarity index 100%
rename from plugins/com.sovereign-demo.iframe-local/iframe/style.css
rename to plugins/com.sovereign-demo.iframe-html/iframe/style.css
diff --git a/plugins/com.sovereign-demo.external/manifest.json b/plugins/com.sovereign-demo.iframe-html/manifest.json
similarity index 55%
rename from plugins/com.sovereign-demo.external/manifest.json
rename to plugins/com.sovereign-demo.iframe-html/manifest.json
index c68f772..10dd99f 100644
--- a/plugins/com.sovereign-demo.external/manifest.json
+++ b/plugins/com.sovereign-demo.iframe-html/manifest.json
@@ -1,9 +1,13 @@
{
"schemaVersion": 1,
- "id": "com.sovereign-demo.external",
- "name": "External Demo",
- "version": "0.0.0",
- "runtime": "external",
+ "id": "com.sovereign-demo.iframe-html",
+ "name": "iframe Html Demo",
+ "version": "0.1.0",
+ "runtime": "iframe",
+ "runtimeConfig": {
+ "engine": "html",
+ "entrypoint": "iframe/index.html"
+ },
"permissions": [],
"author": "Sovereign Core Team",
"license": "AGPL-3.0",
@@ -11,12 +15,7 @@
"minPlatformVersion": "0.1.0"
},
"launch": {
- "path": "/apps/com.sovereign.external-demo"
- },
- "runtimeConfig": {
- "external": {
- "url": "https://example.com/"
- }
+ "path": "/apps/com.sovereign-demo.iframe-html"
},
"extensionPoints": {
"launcher": true,
diff --git a/plugins/com.sovereign-demo.iframe-local/manifest.json b/plugins/com.sovereign-demo.iframe-local/manifest.json
index 90cf4da..86926ac 100644
--- a/plugins/com.sovereign-demo.iframe-local/manifest.json
+++ b/plugins/com.sovereign-demo.iframe-local/manifest.json
@@ -1,9 +1,14 @@
{
"schemaVersion": 1,
"id": "com.sovereign-demo.iframe-local",
- "name": "Iframe Demo",
- "version": "0.0.0",
- "runtime": "iframe-local",
+ "name": "Iframe Local Demo",
+ "version": "0.1.0",
+ "runtime": "iframe",
+ "runtimeConfig": {
+ "engine": "*",
+ "host": "localhost",
+ "port": "8080"
+ },
"permissions": [],
"author": "Sovereign Core Team",
"license": "AGPL-3.0",
@@ -11,12 +16,7 @@
"minPlatformVersion": "0.1.0"
},
"launch": {
- "path": "/apps/com.sovereign.iframe-demo"
- },
- "runtimeConfig": {
- "iframeLocal": {
- "entrypoint": "iframe/index.html"
- }
+ "path": "/apps/com.sovereign-demo.iframe-local"
},
"extensionPoints": {
"launcher": true,
diff --git a/plugins/com.sovereign-demo.iframe-remote/manifest.json b/plugins/com.sovereign-demo.iframe-remote/manifest.json
index f459fb1..62daa65 100644
--- a/plugins/com.sovereign-demo.iframe-remote/manifest.json
+++ b/plugins/com.sovereign-demo.iframe-remote/manifest.json
@@ -1,9 +1,15 @@
{
"schemaVersion": 1,
"id": "com.sovereign-demo.iframe-remote",
- "name": "Remote Demo",
- "version": "0.0.0",
- "runtime": "iframe-remote",
+ "name": "Remote Remote Demo",
+ "version": "0.1.0",
+ "runtime": "iframe",
+ "runtimeConfig": {
+ "engine": "*",
+ "host": "example.com",
+ "https": true,
+ "uri": "/#test"
+ },
"permissions": [],
"author": "Sovereign Core Team",
"license": "AGPL-3.0",
@@ -11,12 +17,7 @@
"minPlatformVersion": "0.1.0"
},
"launch": {
- "path": "/apps/com.sovereign.remote-demo"
- },
- "runtimeConfig": {
- "iframeRemote": {
- "url": "https://example.com/"
- }
+ "path": "/apps/com.sovereign-demo.iframe-remote"
},
"extensionPoints": {
"launcher": true,
diff --git a/plugins/com.sovereign-demo.standalone-html/index.html b/plugins/com.sovereign-demo.standalone-html/index.html
new file mode 100644
index 0000000..0bf2938
--- /dev/null
+++ b/plugins/com.sovereign-demo.standalone-html/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+ Sovereign Standalone Html Demo
+
+
+
+
+ iframe-local
+ Sovereign Standalone Html Demo
+
+ This plugin to serve html + js app directly via Sovereign.
+
+
+
+ Current route:
+
+
+
+
+
+
+
diff --git a/plugins/com.sovereign-demo.standalone-html/manifest.json b/plugins/com.sovereign-demo.standalone-html/manifest.json
new file mode 100644
index 0000000..6955089
--- /dev/null
+++ b/plugins/com.sovereign-demo.standalone-html/manifest.json
@@ -0,0 +1,24 @@
+{
+ "schemaVersion": 1,
+ "id": "com.sovereign-demo.standalone-html",
+ "name": "Standalone Html Demo",
+ "version": "0.1.0",
+ "runtime": "standalone",
+ "runtimeConfig": {
+ "engine": "html",
+ "entrypoint": "index.html"
+ },
+ "permissions": [],
+ "author": "Sovereign Core Team",
+ "license": "AGPL-3.0",
+ "compatibility": {
+ "minPlatformVersion": "0.1.0"
+ },
+ "launch": {
+ "path": "/apps/com.sovereign-demo.standalone-html"
+ },
+ "extensionPoints": {
+ "launcher": true,
+ "sidebar": true
+ }
+}
diff --git a/plugins/com.sovereign-demo.standalone-html/style.css b/plugins/com.sovereign-demo.standalone-html/style.css
new file mode 100644
index 0000000..a3ceed7
--- /dev/null
+++ b/plugins/com.sovereign-demo.standalone-html/style.css
@@ -0,0 +1,73 @@
+:root {
+ color: #172033;
+ background: #f7f9fb;
+ font-family:
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+ sans-serif;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ margin: 0;
+ display: grid;
+ place-items: center;
+ padding: 32px;
+}
+
+main {
+ width: min(100%, 720px);
+ padding: 32px;
+ border: 1px solid #d7dee8;
+ border-radius: 8px;
+ background: #ffffff;
+}
+
+.eyebrow {
+ margin: 0 0 12px;
+ color: #426b5a;
+ font-size: 13px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+h1 {
+ margin: 0;
+ font-size: 32px;
+ line-height: 1.2;
+}
+
+p {
+ max-width: 58ch;
+ color: #4c5870;
+ line-height: 1.6;
+}
+
+nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin: 24px 0 12px;
+}
+
+button {
+ min-height: 40px;
+ padding: 0 14px;
+ border: 1px solid #172033;
+ border-radius: 6px;
+ color: #ffffff;
+ background: #172033;
+ font: inherit;
+ cursor: pointer;
+}
+
+output {
+ display: block;
+ min-height: 24px;
+ margin-top: 16px;
+ color: #426b5a;
+ font-weight: 600;
+}
diff --git a/plugins/com.sovereign-demo.standalone-react/manifest.json b/plugins/com.sovereign-demo.standalone-react/manifest.json
new file mode 100644
index 0000000..d023ed7
--- /dev/null
+++ b/plugins/com.sovereign-demo.standalone-react/manifest.json
@@ -0,0 +1,23 @@
+{
+ "schemaVersion": 1,
+ "id": "com.sovereign-demo.standalone-react",
+ "name": "Next.js + React Demo",
+ "version": "0.1.0",
+ "runtime": "standalone",
+ "runtimeConfig": {
+ "engine": "react"
+ },
+ "permissions": ["auth:read"],
+ "launch": {
+ "path": "/apps/com.sovereign-demo.standalone-react"
+ },
+ "extensionPoints": {
+ "launcher": true,
+ "sidebar": true
+ },
+ "author": "Sovereign Core Team",
+ "license": "AGPL-3.0",
+ "compatibility": {
+ "minPlatformVersion": "0.1.0"
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.sovereign.launcher/src/index.tsx b/plugins/com.sovereign-demo.standalone-react/src/index.tsx
similarity index 100%
rename from plugins/com.sovereign.launcher/src/index.tsx
rename to plugins/com.sovereign-demo.standalone-react/src/index.tsx
diff --git a/plugins/com.sovereign.launcher/manifest.json b/plugins/com.sovereign.launcher/manifest.json
deleted file mode 100644
index c830f0c..0000000
--- a/plugins/com.sovereign.launcher/manifest.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "schemaVersion": 1,
- "id": "com.sovereign.launcher",
- "name": "Launcher",
- "version": "0.0.0",
- "runtime": "route-source",
- "permissions": ["auth:profile"],
- "launch": {
- "path": "/apps/com.sovereign.launcher"
- },
- "extensionPoints": {
- "launcher": true,
- "sidebar": true
- },
- "author": "Sovereign Core Team",
- "license": "AGPL-3.0",
- "compatibility": {
- "minPlatformVersion": "0.1.0"
- }
-}
\ No newline at end of file
diff --git a/tools/generate-app-registry.ts b/tools/generate-app-registry.ts
index e0a3c12..16d64f4 100644
--- a/tools/generate-app-registry.ts
+++ b/tools/generate-app-registry.ts
@@ -15,9 +15,9 @@ const PERMISSIONS_OUTPUT_FILE = path.join(
OUTPUT_DIR,
"permissions.generated.ts"
);
-const INTERNAL_APPS_OUTPUT_FILE = path.join(
+const STANDALONE_APPS_OUTPUT_FILE = path.join(
OUTPUT_DIR,
- "internal-apps.generated.tsx"
+ "standalone-apps.generated.tsx"
);
interface InstalledAppEntry {
@@ -90,15 +90,14 @@ function serializeInstalledApp(entry: InstalledAppEntry) {
};
const manifestJson = JSON.stringify(app, null, 2);
- if (entry.manifest.runtime === "internal") {
- return `${manifestJson.slice(0, -1)},\n module: () => import("./internal-apps.generated").then((module) => ({ default: module.${toComponentName(entry.manifest.id)} }))\n}`;
+ if (
+ entry.manifest.runtime === "standalone" &&
+ entry.manifest.runtimeConfig?.engine === "react"
+ ) {
+ return `${manifestJson.slice(0, -1)},\n module: () => import("./standalone-apps.generated").then((module) => ({ default: module.${toComponentName(entry.manifest.id)} }))\n}`;
}
- if (entry.manifest.runtime !== "route-source") {
- return manifestJson;
- }
-
- return `${manifestJson.slice(0, -1)},\n module: () => import("../../plugins/${entry.pluginDirectory}/src")\n}`;
+ return manifestJson;
}
function generateRegistryFile(installedApps: InstalledAppEntry[]) {
@@ -128,22 +127,24 @@ function generatePermissionsFile(installedApps: InstalledAppEntry[]) {
fs.writeFileSync(PERMISSIONS_OUTPUT_FILE, content, "utf-8");
}
-function generateInternalAppsFile(installedApps: InstalledAppEntry[]) {
- const internalApps = installedApps.filter(
- ({ manifest }) => manifest.runtime === "internal"
+function generateStandaloneAppsFile(installedApps: InstalledAppEntry[]) {
+ const standaloneApps = installedApps.filter(
+ ({ manifest }) =>
+ manifest.runtime === "standalone" &&
+ manifest.runtimeConfig?.engine === "react"
);
- const imports = internalApps
+ const imports = standaloneApps
.map(
({ pluginDirectory }, index) =>
- `import InternalApp${index} from "../../plugins/${pluginDirectory}/src/App";`
+ `import StandaloneApp${index} from "../../plugins/${pluginDirectory}/src";`
)
.join("\n");
- const components = internalApps
+ const components = standaloneApps
.map(
({ manifest }, index) =>
- `export function ${toComponentName(manifest.id)}() {\n const sdk = createAppSdk({ appId: "${manifest.id}" });\n\n return ;\n}`
+ `export function ${toComponentName(manifest.id)}() {\n const sdk = createAppSdk({ appId: "${manifest.id}" });\n\n return ;\n}`
)
.join("\n\n");
@@ -151,7 +152,7 @@ function generateInternalAppsFile(installedApps: InstalledAppEntry[]) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
- fs.writeFileSync(INTERNAL_APPS_OUTPUT_FILE, content, "utf-8");
+ fs.writeFileSync(STANDALONE_APPS_OUTPUT_FILE, content, "utf-8");
}
function toComponentName(appId: string) {
@@ -159,7 +160,7 @@ function toComponentName(appId: string) {
.split(/[^a-zA-Z0-9]/)
.filter(Boolean)
.map((segment) => `${segment[0]?.toUpperCase() ?? ""}${segment.slice(1)}`)
- .join("")}InternalApp`;
+ .join("")}StandaloneApp`;
}
function main() {
@@ -169,7 +170,7 @@ function main() {
generateRegistryFile(installedApps);
generatePermissionsFile(installedApps);
- generateInternalAppsFile(installedApps);
+ generateStandaloneAppsFile(installedApps);
console.log(
`Generated platform/generated metadata with ${installedApps.length} app(s)`
diff --git a/yarn.lock b/yarn.lock
index 3de64b3..bdf1be9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1980,9 +1980,9 @@ __metadata:
languageName: node
linkType: hard
-"com.sovereign-demo.vite-react@workspace:plugins/com.sovereign-demo.vite-react":
+"com.sovereign-demo.dom-react@workspace:plugins/com.sovereign-demo.dom-react":
version: 0.0.0-use.local
- resolution: "com.sovereign-demo.vite-react@workspace:plugins/com.sovereign-demo.vite-react"
+ resolution: "com.sovereign-demo.dom-react@workspace:plugins/com.sovereign-demo.dom-react"
dependencies:
"@babel/core": "npm:^7.29.0"
"@eslint/js": "npm:^10.0.1"