@bardioc/create-bardioc-app 0.5.6 → 0.6.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 +1 -1
- package/package.json +1 -1
- package/templates/_base/README.md +1 -1
- package/templates/_base/public/app-manifest.json +1 -2
- package/templates/angular/.env.example +4 -1
- package/templates/angular/package.json +2 -2
- package/templates/angular/scripts/dev-env.mjs +62 -0
- package/templates/angular/scripts/dev.mjs +56 -0
- package/templates/angular/scripts/sync-runtime-env.mjs +2 -28
- package/templates/angular/src/app/app.component.ts +44 -26
- package/templates/angular/src/app/bardioc.token.ts +2 -2
- package/templates/angular/src/main.ts +6 -8
- package/templates/angular/src/styles.css +4 -0
- package/templates/nextjs/.env.example +4 -1
- package/templates/nextjs/app/page.tsx +18 -15
- package/templates/nextjs/next.config.ts +1 -1
- package/templates/nextjs/package.json +3 -3
- package/templates/nextjs/scripts/dev-env.mjs +62 -0
- package/templates/nextjs/scripts/dev.mjs +56 -0
- package/templates/preact/.env.example +3 -0
- package/templates/preact/package.json +2 -2
- package/templates/preact/src/App.tsx +10 -10
- package/templates/preact/src/main.tsx +3 -3
- package/templates/preact/vite.config.ts +4 -1
- package/templates/solid/.env.example +3 -0
- package/templates/solid/package.json +2 -2
- package/templates/solid/src/App.tsx +10 -10
- package/templates/solid/src/bardioc-sdk.tsx +11 -23
- package/templates/solid/src/main.tsx +5 -3
- package/templates/solid/vite.config.ts +4 -1
- package/templates/svelte/.env.example +3 -0
- package/templates/svelte/package.json +2 -2
- package/templates/svelte/src/App.svelte +12 -12
- package/templates/svelte/src/main.ts +5 -5
- package/templates/svelte/vite.config.ts +4 -1
- package/templates/vite/.env.example +3 -0
- package/templates/vite/CLAUDE.md +34 -7
- package/templates/vite/package.json +3 -3
- package/templates/vite/public/app-manifest.json +0 -1
- package/templates/vite/src/auth/onboarding.tsx +7 -5
- package/templates/vite/src/climate/conditions-chart.tsx +2 -2
- package/templates/vite/src/climate/overview-view.tsx +1 -1
- package/templates/vite/src/climate/stations-view.tsx +3 -10
- package/templates/vite/src/climate/use-climate.ts +8 -8
- package/templates/vite/src/events/live-events-view.tsx +8 -10
- package/templates/vite/src/graph/explorer-view.tsx +7 -14
- package/templates/vite/src/graph/requests.ts +16 -19
- package/templates/vite/src/locales/de.json +7 -8
- package/templates/vite/src/locales/en.json +45 -46
- package/templates/vite/src/main.tsx +3 -3
- package/templates/vite/src/profile/profile-view.tsx +5 -12
- package/templates/vite/src/profile/use-profile.ts +3 -3
- package/templates/vite/src/shell/about-dialog.tsx +5 -11
- package/templates/vite/src/shell/app-shell.tsx +63 -22
- package/templates/vite/src/shell/header.tsx +24 -24
- package/templates/vite/vite.config.ts +4 -1
- package/templates/vue/.env.example +3 -0
- package/templates/vue/package.json +2 -2
- package/templates/vue/src/App.vue +12 -12
- package/templates/vue/src/bardioc-sdk.ts +6 -15
- package/templates/vue/src/main.ts +5 -4
- package/templates/vue/vite.config.ts +4 -1
- package/templates/angular/scripts/dev-auth-proxy.mjs +0 -17
- package/templates/angular/src/app/bardioc-bridge.ts +0 -5
- package/templates/nextjs/scripts/dev-auth-proxy.mjs +0 -17
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ Generated apps can run standalone with the `DEV_SECRET` dev-auth proxy.
|
|
|
72
72
|
2. Fill in `VITE_APP_NAME`, `DEV_SECRET`, and `API_BASE_URL`
|
|
73
73
|
3. Run `npm run dev`
|
|
74
74
|
|
|
75
|
-
When running outside the host iframe, the SDK proxies `/rest/*` to `API_BASE_URL` with the `X-Dev-Secret` header and `installDevBridge()` handles SDK messages. Vite templates do this through the `bardiocDevAuth` plugin; the angular and nextjs templates run `scripts/dev
|
|
75
|
+
When running outside the host iframe, the SDK proxies `/rest/*` to `API_BASE_URL` with the `X-Dev-Secret` header and `installDevBridge()` handles SDK messages. Vite templates do this through the `bardiocDevAuth` plugin; the angular and nextjs templates run `scripts/dev.mjs`, which starts it alongside their dev server.
|
|
76
76
|
|
|
77
77
|
## Options
|
|
78
78
|
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@ npm run dev # http://localhost:{{PORT}}
|
|
|
18
18
|
|
|
19
19
|
`npm run dev` starts the dev server as a local preview shell. Live SDK data comes when the app is
|
|
20
20
|
opened inside the host, or standalone once `DEV_SECRET` and `API_BASE_URL` are set — the SDK then
|
|
21
|
-
proxies `/rest/*` to the BFF (via the `bardiocDevAuth` Vite plugin, or `scripts/dev
|
|
21
|
+
proxies `/rest/*` to the BFF (via the `bardiocDevAuth` Vite plugin, or `scripts/dev.mjs`
|
|
22
22
|
in the angular and nextjs templates, which `npm run dev` starts alongside the dev server). See
|
|
23
23
|
`.env.example` for the required variables.
|
|
24
24
|
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
VITE_APP_NAME={{APP_NAME}}
|
|
3
3
|
|
|
4
4
|
# Paste the dev secret from the BardiocOS AppStore after registering your app.
|
|
5
|
-
# Used by the dev-auth proxy (scripts/dev
|
|
5
|
+
# Used by the dev-auth proxy (scripts/dev.mjs) to sign requests to the BFF.
|
|
6
6
|
DEV_SECRET=
|
|
7
7
|
|
|
8
8
|
# Base URL of the Bardioc BFF / backend-for-frontend (e.g. https://your-host).
|
|
9
9
|
API_BASE_URL=
|
|
10
|
+
|
|
11
|
+
# By default, the app will start on port {{PORT}}. Feel free to uncomment the following variable and change it to match your local setup preferences.
|
|
12
|
+
#LOCALHOST_PORT={{PORT}}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"sync:runtime-env": "node ./scripts/sync-runtime-env.mjs",
|
|
7
7
|
"ng": "ng",
|
|
8
8
|
"predev": "npm run sync:runtime-env",
|
|
9
|
-
"dev": "node ./scripts/dev
|
|
9
|
+
"dev": "node ./scripts/dev.mjs",
|
|
10
10
|
"prebuild": "npm run sync:runtime-env",
|
|
11
11
|
"build": "ng build && node scripts/stamp-manifest.mjs dist/{{APP_NAME}}/browser",
|
|
12
12
|
"bundle": "ng build && node scripts/stamp-manifest.mjs dist/{{APP_NAME}}/browser && cd dist/{{APP_NAME}}/browser && zip -r ../../../{{APP_NAME}}.zip .",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@angular/platform-browser": "^19.2.12",
|
|
29
29
|
"@angular/platform-browser-dynamic": "^19.2.12",
|
|
30
30
|
"@angular/router": "^19.2.12",
|
|
31
|
-
"@bardioc/app-sdk": "^0.
|
|
31
|
+
"@bardioc/app-sdk": "^1.0.0",
|
|
32
32
|
"rxjs": "~7.8.2",
|
|
33
33
|
"tslib": "^2.8.1",
|
|
34
34
|
"zone.js": "~0.15.1"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
// The port a scaffolded app defaults to. LOCALHOST_PORT overrides it, from .env or the shell.
|
|
5
|
+
const DEFAULT_PORT = {{PORT}};
|
|
6
|
+
|
|
7
|
+
// The dev-auth proxy answers on the app port plus this offset — the same origin the app points
|
|
8
|
+
// installDevHost at.
|
|
9
|
+
export const PROXY_PORT_OFFSET = 100;
|
|
10
|
+
|
|
11
|
+
// Neither Next nor Angular exposes import.meta.env to a node script, so the dotenv files are read
|
|
12
|
+
// here: .env, then .env.local, then the shell, which always wins — the precedence of Vite's loadEnv.
|
|
13
|
+
export function readEnvValue(projectDir, key) {
|
|
14
|
+
let value;
|
|
15
|
+
|
|
16
|
+
for (const fileName of ['.env', '.env.local']) {
|
|
17
|
+
let source;
|
|
18
|
+
try {
|
|
19
|
+
source = readFileSync(join(projectDir, fileName), 'utf8');
|
|
20
|
+
} catch {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
25
|
+
const line = rawLine.trim();
|
|
26
|
+
if (line.startsWith(`${key}=`)) {
|
|
27
|
+
value = parseValue(line.slice(key.length + 1));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return process.env[key] ?? value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// A quoted value is taken verbatim; an unquoted one ends at a `#` comment, as dotenv reads it.
|
|
36
|
+
function parseValue(raw) {
|
|
37
|
+
const value = raw.trim();
|
|
38
|
+
const quoted = /^(['"`])(.*?)\1/.exec(value);
|
|
39
|
+
return quoted ? quoted[2] : value.split('#')[0].trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveAppPort(projectDir) {
|
|
43
|
+
const raw = readEnvValue(projectDir, 'LOCALHOST_PORT');
|
|
44
|
+
if (!raw) {
|
|
45
|
+
return DEFAULT_PORT;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const port = Number(raw);
|
|
49
|
+
if (Number.isInteger(port) && port >= 1 && port <= 65535) {
|
|
50
|
+
return port;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.warn(`[dev] LOCALHOST_PORT=${raw} is not a valid port — using ${DEFAULT_PORT}`);
|
|
54
|
+
return DEFAULT_PORT;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The standalone dev-auth contract; null when either value is missing, which disables the proxy.
|
|
58
|
+
export function readDevAuthEnv(projectDir) {
|
|
59
|
+
const devSecret = readEnvValue(projectDir, 'DEV_SECRET')?.trim();
|
|
60
|
+
const apiBaseUrl = readEnvValue(projectDir, 'API_BASE_URL')?.trim().replace(/\/$/, '');
|
|
61
|
+
return devSecret && apiBaseUrl ? { devSecret, apiBaseUrl } : null;
|
|
62
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createDevAuthProxyHandler } from '@bardioc/app-sdk/dev-proxy';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { createServer } from 'node:http';
|
|
6
|
+
import { dirname, resolve } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { PROXY_PORT_OFFSET, readDevAuthEnv, resolveAppPort } from './dev-env.mjs';
|
|
9
|
+
|
|
10
|
+
const PROXY_HOST = '127.0.0.1';
|
|
11
|
+
|
|
12
|
+
const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const port = resolveAppPort(projectDir);
|
|
14
|
+
|
|
15
|
+
// Angular's dev server cannot host the SDK Vite plugin, so the proxy runs beside it.
|
|
16
|
+
const proxy = await startProxy(port + PROXY_PORT_OFFSET);
|
|
17
|
+
|
|
18
|
+
// One command string: shell:true resolves the .cmd shim on Windows, and passing an args array
|
|
19
|
+
// alongside it is deprecated (DEP0190).
|
|
20
|
+
const server = spawn(`ng serve --port ${port}`, { stdio: 'inherit', shell: true });
|
|
21
|
+
|
|
22
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
23
|
+
process.on(signal, () => server.kill(signal));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Exit only once the dev server has, carrying its exit code.
|
|
27
|
+
server.on('exit', (code, signal) => {
|
|
28
|
+
proxy?.close();
|
|
29
|
+
process.exit(signal ? 1 : (code ?? 0));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Built from the SDK handler rather than startDevAuthProxy, whose own SIGINT handler exits the
|
|
33
|
+
// process before the dev server has shut down. Without DEV_SECRET / API_BASE_URL the dev server
|
|
34
|
+
// runs alone, matching how the SDK Vite plugin disables itself.
|
|
35
|
+
async function startProxy(proxyPort) {
|
|
36
|
+
const env = readDevAuthEnv(projectDir);
|
|
37
|
+
if (!env) {
|
|
38
|
+
console.warn(
|
|
39
|
+
'[bardioc-dev-auth] DEV_SECRET and API_BASE_URL are required for dev auth — proxy disabled'
|
|
40
|
+
);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const handler = createDevAuthProxyHandler({ ...env, allowOrigin: '*' });
|
|
45
|
+
const proxyServer = createServer((req, res) => void handler(req, res));
|
|
46
|
+
|
|
47
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
48
|
+
proxyServer.once('error', rejectListen);
|
|
49
|
+
proxyServer.listen(proxyPort, PROXY_HOST, resolveListen);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log(
|
|
53
|
+
`[bardioc-dev-auth] proxying /rest/* → ${env.apiBaseUrl} on http://${PROXY_HOST}:${proxyPort}/rest`
|
|
54
|
+
);
|
|
55
|
+
return proxyServer;
|
|
56
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { mkdirSync,
|
|
3
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { readEnvValue } from './dev-env.mjs';
|
|
6
7
|
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const projectRoot = resolve(__dirname, '..');
|
|
@@ -19,30 +20,3 @@ writeFileSync(
|
|
|
19
20
|
'utf8'
|
|
20
21
|
);
|
|
21
22
|
|
|
22
|
-
// Angular has no import.meta.env, so the app slug is baked into a runtime script instead.
|
|
23
|
-
function readEnvValue(projectDir, key) {
|
|
24
|
-
let value;
|
|
25
|
-
|
|
26
|
-
for (const fileName of ['.env', '.env.local']) {
|
|
27
|
-
let source;
|
|
28
|
-
try {
|
|
29
|
-
source = readFileSync(join(projectDir, fileName), 'utf8');
|
|
30
|
-
} catch {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
for (const rawLine of source.split(/\r?\n/)) {
|
|
35
|
-
const line = rawLine.trim();
|
|
36
|
-
if (!line.startsWith(`${key}=`)) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
value = line
|
|
40
|
-
.slice(key.length + 1)
|
|
41
|
-
.trim()
|
|
42
|
-
.replace(/^['"]|['"]$/g, '');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// A shell value always wins over the dotenv files, like Vite's env loading.
|
|
47
|
-
return process.env[key] ?? value;
|
|
48
|
-
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
|
|
2
1
|
import { JsonPipe } from '@angular/common';
|
|
3
2
|
import { Component, inject, signal } from '@angular/core';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
3
|
+
import type { NotifyLevel } from '@bardioc/app-sdk';
|
|
4
|
+
import { isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
|
|
5
|
+
import { BARDIOC } from './bardioc.token';
|
|
6
6
|
|
|
7
7
|
type ResultState =
|
|
8
8
|
| { status: 'idle' }
|
|
@@ -15,7 +15,7 @@ type ResultState =
|
|
|
15
15
|
standalone: true,
|
|
16
16
|
imports: [JsonPipe],
|
|
17
17
|
template: `
|
|
18
|
-
<div class="bg-background text-foreground min-h-screen">
|
|
18
|
+
<div class="bg-background text-foreground @container/app min-h-screen">
|
|
19
19
|
@if (showSdkDemo) {
|
|
20
20
|
<div class="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-3 p-4">
|
|
21
21
|
<div class="flex flex-col gap-1">
|
|
@@ -27,30 +27,38 @@ type ResultState =
|
|
|
27
27
|
|
|
28
28
|
<div class="flex flex-wrap gap-2">
|
|
29
29
|
<button
|
|
30
|
-
class="bg-primary text-primary-foreground hover:bg-primary/90
|
|
30
|
+
class="bg-primary text-primary-foreground hover:bg-primary/90 rounded px-3 py-1 text-xs font-medium disabled:cursor-not-allowed disabled:opacity-50"
|
|
31
31
|
(click)="runGetProfile()"
|
|
32
|
-
[disabled]="running()"
|
|
32
|
+
[disabled]="running()"
|
|
33
|
+
>
|
|
33
34
|
Get Profile
|
|
34
35
|
</button>
|
|
35
36
|
<button
|
|
36
|
-
class="bg-primary text-primary-foreground hover:bg-primary/90
|
|
37
|
+
class="bg-primary text-primary-foreground hover:bg-primary/90 rounded px-3 py-1 text-xs font-medium disabled:cursor-not-allowed disabled:opacity-50"
|
|
37
38
|
(click)="handleNotify()"
|
|
38
|
-
[disabled]="running()"
|
|
39
|
+
[disabled]="running()"
|
|
40
|
+
>
|
|
39
41
|
Send Notification OS
|
|
40
42
|
</button>
|
|
41
43
|
<button
|
|
42
44
|
class="border-border text-muted-foreground hover:text-foreground rounded border px-3 py-1 text-xs"
|
|
43
45
|
(click)="clearResult()"
|
|
44
|
-
[disabled]="running()"
|
|
46
|
+
[disabled]="running()"
|
|
47
|
+
>
|
|
45
48
|
Clear
|
|
46
49
|
</button>
|
|
47
50
|
</div>
|
|
48
51
|
|
|
49
|
-
<div
|
|
52
|
+
<div
|
|
53
|
+
class="border-border bg-background flex min-h-0 flex-1 flex-col overflow-hidden rounded border"
|
|
54
|
+
>
|
|
50
55
|
@switch (result().status) {
|
|
51
56
|
@case ('idle') {
|
|
52
|
-
<div
|
|
53
|
-
|
|
57
|
+
<div
|
|
58
|
+
class="text-muted-foreground flex h-full items-center justify-center p-3 text-center text-xs leading-5"
|
|
59
|
+
>
|
|
60
|
+
Run a query to see results. If the host session is missing, the transport route
|
|
61
|
+
will return 401.
|
|
54
62
|
</div>
|
|
55
63
|
}
|
|
56
64
|
@case ('loading') {
|
|
@@ -64,15 +72,21 @@ type ResultState =
|
|
|
64
72
|
<div class="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
|
|
65
73
|
{{ resultRequest() }}
|
|
66
74
|
</div>
|
|
67
|
-
<div class="text-destructive
|
|
75
|
+
<div class="text-destructive whitespace-pre-wrap break-all">
|
|
76
|
+
{{ resultMessage() }}
|
|
77
|
+
</div>
|
|
68
78
|
</div>
|
|
69
79
|
}
|
|
70
80
|
@case ('success') {
|
|
71
81
|
<div class="flex h-full flex-col gap-0 overflow-hidden">
|
|
72
|
-
<div
|
|
82
|
+
<div
|
|
83
|
+
class="border-border text-muted-foreground shrink-0 border-b px-3 py-1 font-mono text-xs"
|
|
84
|
+
>
|
|
73
85
|
{{ resultRequest() }}
|
|
74
86
|
</div>
|
|
75
|
-
<pre class="text-foreground flex-1 overflow-auto p-3 text-[11px] leading-5">{{
|
|
87
|
+
<pre class="text-foreground flex-1 overflow-auto p-3 text-[11px] leading-5">{{
|
|
88
|
+
resultData() | json
|
|
89
|
+
}}</pre>
|
|
76
90
|
</div>
|
|
77
91
|
}
|
|
78
92
|
}
|
|
@@ -82,7 +96,7 @@ type ResultState =
|
|
|
82
96
|
<div class="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-4 p-4">
|
|
83
97
|
<h1 class="m-0 text-2xl font-semibold">Hello Bardioc App</h1>
|
|
84
98
|
<p class="text-muted-foreground m-0 max-w-2xl text-xs leading-5">
|
|
85
|
-
Open this app inside Bardioc
|
|
99
|
+
Open this app inside Bardioc to use transport requests and send host notifications.
|
|
86
100
|
</p>
|
|
87
101
|
<div class="border-border bg-muted/20 rounded-xl border p-3 text-xs leading-5">
|
|
88
102
|
Outside the host iframe this page is only a local preview shell.
|
|
@@ -93,30 +107,30 @@ type ResultState =
|
|
|
93
107
|
`,
|
|
94
108
|
})
|
|
95
109
|
export class AppComponent {
|
|
96
|
-
private
|
|
110
|
+
private bardioc = inject(BARDIOC, { optional: true });
|
|
97
111
|
|
|
98
112
|
showSdkDemo = isInsideIframe() || isDevStandalone();
|
|
99
113
|
result = signal<ResultState>({ status: 'idle' });
|
|
100
114
|
running = signal(false);
|
|
101
115
|
|
|
102
116
|
async runGetProfile(): Promise<void> {
|
|
103
|
-
if (this.running() || !this.
|
|
117
|
+
if (this.running() || !this.bardioc) return;
|
|
104
118
|
this.running.set(true);
|
|
105
119
|
|
|
106
|
-
const request = '
|
|
120
|
+
const request = 'os.profile.get()';
|
|
107
121
|
this.result.set({ status: 'loading', request });
|
|
108
122
|
|
|
109
123
|
const startTime = Date.now();
|
|
110
|
-
this.
|
|
124
|
+
this.notify('Getting user profile…', 'info');
|
|
111
125
|
|
|
112
126
|
try {
|
|
113
|
-
const data = await this.
|
|
127
|
+
const data = await this.bardioc.os.profile.get();
|
|
114
128
|
const duration = Date.now() - startTime;
|
|
115
|
-
this.
|
|
129
|
+
this.notify(`Profile loaded (${duration}ms).`, 'success');
|
|
116
130
|
this.result.set({ status: 'success', request, data });
|
|
117
131
|
} catch (err) {
|
|
118
132
|
const message = err instanceof Error ? err.message : String(err);
|
|
119
|
-
this.
|
|
133
|
+
this.notify(`Failed: ${message}`, 'error');
|
|
120
134
|
this.result.set({ status: 'error', request, message });
|
|
121
135
|
}
|
|
122
136
|
|
|
@@ -124,15 +138,19 @@ export class AppComponent {
|
|
|
124
138
|
}
|
|
125
139
|
|
|
126
140
|
handleNotify(): void {
|
|
127
|
-
if (!this.
|
|
128
|
-
this.
|
|
141
|
+
if (!this.bardioc) return;
|
|
142
|
+
this.notify('Hello from {{DISPLAY_NAME}}.', 'info');
|
|
129
143
|
this.result.set({
|
|
130
144
|
status: 'success',
|
|
131
|
-
request: '
|
|
145
|
+
request: 'ui.notify',
|
|
132
146
|
data: { ok: true, message: 'Notification sent to OS' },
|
|
133
147
|
});
|
|
134
148
|
}
|
|
135
149
|
|
|
150
|
+
private notify(message: string, level: NotifyLevel): void {
|
|
151
|
+
this.bardioc?.ui.notify(message, level).catch(console.error);
|
|
152
|
+
}
|
|
153
|
+
|
|
136
154
|
clearResult(): void {
|
|
137
155
|
this.result.set({ status: 'idle' });
|
|
138
156
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { InjectionToken } from '@angular/core';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Bardioc } from '@bardioc/app-sdk';
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const BARDIOC = new InjectionToken<Bardioc>('BARDIOC');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { connect } from '@bardioc/app-sdk';
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
+
installDevHost,
|
|
3
4
|
isDevStandalone,
|
|
4
5
|
isInsideIframe,
|
|
5
6
|
} from '@bardioc/app-sdk/dev';
|
|
@@ -7,8 +8,7 @@ import { bootstrapApplication } from '@angular/platform-browser';
|
|
|
7
8
|
import { provideHttpClient } from '@angular/common/http';
|
|
8
9
|
import { type EnvironmentProviders, type Provider } from '@angular/core';
|
|
9
10
|
import { AppComponent } from './app/app.component';
|
|
10
|
-
import {
|
|
11
|
-
import { BARDIOC_BRIDGE } from './app/bardioc.token';
|
|
11
|
+
import { BARDIOC } from './app/bardioc.token';
|
|
12
12
|
import './styles.css';
|
|
13
13
|
|
|
14
14
|
function readAppPort(): number {
|
|
@@ -24,7 +24,8 @@ const useSdkProvider = insideIframe || devStandalone;
|
|
|
24
24
|
|
|
25
25
|
async function bootstrap() {
|
|
26
26
|
if (devStandalone) {
|
|
27
|
-
|
|
27
|
+
installDevHost({
|
|
28
|
+
appId: '{{APP_NAME}}',
|
|
28
29
|
apiBaseUrl: `http://127.0.0.1:${proxyPort}/rest`,
|
|
29
30
|
});
|
|
30
31
|
}
|
|
@@ -34,10 +35,7 @@ async function bootstrap() {
|
|
|
34
35
|
];
|
|
35
36
|
|
|
36
37
|
if (useSdkProvider) {
|
|
37
|
-
providers.push({
|
|
38
|
-
provide: BARDIOC_BRIDGE,
|
|
39
|
-
useValue: createBardiocBridge(),
|
|
40
|
-
});
|
|
38
|
+
providers.push({ provide: BARDIOC, useValue: await connect() });
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
bootstrapApplication(AppComponent, {
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
NEXT_PUBLIC_APP_NAME={{APP_NAME}}
|
|
3
3
|
|
|
4
4
|
# Paste the dev secret from the BardiocOS AppStore after registering your app.
|
|
5
|
-
# Used by the dev-auth proxy (scripts/dev
|
|
5
|
+
# Used by the dev-auth proxy (scripts/dev.mjs) to sign requests to the BFF.
|
|
6
6
|
DEV_SECRET=
|
|
7
7
|
|
|
8
8
|
# Base URL of the Bardioc BFF / backend-for-frontend (e.g. https://your-host).
|
|
9
9
|
API_BASE_URL=
|
|
10
|
+
|
|
11
|
+
# By default, the app will start on port {{PORT}}. Feel free to uncomment the following variable and change it to match your local setup preferences.
|
|
12
|
+
#LOCALHOST_PORT={{PORT}}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { installDevHost, isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
|
|
4
4
|
import { AppSdkProvider, useNotify, useSdk } from '@bardioc/app-sdk/react';
|
|
5
5
|
import { useEffect, useState } from 'react';
|
|
6
6
|
|
|
@@ -12,11 +12,11 @@ type ResultState =
|
|
|
12
12
|
|
|
13
13
|
const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME?.trim();
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
const
|
|
15
|
+
// The port the app falls back to when it is not being served over HTTP (LOCALHOST_PORT moves it).
|
|
16
|
+
const DEFAULT_PORT = Number('{{PORT}}');
|
|
17
17
|
|
|
18
18
|
function SdkDemo() {
|
|
19
|
-
const
|
|
19
|
+
const bardioc = useSdk();
|
|
20
20
|
const notify = useNotify();
|
|
21
21
|
const [result, setResult] = useState<ResultState>({ status: 'idle' });
|
|
22
22
|
const [running, setRunning] = useState(false);
|
|
@@ -25,17 +25,17 @@ function SdkDemo() {
|
|
|
25
25
|
if (running) return;
|
|
26
26
|
setRunning(true);
|
|
27
27
|
|
|
28
|
-
const request = '
|
|
28
|
+
const request = 'os.profile.get()';
|
|
29
29
|
setResult({ status: 'loading', request });
|
|
30
30
|
|
|
31
31
|
const startTime = Date.now();
|
|
32
|
-
notify('Getting user profile', 'info');
|
|
32
|
+
notify('Getting user profile…', 'info');
|
|
33
33
|
|
|
34
34
|
try {
|
|
35
|
-
const data = await
|
|
35
|
+
const data = await bardioc.os.profile.get();
|
|
36
36
|
|
|
37
37
|
const duration = Date.now() - startTime;
|
|
38
|
-
notify(`Profile loaded (${duration}ms)
|
|
38
|
+
notify(`Profile loaded (${duration}ms).`, 'success');
|
|
39
39
|
setResult({ status: 'success', request, data });
|
|
40
40
|
} catch (err) {
|
|
41
41
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -47,10 +47,10 @@ function SdkDemo() {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function handleNotify() {
|
|
50
|
-
notify('Hello from {{DISPLAY_NAME}}', 'info');
|
|
50
|
+
notify('Hello from {{DISPLAY_NAME}}.', 'info');
|
|
51
51
|
setResult({
|
|
52
52
|
status: 'success',
|
|
53
|
-
request: '
|
|
53
|
+
request: 'ui.notify',
|
|
54
54
|
data: { ok: true, message: 'Notification sent to OS' },
|
|
55
55
|
});
|
|
56
56
|
}
|
|
@@ -106,7 +106,7 @@ function SdkDemo() {
|
|
|
106
106
|
<div className="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
|
|
107
107
|
{result.request}
|
|
108
108
|
</div>
|
|
109
|
-
<div className="text-destructive
|
|
109
|
+
<div className="text-destructive whitespace-pre-wrap break-all">{result.message}</div>
|
|
110
110
|
</div>
|
|
111
111
|
)}
|
|
112
112
|
{result.status === 'success' && (
|
|
@@ -129,7 +129,7 @@ function PreviewShell() {
|
|
|
129
129
|
<div className="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-4 p-4">
|
|
130
130
|
<h1 className="m-0 text-2xl font-semibold">Hello Bardioc App</h1>
|
|
131
131
|
<p className="text-muted-foreground m-0 max-w-2xl text-xs leading-5">
|
|
132
|
-
Open this app inside Bardioc
|
|
132
|
+
Open this app inside Bardioc to use transport requests and send host notifications.
|
|
133
133
|
</p>
|
|
134
134
|
<div className="border-border bg-muted/20 rounded-xl border p-3 text-xs leading-5">
|
|
135
135
|
Outside the host iframe this page is only a local preview shell.
|
|
@@ -147,14 +147,17 @@ export default function Page() {
|
|
|
147
147
|
|
|
148
148
|
useEffect(() => {
|
|
149
149
|
if (isDevStandalone()) {
|
|
150
|
-
|
|
150
|
+
// scripts/dev.mjs runs the dev-auth proxy on the app port + 100, so read the port the page
|
|
151
|
+
// is actually served on rather than the one baked in at scaffold time.
|
|
152
|
+
const appPort = Number(window.location.port) || DEFAULT_PORT;
|
|
153
|
+
installDevHost({ appId: APP_NAME, apiBaseUrl: `http://127.0.0.1:${appPort + 100}/rest` });
|
|
151
154
|
}
|
|
152
155
|
}, []);
|
|
153
156
|
|
|
154
157
|
return (
|
|
155
|
-
<div className="bg-background text-foreground min-h-screen">
|
|
158
|
+
<div className="bg-background text-foreground @container/app min-h-screen">
|
|
156
159
|
{useSdkProvider ? (
|
|
157
|
-
<AppSdkProvider
|
|
160
|
+
<AppSdkProvider>
|
|
158
161
|
<SdkDemo />
|
|
159
162
|
</AppSdkProvider>
|
|
160
163
|
) : (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { NextConfig } from 'next';
|
|
2
2
|
|
|
3
|
-
// Standalone dev auth lives in scripts/dev
|
|
3
|
+
// Standalone dev auth lives in scripts/dev.mjs (the SDK dev-auth proxy), not here:
|
|
4
4
|
// the BFF needs Origin dropped and X-Requested-With stamped, which rewrites cannot do.
|
|
5
5
|
const nextConfig: NextConfig = {
|
|
6
6
|
output: 'export',
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "0.0.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
-
"dev": "node ./scripts/dev
|
|
6
|
+
"dev": "node ./scripts/dev.mjs",
|
|
7
7
|
"build": "next build && node scripts/stamp-manifest.mjs out",
|
|
8
8
|
"bundle": "next build && node scripts/stamp-manifest.mjs out && cd out && zip -r ../{{APP_NAME}}.zip .",
|
|
9
9
|
"start": "next start",
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
"release:publish": "changeset publish"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@bardioc/app-sdk": "^0.
|
|
20
|
+
"@bardioc/app-sdk": "^1.0.0",
|
|
21
21
|
"next": "^15.3.2",
|
|
22
22
|
"react": "^19.2.5",
|
|
23
23
|
"react-dom": "^19.2.5"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@bardioc/configs": "
|
|
26
|
+
"@bardioc/configs": "0.2.1",
|
|
27
27
|
"@changesets/cli": "^2.31.0",
|
|
28
28
|
"@tailwindcss/postcss": "^4.2.4",
|
|
29
29
|
"@types/node": "^22.10.0",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
// The port a scaffolded app defaults to. LOCALHOST_PORT overrides it, from .env or the shell.
|
|
5
|
+
const DEFAULT_PORT = {{PORT}};
|
|
6
|
+
|
|
7
|
+
// The dev-auth proxy answers on the app port plus this offset — the same origin the app points
|
|
8
|
+
// installDevHost at.
|
|
9
|
+
export const PROXY_PORT_OFFSET = 100;
|
|
10
|
+
|
|
11
|
+
// Neither Next nor Angular exposes import.meta.env to a node script, so the dotenv files are read
|
|
12
|
+
// here: .env, then .env.local, then the shell, which always wins — the precedence of Vite's loadEnv.
|
|
13
|
+
export function readEnvValue(projectDir, key) {
|
|
14
|
+
let value;
|
|
15
|
+
|
|
16
|
+
for (const fileName of ['.env', '.env.local']) {
|
|
17
|
+
let source;
|
|
18
|
+
try {
|
|
19
|
+
source = readFileSync(join(projectDir, fileName), 'utf8');
|
|
20
|
+
} catch {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
25
|
+
const line = rawLine.trim();
|
|
26
|
+
if (line.startsWith(`${key}=`)) {
|
|
27
|
+
value = parseValue(line.slice(key.length + 1));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return process.env[key] ?? value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// A quoted value is taken verbatim; an unquoted one ends at a `#` comment, as dotenv reads it.
|
|
36
|
+
function parseValue(raw) {
|
|
37
|
+
const value = raw.trim();
|
|
38
|
+
const quoted = /^(['"`])(.*?)\1/.exec(value);
|
|
39
|
+
return quoted ? quoted[2] : value.split('#')[0].trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveAppPort(projectDir) {
|
|
43
|
+
const raw = readEnvValue(projectDir, 'LOCALHOST_PORT');
|
|
44
|
+
if (!raw) {
|
|
45
|
+
return DEFAULT_PORT;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const port = Number(raw);
|
|
49
|
+
if (Number.isInteger(port) && port >= 1 && port <= 65535) {
|
|
50
|
+
return port;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.warn(`[dev] LOCALHOST_PORT=${raw} is not a valid port — using ${DEFAULT_PORT}`);
|
|
54
|
+
return DEFAULT_PORT;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The standalone dev-auth contract; null when either value is missing, which disables the proxy.
|
|
58
|
+
export function readDevAuthEnv(projectDir) {
|
|
59
|
+
const devSecret = readEnvValue(projectDir, 'DEV_SECRET')?.trim();
|
|
60
|
+
const apiBaseUrl = readEnvValue(projectDir, 'API_BASE_URL')?.trim().replace(/\/$/, '');
|
|
61
|
+
return devSecret && apiBaseUrl ? { devSecret, apiBaseUrl } : null;
|
|
62
|
+
}
|