@qpjoy/mx-launcher-standalone 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # @qpjoy/mx-launcher-standalone
2
+
3
+ `launcher-standalone` is the role SDK for capability owners such as MX-H2I and
4
+ Luopan. A standalone launcher owns the local capability plane for one product
5
+ channel. It can host embed apps, but it must not merge its runtime ownership
6
+ with another standalone channel.
7
+
8
+ ## Responsibilities
9
+
10
+ Standalone owns:
11
+
12
+ - local broker lifecycle
13
+ - channel registry registration and heartbeat
14
+ - user/session bridge
15
+ - permission grant enforcement
16
+ - ProductNetwork and route policy application for its own product
17
+ - WireGuard, DNS, PAC, system proxy, and data-plane diagnostics
18
+ - release policy resolution for itself and hosted embed apps
19
+ - embed app download, verification, install, rollback, and launch
20
+ - AppCenter catalog operations when AppCenter is hosted by this channel
21
+ - audit events for privileged operations
22
+
23
+ Standalone does not own another standalone product. If MX-H2I and Luopan are
24
+ both running, each keeps its own network owner, user context, permission grants,
25
+ release policy, and data-plane state.
26
+
27
+ ## Bootstrap
28
+
29
+ Standalone bootstrap should follow this sequence:
30
+
31
+ 1. Load the product manifest and local install identity.
32
+ 2. Fetch ProductNetwork, release policy, rollout, AppCenter catalog, and
33
+ capability policy from Internal.
34
+ 3. Use signed local cache when Internal is unavailable.
35
+ 4. Start the broker listener.
36
+ 5. Register the channel record in the per-user registry.
37
+ 6. Apply or repair local data plane for this product only.
38
+ 7. Start built-in embed apps such as AppCenter when configured.
39
+
40
+ The broker may run in the Electron main process or a local helper, but it must
41
+ present the same protocol to embed clients.
42
+
43
+ ## Broker Handshake
44
+
45
+ Embed clients connect with:
46
+
47
+ ```ts
48
+ {
49
+ appId: 'appcenter',
50
+ launcherMode: 'embed',
51
+ sdkVersion: '2.3.4',
52
+ protocolVersion: '2',
53
+ minBrokerAbiVersion: '2',
54
+ requestedCapabilities: ['catalog.read', 'app.install']
55
+ }
56
+ ```
57
+
58
+ The standalone broker must:
59
+
60
+ 1. verify protocol and ABI compatibility.
61
+ 2. verify the embed app is allowed to bind to this standalone channel.
62
+ 3. resolve server policy or signed local cache.
63
+ 4. issue a scoped capability session.
64
+ 5. return only the data the embed app is permitted to see.
65
+
66
+ Do not send WireGuard keys, DNS ownership state, long-lived user tokens, or
67
+ system proxy handles to embed apps.
68
+
69
+ The issued session should report `networkScope: 'broker-session'`. The
70
+ standalone channel keeps `networkScope: 'owner'` and remains the only runtime
71
+ peer lease owner for that app group.
72
+
73
+ ## AppCenter And Hosted Apps
74
+
75
+ AppCenter is a privileged embed app hosted by MX-H2I. It is not a standalone
76
+ owner. AppCenter may request catalog, install, launch, and update actions, but
77
+ the MX-H2I standalone broker performs the actual download, verification,
78
+ installation, launch, and audit.
79
+
80
+ H2O and future AppCenter applications are normal embed apps. They should not get
81
+ their own IP, WireGuard interface, DNS owner, or system proxy owner unless they
82
+ are explicitly converted into standalone products.
83
+
84
+ ## Release And Install Owner
85
+
86
+ Standalone release:
87
+
88
+ - downloaded as signed OS artifacts from OSS/CDN.
89
+ - resolved by server release policy.
90
+ - applied by the standalone updater.
91
+
92
+ Hosted embed release:
93
+
94
+ - downloaded as signed embed bundles from OSS/CDN.
95
+ - resolved by the same server release policy.
96
+ - verified and installed by the standalone broker.
97
+ - launched through the broker with a scoped session.
98
+
99
+ NPM package versions are build-time SDK versions. User machines update from
100
+ signed artifacts, not from npm.
101
+
102
+ ## K8s Admin Integration
103
+
104
+ K8s admin should be able to inspect and operate standalone channels:
105
+
106
+ - broker status and ABI version
107
+ - channel registry heartbeat
108
+ - active embed sessions
109
+ - data-plane health
110
+ - ProductNetwork and Domestic materialization status
111
+ - release and rollout status
112
+ - hosted embed app install state
113
+ - audit trail for grants, launches, installs, updates, and rollbacks
114
+
115
+ Operational actions should be idempotent:
116
+
117
+ - refresh policy cache
118
+ - sync AppCenter catalog
119
+ - force hosted app update
120
+ - rollback hosted app
121
+ - diagnose embed connection
122
+ - reapply standalone data plane
@@ -0,0 +1,65 @@
1
+ import { routePlanFromSnapshot, shouldRefreshNetwork, type AnonymousEnrollment, type AnonymousEnrollmentRequest, type LauncherClient, type LauncherClientOptions, type LauncherNetworkLease, type LauncherNetworkLeaseInput, type LauncherNetworkSession, type LauncherNetworkSnapshot, type LauncherNetworkSnapshotInput, type LauncherProductNetwork, type LauncherProductNetworkInput, type LauncherRoutePlan, type LauncherWireGuardKeyPair, type LauncherWireGuardKeyProvider } from '@qpjoy/mx-launcher-core';
2
+ export interface StandaloneLauncherOptions extends LauncherClientOptions {
3
+ productId?: string;
4
+ installId?: string;
5
+ deviceId?: string;
6
+ siteId?: string;
7
+ publicKey?: string;
8
+ privateKey?: string;
9
+ keyPair?: LauncherWireGuardKeyPair;
10
+ keyProvider?: LauncherWireGuardKeyProvider;
11
+ deviceLabel?: string;
12
+ }
13
+ export interface StandaloneSnapshotOptions extends Omit<LauncherNetworkSnapshotInput, 'appId' | 'launcherMode' | 'installId' | 'deviceId' | 'siteId' | 'publicKey'> {
14
+ installId?: string;
15
+ deviceId?: string;
16
+ siteId?: string | null;
17
+ publicKey?: string | null;
18
+ }
19
+ export interface StandaloneLeaseOptions extends Omit<LauncherNetworkLeaseInput, 'productId' | 'mode' | 'installId' | 'deviceId' | 'siteId' | 'publicKey'> {
20
+ installId?: string;
21
+ deviceId?: string;
22
+ siteId?: string | null;
23
+ publicKey?: string | null;
24
+ }
25
+ export interface StandaloneNetworkSessionOptions extends StandaloneLeaseOptions {
26
+ privateKey?: string | null;
27
+ keyPair?: LauncherWireGuardKeyPair | null;
28
+ keyProvider?: LauncherWireGuardKeyProvider | null;
29
+ snapshotRequestId?: string | null;
30
+ }
31
+ export interface StandaloneConnectOptions extends Omit<AnonymousEnrollmentRequest, 'productId' | 'platform' | 'installId' | 'deviceId' | 'siteId' | 'publicKey'> {
32
+ installId?: string;
33
+ deviceId?: string;
34
+ siteId?: string;
35
+ publicKey?: string;
36
+ platform?: string;
37
+ }
38
+ export interface StandaloneUserConnectOptions extends StandaloneSnapshotOptions {
39
+ userId: string;
40
+ }
41
+ export interface StandaloneConnectionResult {
42
+ enrollment: AnonymousEnrollment;
43
+ snapshot: LauncherNetworkSnapshot;
44
+ routePlan: LauncherRoutePlan;
45
+ }
46
+ export interface StandaloneLauncher {
47
+ readonly mode: 'standalone';
48
+ readonly productId: string;
49
+ readonly client: LauncherClient;
50
+ listProducts(): Promise<LauncherProductNetwork[]>;
51
+ getProduct(productId?: string): Promise<LauncherProductNetwork>;
52
+ upsertProduct(productId: string, input: LauncherProductNetworkInput): Promise<LauncherProductNetwork>;
53
+ enrollLease(options?: StandaloneLeaseOptions): Promise<LauncherNetworkLease>;
54
+ connectNetwork(options?: StandaloneNetworkSessionOptions): Promise<LauncherNetworkSession>;
55
+ createSnapshot(options?: StandaloneSnapshotOptions): Promise<LauncherNetworkSnapshot>;
56
+ createRoutePlan(options?: StandaloneSnapshotOptions): Promise<LauncherRoutePlan>;
57
+ connectAnonymous(options?: StandaloneConnectOptions): Promise<StandaloneConnectionResult>;
58
+ connectUser(options: StandaloneUserConnectOptions): Promise<{
59
+ snapshot: LauncherNetworkSnapshot;
60
+ routePlan: LauncherRoutePlan;
61
+ }>;
62
+ shouldRefreshNetwork: typeof shouldRefreshNetwork;
63
+ }
64
+ export declare function createStandaloneLauncher(options: StandaloneLauncherOptions): StandaloneLauncher;
65
+ export { routePlanFromSnapshot, shouldRefreshNetwork };
package/dist/index.js ADDED
@@ -0,0 +1,108 @@
1
+ import { createLauncherClient, createLauncherNetworkSession, routePlanFromSnapshot, shouldRefreshNetwork } from '@qpjoy/mx-launcher-core';
2
+ export function createStandaloneLauncher(options) {
3
+ const productId = normalizeStandaloneProductId(options.productId);
4
+ const client = createLauncherClient(options);
5
+ async function createSnapshot(input = {}) {
6
+ return client.createSnapshot({
7
+ ...input,
8
+ appId: productId,
9
+ launcherMode: 'standalone',
10
+ installId: input.installId ?? options.installId,
11
+ deviceId: input.deviceId ?? options.deviceId,
12
+ siteId: input.siteId ?? options.siteId,
13
+ publicKey: input.publicKey ?? options.publicKey
14
+ });
15
+ }
16
+ async function enrollLease(input = {}) {
17
+ return client.enrollLease({
18
+ ...input,
19
+ appId: input.appId ?? productId,
20
+ productId,
21
+ mode: 'standalone',
22
+ installId: input.installId ?? options.installId,
23
+ deviceId: input.deviceId ?? options.deviceId,
24
+ siteId: input.siteId ?? options.siteId,
25
+ publicKey: input.publicKey ?? options.publicKey,
26
+ deviceLabel: input.deviceLabel ?? options.deviceLabel,
27
+ requestedBy: input.requestedBy ?? 'standalone-sdk'
28
+ });
29
+ }
30
+ async function connectNetwork(input = {}) {
31
+ return createLauncherNetworkSession(client, {
32
+ ...input,
33
+ productId,
34
+ appId: productId,
35
+ mode: 'standalone',
36
+ launcherMode: 'standalone',
37
+ installId: input.installId ?? options.installId,
38
+ deviceId: input.deviceId ?? options.deviceId,
39
+ siteId: input.siteId ?? options.siteId,
40
+ publicKey: input.publicKey ?? options.publicKey,
41
+ privateKey: input.privateKey ?? options.privateKey,
42
+ keyPair: input.keyPair ?? options.keyPair,
43
+ keyProvider: input.keyProvider ?? options.keyProvider,
44
+ deviceLabel: input.deviceLabel ?? options.deviceLabel,
45
+ requestedBy: input.requestedBy ?? 'standalone-sdk'
46
+ });
47
+ }
48
+ return {
49
+ mode: 'standalone',
50
+ productId,
51
+ client,
52
+ listProducts() {
53
+ return client.listProducts();
54
+ },
55
+ getProduct(targetProductId = productId) {
56
+ return client.getProduct(targetProductId);
57
+ },
58
+ upsertProduct(targetProductId, input) {
59
+ return client.upsertProduct(targetProductId, input);
60
+ },
61
+ enrollLease,
62
+ connectNetwork,
63
+ createSnapshot,
64
+ async createRoutePlan(input) {
65
+ return routePlanFromSnapshot(await createSnapshot(input));
66
+ },
67
+ async connectAnonymous(input = {}) {
68
+ const enrollment = await client.enrollAnonymous({
69
+ ...input,
70
+ productId,
71
+ platform: input.platform ?? 'standalone',
72
+ installId: input.installId ?? options.installId,
73
+ deviceId: input.deviceId ?? options.deviceId,
74
+ siteId: input.siteId ?? options.siteId,
75
+ publicKey: input.publicKey ?? options.publicKey,
76
+ deviceLabel: input.deviceLabel ?? options.deviceLabel,
77
+ relayMode: input.relayMode ?? 'h2i'
78
+ });
79
+ const snapshot = await createSnapshot({
80
+ installId: enrollment.installId,
81
+ deviceId: enrollment.deviceId,
82
+ siteId: enrollment.siteId,
83
+ publicKey: enrollment.publicKey ?? input.publicKey ?? options.publicKey,
84
+ requestId: input.requestId
85
+ });
86
+ return {
87
+ enrollment,
88
+ snapshot,
89
+ routePlan: routePlanFromSnapshot(snapshot)
90
+ };
91
+ },
92
+ async connectUser(input) {
93
+ const snapshot = await createSnapshot(input);
94
+ return {
95
+ snapshot,
96
+ routePlan: routePlanFromSnapshot(snapshot)
97
+ };
98
+ },
99
+ shouldRefreshNetwork
100
+ };
101
+ }
102
+ export { routePlanFromSnapshot, shouldRefreshNetwork };
103
+ function normalizeStandaloneProductId(productId) {
104
+ const trimmed = (productId ?? 'launcher').trim();
105
+ if (!trimmed)
106
+ throw new Error('Standalone launcher productId is required');
107
+ return trimmed;
108
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@qpjoy/mx-launcher-standalone",
3
+ "version": "0.2.0",
4
+ "description": "Standalone MX Launcher shell adapter that uses launcher-core for AppCenter-wide network control.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "package.json"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "dependencies": {
23
+ "@qpjoy/mx-launcher-core": "^0.2.0"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.7.3"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.json",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit"
31
+ }
32
+ }