@nuxtjp/local-runtime 0.1.4 → 0.1.5

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.ja.md CHANGED
@@ -9,7 +9,7 @@
9
9
  `0.1.1`はnpmで公開済みです。Node `^22.19.0`または`^24.11.0`、Nuxt `^4.5.1`、Vue `^3.5.40`を使用してください。
10
10
 
11
11
  ```sh
12
- npm install @nuxtjp/local-runtime@0.1.4
12
+ npm install @nuxtjp/local-runtime
13
13
  ```
14
14
 
15
15
  ```ts
@@ -69,3 +69,6 @@ module/client API、loopback endpointの規則、明示接続の動作、ライ
69
69
  [使い方](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/docs/getting-started.md) · [Schema](schemas) · [実装](https://github.com/nuxtjp/nuxt-local-runtime/tree/main/src) · [テスト](https://github.com/nuxtjp/nuxt-local-runtime/tree/main/test) · [貢献方法](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/CONTRIBUTING.md) · [セキュリティ報告](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/SECURITY.md)
70
70
 
71
71
  コードは[Apache-2.0](LICENSE)です。[NOTICE](NOTICE)と[以前の許諾](LICENSE-PREVIOUS)を保持してください。
72
+
73
+ Dependency updates: [repository policy](docs/dependency-updates.md).
74
+ Registry releases: [version tags and GitHub OIDC](docs/registry-release.md).
package/README.md CHANGED
@@ -9,7 +9,7 @@ Connect a browser interface to one explicitly configured local Rust runtime, aft
9
9
  Install the package version `0.1.4` after it is available on npm. Use Node `^22.19.0` or `^24.11.0`, Nuxt `^4.5.1` and Vue `^3.5.40`.
10
10
 
11
11
  ```sh
12
- npm install @nuxtjp/local-runtime@0.1.4
12
+ npm install @nuxtjp/local-runtime
13
13
  ```
14
14
 
15
15
  ```ts
@@ -73,3 +73,8 @@ Code: [Apache-2.0](LICENSE). Retain [NOTICE](NOTICE) and the prior grants in [LI
73
73
  ## Consumer dependency security
74
74
 
75
75
  See [dependency security backports](security/README.md) before installing this package in a Nuxt application. pnpm consumers must explicitly apply the included backports and verify their locked dependency tree; ordinary npm installation does not apply them.
76
+
77
+ [OSS responsibility and source completion](docs/oss-source-contract.md) defines the supported package boundary and required acceptance. It does not declare unexecuted source, distribution or production checks complete.
78
+
79
+ Dependency updates: [repository policy](docs/dependency-updates.md).
80
+ Registry releases: [version tags and GitHub OIDC](docs/registry-release.md).
package/dist/module.json CHANGED
@@ -4,9 +4,9 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^4.5.1"
6
6
  },
7
- "version": "0.1.4",
7
+ "version": "0.1.5",
8
8
  "builder": {
9
- "@nuxt/module-builder": "1.0.2",
9
+ "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "unknown"
11
11
  }
12
12
  }
@@ -1,4 +1,3 @@
1
- import type { ViewEnvelope } from '../../shared/types.js';
2
1
  export declare function useLocalRuntime(): {
3
2
  state: Readonly<import("vue").Ref<{
4
3
  readonly phase: import("../../client/index.js").ConnectionPhase;
@@ -87,6 +86,6 @@ export declare function useLocalRuntime(): {
87
86
  }>>;
88
87
  connected: import("vue").ComputedRef<boolean>;
89
88
  connect: (capabilities?: string[]) => Promise<void>;
90
- view: (capabilityId: string, viewId: string) => Promise<ViewEnvelope>;
89
+ view: (capabilityId: string, viewId: string) => Promise<import("../../client/index.js").ViewEnvelope>;
91
90
  disconnect: () => Promise<void>;
92
91
  };
@@ -1,51 +1,10 @@
1
- import { computed, readonly, ref } from "vue";
2
1
  import { useRuntimeConfig } from "#imports";
3
2
  import { LocalRuntimeClient } from "../../client/client.js";
3
+ import { createLocalRuntimeConnection } from "../connection.js";
4
4
  export function useLocalRuntime() {
5
5
  const config = useRuntimeConfig();
6
- const state = ref({
7
- phase: "idle",
8
- manifest: null,
9
- error: null
10
- });
11
- let client = null;
12
- async function connect(capabilities = []) {
13
- state.value = { phase: "connecting", manifest: null, error: null };
14
- try {
15
- const endpoint = config.public.nuxtJpLocalRuntime.endpoint;
16
- client = new LocalRuntimeClient({
17
- endpoint,
18
- origin: window.location.origin
19
- });
20
- const manifest = await client.manifest();
21
- await client.connect(capabilities);
22
- state.value = { phase: "connected", manifest, error: null };
23
- } catch (error) {
24
- client = null;
25
- state.value = {
26
- phase: error instanceof DOMException && error.name === "NotAllowedError" ? "denied" : "unavailable",
27
- manifest: null,
28
- error: error instanceof Error ? error.message : "\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F"
29
- };
30
- }
31
- }
32
- async function view(capabilityId, viewId) {
33
- if (!client) throw new Error("local runtime is not connected");
34
- return client.view(capabilityId, viewId);
35
- }
36
- async function disconnect() {
37
- try {
38
- await client?.disconnect();
39
- } finally {
40
- client = null;
41
- state.value = { phase: "idle", manifest: null, error: null };
42
- }
43
- }
44
- return {
45
- state: readonly(state),
46
- connected: computed(() => state.value.phase === "connected"),
47
- connect,
48
- view,
49
- disconnect
50
- };
6
+ return createLocalRuntimeConnection(() => new LocalRuntimeClient({
7
+ endpoint: config.public.nuxtJpLocalRuntime.endpoint,
8
+ origin: window.location.origin
9
+ }));
51
10
  }
@@ -0,0 +1,94 @@
1
+ import type { LocalRuntimeClient } from '../client/client.js';
2
+ import type { ViewEnvelope } from '../shared/types.js';
3
+ /** Own each connection attempt separately; only the latest operation publishes state. */
4
+ export declare function createLocalRuntimeConnection(createClient: () => LocalRuntimeClient): {
5
+ state: Readonly<import("vue").Ref<{
6
+ readonly phase: import("../client/index.js").ConnectionPhase;
7
+ readonly manifest: {
8
+ readonly schema: "nuxtjp://local-runtime/manifest/v1";
9
+ readonly runtime_id: string;
10
+ readonly protocol_version: "1";
11
+ readonly mode: "local-simulation" | "local-production";
12
+ readonly external_actions: false;
13
+ readonly engine: {
14
+ readonly kind: "rust-v8";
15
+ readonly status: "disabled" | "simulated" | "ready";
16
+ readonly isolation: "in-process-simulation" | "process";
17
+ readonly arbitrary_network: false;
18
+ readonly arbitrary_filesystem: false;
19
+ };
20
+ readonly security: {
21
+ readonly default_band: "sealed";
22
+ readonly external_egress: "deny";
23
+ readonly allowed_origins: readonly string[];
24
+ readonly limits: {
25
+ readonly max_request_bytes: number;
26
+ readonly max_response_bytes: number;
27
+ readonly max_messages_per_minute: number;
28
+ readonly max_bytes_per_minute: number;
29
+ readonly session_ttl_seconds: number;
30
+ };
31
+ };
32
+ readonly capabilities: readonly {
33
+ readonly id: string;
34
+ readonly service_id: string;
35
+ readonly description: string;
36
+ readonly output_band: "session" | "public";
37
+ readonly payload_schema: {
38
+ readonly id: string;
39
+ readonly fields: readonly {
40
+ readonly name: string;
41
+ readonly kind: import("../client/index.js").PayloadValueKind;
42
+ }[];
43
+ };
44
+ }[];
45
+ } | null;
46
+ readonly error: string | null;
47
+ }, {
48
+ readonly phase: import("../client/index.js").ConnectionPhase;
49
+ readonly manifest: {
50
+ readonly schema: "nuxtjp://local-runtime/manifest/v1";
51
+ readonly runtime_id: string;
52
+ readonly protocol_version: "1";
53
+ readonly mode: "local-simulation" | "local-production";
54
+ readonly external_actions: false;
55
+ readonly engine: {
56
+ readonly kind: "rust-v8";
57
+ readonly status: "disabled" | "simulated" | "ready";
58
+ readonly isolation: "in-process-simulation" | "process";
59
+ readonly arbitrary_network: false;
60
+ readonly arbitrary_filesystem: false;
61
+ };
62
+ readonly security: {
63
+ readonly default_band: "sealed";
64
+ readonly external_egress: "deny";
65
+ readonly allowed_origins: readonly string[];
66
+ readonly limits: {
67
+ readonly max_request_bytes: number;
68
+ readonly max_response_bytes: number;
69
+ readonly max_messages_per_minute: number;
70
+ readonly max_bytes_per_minute: number;
71
+ readonly session_ttl_seconds: number;
72
+ };
73
+ };
74
+ readonly capabilities: readonly {
75
+ readonly id: string;
76
+ readonly service_id: string;
77
+ readonly description: string;
78
+ readonly output_band: "session" | "public";
79
+ readonly payload_schema: {
80
+ readonly id: string;
81
+ readonly fields: readonly {
82
+ readonly name: string;
83
+ readonly kind: import("../client/index.js").PayloadValueKind;
84
+ }[];
85
+ };
86
+ }[];
87
+ } | null;
88
+ readonly error: string | null;
89
+ }>>;
90
+ connected: import("vue").ComputedRef<boolean>;
91
+ connect: (capabilities?: string[]) => Promise<void>;
92
+ view: (capabilityId: string, viewId: string) => Promise<ViewEnvelope>;
93
+ disconnect: () => Promise<void>;
94
+ };
@@ -0,0 +1,57 @@
1
+ import { computed, readonly, ref } from "vue";
2
+ export function createLocalRuntimeConnection(createClient) {
3
+ const state = ref({ phase: "idle", manifest: null, error: null });
4
+ let generation = 0;
5
+ let client = null;
6
+ let pending = null;
7
+ async function connect(capabilities = []) {
8
+ const operation = ++generation;
9
+ const previous = pending || client;
10
+ client = null;
11
+ pending = null;
12
+ state.value = { phase: "connecting", manifest: null, error: null };
13
+ let attempt = null;
14
+ try {
15
+ attempt = createClient();
16
+ pending = attempt;
17
+ await previous?.disconnect();
18
+ if (operation !== generation) return;
19
+ const manifest = await attempt.manifest();
20
+ await attempt.connect(capabilities);
21
+ if (operation !== generation) {
22
+ await attempt.disconnect();
23
+ return;
24
+ }
25
+ client = attempt;
26
+ pending = null;
27
+ state.value = { phase: "connected", manifest, error: null };
28
+ } catch (error) {
29
+ if (operation !== generation) return;
30
+ try {
31
+ await attempt?.disconnect();
32
+ } catch {
33
+ }
34
+ if (operation !== generation) return;
35
+ pending = null;
36
+ client = null;
37
+ state.value = {
38
+ phase: error instanceof DOMException && error.name === "NotAllowedError" ? "denied" : "unavailable",
39
+ manifest: null,
40
+ error: error instanceof Error ? error.message : "\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F"
41
+ };
42
+ }
43
+ }
44
+ async function view(capabilityId, viewId) {
45
+ if (!client || state.value.phase !== "connected") throw new Error("local runtime is not connected");
46
+ return client.view(capabilityId, viewId);
47
+ }
48
+ async function disconnect() {
49
+ ++generation;
50
+ const previous = pending || client;
51
+ pending = null;
52
+ client = null;
53
+ state.value = { phase: "idle", manifest: null, error: null };
54
+ await previous?.disconnect();
55
+ }
56
+ return { state: readonly(state), connected: computed(() => state.value.phase === "connected"), connect, view, disconnect };
57
+ }
@@ -12,6 +12,7 @@ export declare class LocalRuntimeClient {
12
12
  private readonly fetcher;
13
13
  private readonly maximumResponseBytes;
14
14
  private readonly timeoutMilliseconds;
15
+ private operation;
15
16
  private grant;
16
17
  private runtimeManifest;
17
18
  constructor(options: LocalRuntimeClientOptions);
@@ -19,4 +20,5 @@ export declare class LocalRuntimeClient {
19
20
  connect(capabilities: string[]): Promise<SessionGrant>;
20
21
  view(capabilityId: string, viewId: string): Promise<ViewEnvelope>;
21
22
  disconnect(): Promise<void>;
23
+ private revoke;
22
24
  }
@@ -12,6 +12,7 @@ export class LocalRuntimeClient {
12
12
  fetcher;
13
13
  maximumResponseBytes;
14
14
  timeoutMilliseconds;
15
+ operation = 0;
15
16
  grant = null;
16
17
  runtimeManifest = null;
17
18
  constructor(options) {
@@ -28,6 +29,7 @@ export class LocalRuntimeClient {
28
29
  }
29
30
  }
30
31
  async manifest() {
32
+ const operation = this.operation;
31
33
  const value = await localJson(
32
34
  this.fetcher,
33
35
  runtimeUrl(this.endpoint, "/v1/manifest"),
@@ -38,6 +40,7 @@ export class LocalRuntimeClient {
38
40
  if (!isLocalRuntimeManifest(value) || !value.security.allowed_origins.includes(this.origin)) {
39
41
  throw new Error("invalid runtime manifest");
40
42
  }
43
+ if (operation !== this.operation) throw new Error("local runtime operation was superseded");
41
44
  this.runtimeManifest = value;
42
45
  return value;
43
46
  }
@@ -52,6 +55,12 @@ export class LocalRuntimeClient {
52
55
  if (capabilities.some((capability) => !available.has(capability))) {
53
56
  throw new Error("requested capability is not declared");
54
57
  }
58
+ const runtimeManifest = this.runtimeManifest;
59
+ const operation = ++this.operation;
60
+ const previous = this.grant;
61
+ this.grant = null;
62
+ if (previous) await this.revoke(previous.token);
63
+ if (operation !== this.operation) throw new Error("local runtime operation was superseded");
55
64
  const request = {
56
65
  schema: "nuxtjp://local-runtime/session-request/v1",
57
66
  origin: this.origin,
@@ -69,10 +78,18 @@ export class LocalRuntimeClient {
69
78
  const expected = [...new Set(capabilities)].sort();
70
79
  const observed = isSessionGrant(value) ? [...value.granted_capabilities].sort() : [];
71
80
  const now = Math.floor(Date.now() / 1e3);
72
- const limits = this.runtimeManifest.security.limits;
81
+ const limits = runtimeManifest.security.limits;
73
82
  if (!isSessionGrant(value) || value.audience !== this.origin || value.expires_at_unix_seconds <= now || value.expires_at_unix_seconds > now + limits.session_ttl_seconds + 5 || JSON.stringify(observed) !== JSON.stringify(expected) || value.budget.max_messages > limits.max_messages_per_minute || value.budget.max_bytes > limits.max_bytes_per_minute) {
74
83
  throw new Error("invalid runtime session grant");
75
84
  }
85
+ if (operation !== this.operation) {
86
+ try {
87
+ await this.revoke(value.token);
88
+ } catch {
89
+ throw new Error("superseded runtime session cleanup failed");
90
+ }
91
+ throw new Error("local runtime operation was superseded");
92
+ }
76
93
  this.grant = value;
77
94
  return value;
78
95
  }
@@ -83,6 +100,9 @@ export class LocalRuntimeClient {
83
100
  if (!this.grant.granted_capabilities.includes(capabilityId)) {
84
101
  throw new Error("capability was not granted");
85
102
  }
103
+ const operation = this.operation;
104
+ const grant = this.grant;
105
+ const runtimeManifest = this.runtimeManifest;
86
106
  const request = {
87
107
  schema: "nuxtjp://local-runtime/view-request/v1",
88
108
  capability_id: capabilityId,
@@ -92,20 +112,26 @@ export class LocalRuntimeClient {
92
112
  const value = await localJson(
93
113
  this.fetcher,
94
114
  runtimeUrl(this.endpoint, "/v1/view"),
95
- localRequest("POST", { body: request, token: this.grant.token }),
115
+ localRequest("POST", { body: request, token: grant.token }),
96
116
  this.maximumResponseBytes,
97
117
  this.timeoutMilliseconds
98
118
  );
99
- const capability = this.runtimeManifest.capabilities.find((item) => item.id === capabilityId);
119
+ if (operation !== this.operation || this.grant !== grant) {
120
+ throw new Error("local runtime operation was superseded");
121
+ }
122
+ const capability = runtimeManifest.capabilities.find((item) => item.id === capabilityId);
100
123
  if (!capability || !isViewEnvelope(value, capability.payload_schema) || value.capability_id !== capabilityId || value.view_id !== viewId) {
101
124
  throw new Error("invalid runtime view");
102
125
  }
103
126
  return value;
104
127
  }
105
128
  async disconnect() {
129
+ ++this.operation;
106
130
  const token = this.grant?.token;
107
131
  this.grant = null;
108
- if (!token) return;
132
+ if (token) await this.revoke(token);
133
+ }
134
+ async revoke(token) {
109
135
  await localJson(
110
136
  this.fetcher,
111
137
  runtimeUrl(this.endpoint, "/v1/session"),
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "@nuxtjp/local-runtime",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Fail-closed Nuxt client for a loopback local runtime",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
- "packageManager": "pnpm@10.29.3",
8
7
  "engines": {
9
8
  "node": "^22.19.0 || ^24.11.0",
10
9
  "pnpm": ">=10 <11"
@@ -41,31 +40,23 @@
41
40
  },
42
41
  "./schema": "./schemas/local-runtime-v1.schema.json"
43
42
  },
44
- "scripts": {
45
- "build": "nuxt-module-build build && nuxt build playground",
46
- "typecheck": "vue-tsc --noEmit --project tsconfig.json",
47
- "test": "vitest run && pnpm run test:dependency-security && pnpm run test:consumer-security",
48
- "prepack": "pnpm run test && pnpm run typecheck && pnpm run build",
49
- "test:dependency-security": "node --test scripts/dependency-security.check.cjs",
50
- "test:consumer-security": "node --test scripts/consumer-security.check.mjs"
51
- },
52
43
  "peerDependencies": {
53
44
  "nuxt": "^4.5.1",
54
45
  "vue": "^3.5.40"
55
46
  },
56
47
  "dependencies": {
57
- "@nuxt/kit": "4.5.2",
58
- "@nuxt/schema": "4.5.1"
48
+ "@nuxt/kit": "4.6.1",
49
+ "@nuxt/schema": "4.6.1"
59
50
  },
60
51
  "devDependencies": {
61
- "@nuxt/module-builder": "1.0.2",
52
+ "@nuxt/module-builder": "1.0.3",
62
53
  "@types/node": "24.12.0",
63
54
  "ajv": "8.20.0",
64
- "nuxt": "4.5.2",
55
+ "nuxt": "4.6.1",
65
56
  "typescript": "5.9.3",
66
- "vitest": "4.1.11",
57
+ "vitest": "5.0.3",
67
58
  "vue": "3.5.43",
68
- "vue-tsc": "3.2.8"
59
+ "vue-tsc": "3.3.13"
69
60
  },
70
61
  "repository": {
71
62
  "type": "git",
@@ -80,17 +71,11 @@
80
71
  "registry": "https://registry.npmjs.org",
81
72
  "provenance": true
82
73
  },
83
- "pnpm": {
84
- "overrides": {
85
- "@nuxt/devtools>simple-git": "4.0.2",
86
- "simple-git": "4.0.2",
87
- "@simple-git/argv-parser": "2.0.1",
88
- "esbuild": "0.28.2"
89
- },
90
- "patchedDependencies": {
91
- "braces@3.0.3": "patches/braces@3.0.3.patch",
92
- "node-forge@1.4.0": "patches/node-forge@1.4.0.patch",
93
- "simple-git@4.0.2": "security/patches/simple-git@4.0.2.patch"
94
- }
74
+ "scripts": {
75
+ "build": "nuxt-module-build build && nuxt build playground",
76
+ "typecheck": "vue-tsc --noEmit --project tsconfig.json",
77
+ "test": "vitest run && pnpm run test:dependency-security && pnpm run test:consumer-security",
78
+ "test:dependency-security": "node --test scripts/dependency-security.check.cjs",
79
+ "test:consumer-security": "node --test scripts/consumer-security.check.mjs"
95
80
  }
96
- }
81
+ }