@pikku/bun-server 0.12.4 → 0.12.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/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/pikku-bun-server.d.ts +5 -1
- package/dist/pikku-bun-server.js +16 -1
- package/dist/pikku-bun-server.js.map +1 -1
- package/package.json +2 -2
- package/src/local-content.test.ts +193 -0
- package/src/pikku-bun-server.ts +44 -2
- package/src/static-mount.test.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @pikku/bun-server
|
|
2
2
|
|
|
3
|
+
## 0.12.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ea8aabf: Serve `LocalContent` uploads and signed reads under Bun.
|
|
8
|
+
|
|
9
|
+
`LocalContent` hands the browser a `PUT <uploadUrlPrefix>/<key>` upload URL and a signed
|
|
10
|
+
`GET <assetUrlPrefix>/<key>` read URL, but it is a `ContentService` and cannot answer
|
|
11
|
+
either — something in the serving path has to. Only `@pikku/node-http-server` did. The
|
|
12
|
+
same project served under Bun handed out upload URLs that 404ed, with nothing naming the
|
|
13
|
+
cause: the config was accepted, the service was constructed, and the URLs looked right.
|
|
14
|
+
|
|
15
|
+
`@pikku/core` now exports `createLocalContentRequestHandler` from
|
|
16
|
+
`@pikku/core/services/local-content-request-handler` — the server half of `LocalContent`,
|
|
17
|
+
expressed in Web `Request`/`Response` so every runtime shares one implementation of the
|
|
18
|
+
signature check rather than each re-deriving it. It returns `null` for anything that is
|
|
19
|
+
not a content request, which is the caller's signal to carry on with its normal routing.
|
|
20
|
+
|
|
21
|
+
`PikkuBunServer` accepts `config.content` and a `contentSigningJWT` option, mirroring
|
|
22
|
+
`PikkuNodeHTTPServer`, and answers both prefixes ahead of static mounts and routing.
|
|
23
|
+
`BunServerRunner` was dropping `contentSigningJWT` on the floor, which silently disabled
|
|
24
|
+
signed asset reads for every Bun project even once the prefixes were served — the config
|
|
25
|
+
arrived, the service that verifies its signatures did not.
|
|
26
|
+
|
|
27
|
+
Signed reads are refused unless every claim matches, the path included: without that, a
|
|
28
|
+
signature minted for one asset would read any other.
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [32277d5]
|
|
31
|
+
- Updated dependencies [ea8aabf]
|
|
32
|
+
- Updated dependencies [33e96ab]
|
|
33
|
+
- Updated dependencies [fd72e58]
|
|
34
|
+
- Updated dependencies [fd72e58]
|
|
35
|
+
- Updated dependencies [fd72e58]
|
|
36
|
+
- Updated dependencies [894b2f8]
|
|
37
|
+
- Updated dependencies [dd19aa7]
|
|
38
|
+
- Updated dependencies [50ec500]
|
|
39
|
+
- @pikku/core@0.12.75
|
|
40
|
+
|
|
3
41
|
## 0.12.4
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 - present Yasser Fadl and Pikku contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @pikku/bun-server
|
|
2
|
+
|
|
3
|
+
Pikku server for Bun, built on `Bun.serve`. Includes a Bun-native event hub and
|
|
4
|
+
optional MCP support.
|
|
5
|
+
|
|
6
|
+
For Node use `@pikku/node-http-server`.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bun add @pikku/bun-server
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { PikkuBunServer } from '@pikku/bun-server'
|
|
18
|
+
|
|
19
|
+
import './.pikku/pikku-bootstrap.gen.js'
|
|
20
|
+
|
|
21
|
+
const server = new PikkuBunServer(config, logger)
|
|
22
|
+
|
|
23
|
+
await server.init()
|
|
24
|
+
await server.start()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Pass `{ eventHub, mcpJson, mcpPath }` as the third argument to override the
|
|
28
|
+
default `BunEventHubService` or enable MCP.
|
|
29
|
+
|
|
30
|
+
## Docs
|
|
31
|
+
|
|
32
|
+
https://pikku.dev/docs
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CoreConfig } from '@pikku/core';
|
|
2
|
-
import type { Logger } from '@pikku/core/services';
|
|
2
|
+
import type { JWTService, Logger } from '@pikku/core/services';
|
|
3
|
+
import type { LocalContentConfig } from '@pikku/core/services/local-content';
|
|
3
4
|
import { type RunHTTPWiringOptions } from '@pikku/core/http';
|
|
4
5
|
import { BunEventHubService } from './bun-event-hub-service.js';
|
|
5
6
|
export type StaticMount = {
|
|
@@ -12,6 +13,7 @@ export type BunServerConfig = CoreConfig & {
|
|
|
12
13
|
hostname?: string;
|
|
13
14
|
staticMounts?: StaticMount[];
|
|
14
15
|
healthCheckPath?: string;
|
|
16
|
+
content?: LocalContentConfig;
|
|
15
17
|
};
|
|
16
18
|
export type PikkuBunServerOptions = RunHTTPWiringOptions & {
|
|
17
19
|
eventHub?: BunEventHubService;
|
|
@@ -21,6 +23,7 @@ export type PikkuBunServerOptions = RunHTTPWiringOptions & {
|
|
|
21
23
|
prompts?: unknown[];
|
|
22
24
|
};
|
|
23
25
|
mcpPath?: string;
|
|
26
|
+
contentSigningJWT?: JWTService;
|
|
24
27
|
};
|
|
25
28
|
export declare class PikkuBunServer {
|
|
26
29
|
private readonly config;
|
|
@@ -31,6 +34,7 @@ export declare class PikkuBunServer {
|
|
|
31
34
|
private readonly mcpJson?;
|
|
32
35
|
private readonly mcpPath;
|
|
33
36
|
private mcpHandler?;
|
|
37
|
+
private readonly localContent?;
|
|
34
38
|
constructor(config: BunServerConfig, logger: Logger, options?: PikkuBunServerOptions);
|
|
35
39
|
init(): Promise<void>;
|
|
36
40
|
private initMCP;
|
package/dist/pikku-bun-server.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { stopSingletonServices } from '@pikku/core';
|
|
2
|
+
import { pikkuState } from '@pikku/core/internal';
|
|
3
|
+
import { createLocalContentRequestHandler, } from '@pikku/core/services/local-content-request-handler';
|
|
2
4
|
import { fetchData, PikkuFetchHTTPRequest, PikkuFetchHTTPResponse, logRoutes as logRegisterRoutes, } from '@pikku/core/http';
|
|
3
5
|
import { logChannels } from '@pikku/core/channel';
|
|
4
6
|
import { runLocalChannel } from '@pikku/core/channel/local';
|
|
@@ -24,14 +26,23 @@ export class PikkuBunServer {
|
|
|
24
26
|
mcpJson;
|
|
25
27
|
mcpPath;
|
|
26
28
|
mcpHandler;
|
|
29
|
+
localContent;
|
|
27
30
|
constructor(config, logger, options = {}) {
|
|
28
31
|
this.config = config;
|
|
29
32
|
this.logger = logger;
|
|
30
|
-
const { eventHub, mcpJson, mcpPath, ...httpOptions } = options;
|
|
33
|
+
const { eventHub, mcpJson, mcpPath, contentSigningJWT, ...httpOptions } = options;
|
|
31
34
|
this.eventHub = eventHub ?? new BunEventHubService();
|
|
32
35
|
this.mcpJson = mcpJson;
|
|
33
36
|
this.mcpPath = mcpPath ?? '/mcp';
|
|
34
37
|
this.options = httpOptions;
|
|
38
|
+
this.localContent = config.content
|
|
39
|
+
? createLocalContentRequestHandler({
|
|
40
|
+
content: config.content,
|
|
41
|
+
logger,
|
|
42
|
+
getJWT: () => contentSigningJWT ??
|
|
43
|
+
pikkuState(null, 'package', 'singletonServices')?.jwt,
|
|
44
|
+
})
|
|
45
|
+
: undefined;
|
|
35
46
|
}
|
|
36
47
|
async init() {
|
|
37
48
|
compileAllSchemas(this.logger);
|
|
@@ -102,6 +113,10 @@ export class PikkuBunServer {
|
|
|
102
113
|
return await mcpHandler(req);
|
|
103
114
|
}
|
|
104
115
|
}
|
|
116
|
+
const contentResponse = await this.localContent?.(req);
|
|
117
|
+
if (contentResponse) {
|
|
118
|
+
return contentResponse;
|
|
119
|
+
}
|
|
105
120
|
const staticResponse = await this.serveStaticMounts(req);
|
|
106
121
|
if (staticResponse) {
|
|
107
122
|
return staticResponse;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pikku-bun-server.js","sourceRoot":"","sources":["../src/pikku-bun-server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,EACL,SAAS,EACT,qBAAqB,EACrB,sBAAsB,EACtB,SAAS,IAAI,iBAAiB,GAE/B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEjD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAEtD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"pikku-bun-server.js","sourceRoot":"","sources":["../src/pikku-bun-server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/bun-server",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.5",
|
|
4
4
|
"description": "Pikku server adapter for Bun (Bun.serve)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "yasser.fadl@gmail.com",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"prepublishOnly": "bun run build"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@pikku/core": "^0.12.
|
|
19
|
+
"@pikku/core": "^0.12.75",
|
|
20
20
|
"@pikku/modelcontextprotocol": "^0.12.6"
|
|
21
21
|
},
|
|
22
22
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, test, before, after } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import {
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
} from 'node:fs'
|
|
10
|
+
import { tmpdir } from 'node:os'
|
|
11
|
+
import { join } from 'node:path'
|
|
12
|
+
import { setSingletonServices } from '@pikku/core'
|
|
13
|
+
import { resetPikkuState } from '@pikku/core/internal'
|
|
14
|
+
import { LocalContent } from '@pikku/core/services/local-content'
|
|
15
|
+
import type { JWTService, Logger } from '@pikku/core/services'
|
|
16
|
+
import { PikkuBunServer } from './pikku-bun-server.js'
|
|
17
|
+
|
|
18
|
+
const noopLogger = {
|
|
19
|
+
info: () => {},
|
|
20
|
+
error: () => {},
|
|
21
|
+
warn: () => {},
|
|
22
|
+
debug: () => {},
|
|
23
|
+
trace: () => {},
|
|
24
|
+
setLevel: () => {},
|
|
25
|
+
} as unknown as Logger
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Signs and verifies with a plain base64 round-trip. The point of these tests is
|
|
29
|
+
* that the server checks the SAME claims the signer wrote, not that any
|
|
30
|
+
* particular algorithm is used — a real JWT here would only slow them down.
|
|
31
|
+
*/
|
|
32
|
+
const fakeJWT = {
|
|
33
|
+
encode: async (_expiry: string, payload: unknown) =>
|
|
34
|
+
Buffer.from(JSON.stringify(payload)).toString('base64url'),
|
|
35
|
+
decode: async (token: string) =>
|
|
36
|
+
JSON.parse(Buffer.from(token, 'base64url').toString()),
|
|
37
|
+
} as unknown as JWTService
|
|
38
|
+
|
|
39
|
+
describe('PikkuBunServer local content', () => {
|
|
40
|
+
let server: PikkuBunServer
|
|
41
|
+
let content: LocalContent
|
|
42
|
+
let tmpDir: string
|
|
43
|
+
let origin: string
|
|
44
|
+
|
|
45
|
+
before(async () => {
|
|
46
|
+
resetPikkuState()
|
|
47
|
+
setSingletonServices({
|
|
48
|
+
logger: noopLogger,
|
|
49
|
+
schema: {
|
|
50
|
+
compileSchema: () => {},
|
|
51
|
+
getSchemaNames: () => new Set<string>(),
|
|
52
|
+
},
|
|
53
|
+
} as any)
|
|
54
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'pikku-bun-content-'))
|
|
55
|
+
mkdirSync(join(tmpDir, 'bucket'), { recursive: true })
|
|
56
|
+
writeFileSync(join(tmpDir, 'bucket', 'existing.bin'), 'already here')
|
|
57
|
+
|
|
58
|
+
const contentConfig = {
|
|
59
|
+
localFileUploadPath: tmpDir,
|
|
60
|
+
uploadUrlPrefix: '/upload',
|
|
61
|
+
assetUrlPrefix: '/assets',
|
|
62
|
+
sizeLimit: '1mb',
|
|
63
|
+
}
|
|
64
|
+
// The service under test on the client side: the URLs it hands out are
|
|
65
|
+
// exactly what the server must answer, so they are not hand-written here.
|
|
66
|
+
content = new LocalContent(contentConfig, noopLogger, fakeJWT)
|
|
67
|
+
|
|
68
|
+
server = new PikkuBunServer(
|
|
69
|
+
{
|
|
70
|
+
port: 0,
|
|
71
|
+
hostname: '127.0.0.1',
|
|
72
|
+
content: contentConfig,
|
|
73
|
+
} as any,
|
|
74
|
+
noopLogger,
|
|
75
|
+
{ contentSigningJWT: fakeJWT }
|
|
76
|
+
)
|
|
77
|
+
await server.init()
|
|
78
|
+
await server.start()
|
|
79
|
+
origin = `http://127.0.0.1:${server.port}`
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
after(async () => {
|
|
83
|
+
await server.stop()
|
|
84
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('answers the upload URL the content service hands out', async () => {
|
|
88
|
+
const { uploadUrl } = await content.getUploadURL({
|
|
89
|
+
bucket: 'bucket',
|
|
90
|
+
fileKey: 'nested/file.bin',
|
|
91
|
+
contentType: 'application/octet-stream',
|
|
92
|
+
} as any)
|
|
93
|
+
|
|
94
|
+
const response = await fetch(`${origin}${uploadUrl}`, {
|
|
95
|
+
method: 'PUT',
|
|
96
|
+
body: 'uploaded bytes',
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
assert.equal(response.status, 200)
|
|
100
|
+
assert.equal(
|
|
101
|
+
readFileSync(join(tmpDir, 'bucket', 'nested', 'file.bin'), 'utf8'),
|
|
102
|
+
'uploaded bytes'
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('serves an asset through the signed URL the content service produces', async () => {
|
|
107
|
+
const signed = await content.signContentKey({
|
|
108
|
+
bucket: 'bucket',
|
|
109
|
+
contentKey: 'existing.bin',
|
|
110
|
+
dateLessThan: new Date(Date.now() + 60_000),
|
|
111
|
+
} as any)
|
|
112
|
+
|
|
113
|
+
const response = await fetch(`${origin}${signed}`)
|
|
114
|
+
assert.equal(response.status, 200)
|
|
115
|
+
assert.equal(await response.text(), 'already here')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('refuses an unsigned asset read', async () => {
|
|
119
|
+
const response = await fetch(`${origin}/assets/bucket/existing.bin`)
|
|
120
|
+
assert.equal(response.status, 403)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('refuses a signature minted for a different asset', async () => {
|
|
124
|
+
// The signature is valid and unexpired — only the path differs. Without the
|
|
125
|
+
// path claim being checked, one signed URL would read the whole bucket.
|
|
126
|
+
const signed = await content.signContentKey({
|
|
127
|
+
bucket: 'bucket',
|
|
128
|
+
contentKey: 'existing.bin',
|
|
129
|
+
dateLessThan: new Date(Date.now() + 60_000),
|
|
130
|
+
} as any)
|
|
131
|
+
writeFileSync(join(tmpDir, 'bucket', 'other.bin'), 'other')
|
|
132
|
+
|
|
133
|
+
const query = signed.slice(signed.indexOf('?'))
|
|
134
|
+
const response = await fetch(`${origin}/assets/bucket/other.bin${query}`)
|
|
135
|
+
assert.equal(response.status, 403)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('refuses an expired signature', async () => {
|
|
139
|
+
const signed = await content.signContentKey({
|
|
140
|
+
bucket: 'bucket',
|
|
141
|
+
contentKey: 'existing.bin',
|
|
142
|
+
dateLessThan: new Date(Date.now() - 1_000),
|
|
143
|
+
} as any)
|
|
144
|
+
|
|
145
|
+
const response = await fetch(`${origin}${signed}`)
|
|
146
|
+
assert.equal(response.status, 403)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// `fetch` normalises `..` out of the path before the request leaves the
|
|
150
|
+
// client, so this cannot drive the guard itself — that is covered directly in
|
|
151
|
+
// core's local-content-request-handler.test.ts. What it does prove is that
|
|
152
|
+
// nothing in the server path un-normalises it back into a write.
|
|
153
|
+
test('does not write outside the content directory', async () => {
|
|
154
|
+
writeFileSync(join(tmpDir, '..', 'pikku-bun-content-secret.txt'), 'secret')
|
|
155
|
+
try {
|
|
156
|
+
const response = await fetch(
|
|
157
|
+
`${origin}/upload/%2e%2e/pikku-bun-content-secret.txt`,
|
|
158
|
+
{ method: 'PUT', body: 'overwritten' }
|
|
159
|
+
)
|
|
160
|
+
assert.notEqual(response.status, 200)
|
|
161
|
+
assert.equal(
|
|
162
|
+
readFileSync(
|
|
163
|
+
join(tmpDir, '..', 'pikku-bun-content-secret.txt'),
|
|
164
|
+
'utf8'
|
|
165
|
+
),
|
|
166
|
+
'secret'
|
|
167
|
+
)
|
|
168
|
+
} finally {
|
|
169
|
+
rmSync(join(tmpDir, '..', 'pikku-bun-content-secret.txt'), {
|
|
170
|
+
force: true,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('rejects an upload over the size limit', async () => {
|
|
176
|
+
const { uploadUrl } = await content.getUploadURL({
|
|
177
|
+
bucket: 'bucket',
|
|
178
|
+
fileKey: 'too-big.bin',
|
|
179
|
+
contentType: 'application/octet-stream',
|
|
180
|
+
} as any)
|
|
181
|
+
|
|
182
|
+
const response = await fetch(`${origin}${uploadUrl}`, {
|
|
183
|
+
method: 'PUT',
|
|
184
|
+
body: Buffer.alloc(1024 * 1024 + 1),
|
|
185
|
+
})
|
|
186
|
+
assert.equal(response.status, 413)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('leaves non-content paths to the router', async () => {
|
|
190
|
+
const response = await fetch(`${origin}/uploadsomething`)
|
|
191
|
+
assert.equal(response.status, 404)
|
|
192
|
+
})
|
|
193
|
+
})
|
package/src/pikku-bun-server.ts
CHANGED
|
@@ -2,7 +2,13 @@ import type { Server as BunServer, ServerWebSocket } from 'bun'
|
|
|
2
2
|
|
|
3
3
|
import type { CoreConfig } from '@pikku/core'
|
|
4
4
|
import { stopSingletonServices } from '@pikku/core'
|
|
5
|
-
import type { Logger } from '@pikku/core/services'
|
|
5
|
+
import type { JWTService, Logger } from '@pikku/core/services'
|
|
6
|
+
import { pikkuState } from '@pikku/core/internal'
|
|
7
|
+
import type { LocalContentConfig } from '@pikku/core/services/local-content'
|
|
8
|
+
import {
|
|
9
|
+
createLocalContentRequestHandler,
|
|
10
|
+
type LocalContentRequestHandler,
|
|
11
|
+
} from '@pikku/core/services/local-content-request-handler'
|
|
6
12
|
import {
|
|
7
13
|
fetchData,
|
|
8
14
|
PikkuFetchHTTPRequest,
|
|
@@ -30,6 +36,13 @@ export type BunServerConfig = CoreConfig & {
|
|
|
30
36
|
hostname?: string
|
|
31
37
|
staticMounts?: StaticMount[]
|
|
32
38
|
healthCheckPath?: string
|
|
39
|
+
/**
|
|
40
|
+
* Serve the `LocalContent` upload and asset prefixes. Pass the same config the
|
|
41
|
+
* `LocalContent` service was built from — it is that service which hands the
|
|
42
|
+
* browser these URLs, and a prefix that disagrees produces a 404 naming
|
|
43
|
+
* nothing. Mirrors `PikkuNodeHTTPServerConfig.content`.
|
|
44
|
+
*/
|
|
45
|
+
content?: LocalContentConfig
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
export type PikkuBunServerOptions = RunHTTPWiringOptions & {
|
|
@@ -53,6 +66,14 @@ export type PikkuBunServerOptions = RunHTTPWiringOptions & {
|
|
|
53
66
|
* Path the MCP server is mounted at when `mcpJson` is provided. Default `/mcp`.
|
|
54
67
|
*/
|
|
55
68
|
mcpPath?: string
|
|
69
|
+
/**
|
|
70
|
+
* The JWT service `LocalContent` signs asset URLs with. Required to serve
|
|
71
|
+
* `config.content`'s asset prefix: without it every signed read is refused,
|
|
72
|
+
* since an unverifiable signature is no signature. Falls back to
|
|
73
|
+
* `singletonServices.jwt`. Mirrors
|
|
74
|
+
* `PikkuNodeHTTPServerOptions.contentSigningJWT`.
|
|
75
|
+
*/
|
|
76
|
+
contentSigningJWT?: JWTService
|
|
56
77
|
}
|
|
57
78
|
|
|
58
79
|
type WsData = { channelHandler: PikkuLocalChannelHandler }
|
|
@@ -84,17 +105,30 @@ export class PikkuBunServer {
|
|
|
84
105
|
private readonly mcpJson?: PikkuBunServerOptions['mcpJson']
|
|
85
106
|
private readonly mcpPath: string
|
|
86
107
|
private mcpHandler?: (request: Request) => Promise<Response>
|
|
108
|
+
private readonly localContent?: LocalContentRequestHandler
|
|
87
109
|
|
|
88
110
|
constructor(
|
|
89
111
|
private readonly config: BunServerConfig,
|
|
90
112
|
private readonly logger: Logger,
|
|
91
113
|
options: PikkuBunServerOptions = {}
|
|
92
114
|
) {
|
|
93
|
-
const { eventHub, mcpJson, mcpPath, ...httpOptions } =
|
|
115
|
+
const { eventHub, mcpJson, mcpPath, contentSigningJWT, ...httpOptions } =
|
|
116
|
+
options
|
|
94
117
|
this.eventHub = eventHub ?? new BunEventHubService()
|
|
95
118
|
this.mcpJson = mcpJson
|
|
96
119
|
this.mcpPath = mcpPath ?? '/mcp'
|
|
97
120
|
this.options = httpOptions
|
|
121
|
+
this.localContent = config.content
|
|
122
|
+
? createLocalContentRequestHandler({
|
|
123
|
+
content: config.content,
|
|
124
|
+
logger,
|
|
125
|
+
// Resolved per request: `singletonServices` is populated after the
|
|
126
|
+
// server is constructed, so reading it here would always miss.
|
|
127
|
+
getJWT: () =>
|
|
128
|
+
contentSigningJWT ??
|
|
129
|
+
pikkuState(null, 'package', 'singletonServices')?.jwt,
|
|
130
|
+
})
|
|
131
|
+
: undefined
|
|
98
132
|
}
|
|
99
133
|
|
|
100
134
|
public async init(): Promise<void> {
|
|
@@ -176,6 +210,14 @@ export class PikkuBunServer {
|
|
|
176
210
|
}
|
|
177
211
|
}
|
|
178
212
|
|
|
213
|
+
// Ahead of static mounts and routing: these prefixes belong to the
|
|
214
|
+
// content service, and `fetchData` would answer them with a 404 since
|
|
215
|
+
// no wiring claims them.
|
|
216
|
+
const contentResponse = await this.localContent?.(req)
|
|
217
|
+
if (contentResponse) {
|
|
218
|
+
return contentResponse
|
|
219
|
+
}
|
|
220
|
+
|
|
179
221
|
const staticResponse = await this.serveStaticMounts(req)
|
|
180
222
|
if (staticResponse) {
|
|
181
223
|
return staticResponse
|
package/src/static-mount.test.ts
CHANGED
|
@@ -75,7 +75,7 @@ describe('PikkuBunServer static mounts', () => {
|
|
|
75
75
|
})
|
|
76
76
|
|
|
77
77
|
test('SPA fallback serves index.html for unknown paths under the prefix', async () => {
|
|
78
|
-
const response = await fetch(`${origin}/console/
|
|
78
|
+
const response = await fetch(`${origin}/console/scenarios?id=x`)
|
|
79
79
|
assert.equal(response.status, 200)
|
|
80
80
|
assert.match(await response.text(), /<title>console<\/title>/)
|
|
81
81
|
})
|