@pikku/core 0.12.17 → 0.12.18
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 +11 -0
- package/dist/function/function-runner.js +22 -1
- package/dist/services/in-memory-session-store.d.ts +8 -0
- package/dist/services/in-memory-session-store.js +12 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +1 -0
- package/dist/services/session-store.d.ts +6 -0
- package/dist/services/session-store.js +1 -0
- package/dist/services/user-session-service.d.ts +12 -10
- package/dist/services/user-session-service.js +18 -21
- package/dist/testing/service-tests.d.ts +2 -0
- package/dist/testing/service-tests.js +50 -11
- package/dist/types/core.types.d.ts +3 -0
- package/dist/wirings/channel/channel-store.d.ts +5 -6
- package/dist/wirings/channel/local/local-channel-runner.js +7 -4
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +25 -8
- package/dist/wirings/cli/cli-runner.js +1 -1
- package/dist/wirings/http/http-runner.js +1 -1
- package/dist/wirings/mcp/mcp-runner.js +1 -1
- package/dist/wirings/scheduler/scheduler-runner.js +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +0 -1
- package/package.json +1 -1
- package/src/function/function-runner.ts +36 -1
- package/src/services/in-memory-session-store.test.ts +58 -0
- package/src/services/in-memory-session-store.ts +21 -0
- package/src/services/index.ts +2 -0
- package/src/services/session-store.ts +9 -0
- package/src/services/user-session-service.test.ts +53 -19
- package/src/services/user-session-service.ts +21 -22
- package/src/testing/service-tests.ts +64 -11
- package/src/types/core.types.ts +3 -0
- package/src/wirings/channel/channel-store.ts +5 -8
- package/src/wirings/channel/local/local-channel-runner.ts +10 -4
- package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -5
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +30 -9
- package/src/wirings/cli/cli-runner.ts +3 -1
- package/src/wirings/http/http-runner.ts +3 -1
- package/src/wirings/mcp/mcp-runner.ts +3 -1
- package/src/wirings/scheduler/scheduler-runner.ts +1 -1
- package/src/wirings/workflow/pikku-workflow-service.ts +0 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## 0.12.4
|
|
2
2
|
|
|
3
|
+
## 0.12.18
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 311c0c4: Unify session persistence through SessionStore, remove session blob from ChannelStore
|
|
8
|
+
|
|
9
|
+
- PikkuSessionService now persists sessions via SessionStore on set()/clear() instead of every function call
|
|
10
|
+
- ChannelStore no longer stores session data — maps channelId to pikkuUserId only
|
|
11
|
+
- ChannelStore API: setUserSession/getChannelAndSession replaced with setPikkuUserId/getChannel
|
|
12
|
+
- Serverless channel runner resolves sessions from SessionStore using pikkuUserId from ChannelStore
|
|
13
|
+
|
|
3
14
|
## 0.12.17
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
|
@@ -4,11 +4,31 @@ import { runPermissions } from '../permissions.js';
|
|
|
4
4
|
import { pikkuState } from '../pikku-state.js';
|
|
5
5
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
|
|
6
6
|
import { parseVersionedId } from '../version.js';
|
|
7
|
-
import { createFunctionSessionWireProps } from '../services/user-session-service.js';
|
|
7
|
+
import { PikkuSessionService, createFunctionSessionWireProps, } from '../services/user-session-service.js';
|
|
8
8
|
import { ForbiddenError, ReadonlySessionError } from '../errors/errors.js';
|
|
9
9
|
import { PikkuCredentialWireService, createWireServicesCredentialWireProps, } from '../services/credential-wire-service.js';
|
|
10
|
+
import { defaultPikkuUserIdResolver } from '../services/pikku-user-id.js';
|
|
10
11
|
import { rpcService } from '../wirings/rpc/rpc-runner.js';
|
|
11
12
|
import { closeWireServices } from '../utils.js';
|
|
13
|
+
async function resolveSession(wire, singletonServices, sessionService) {
|
|
14
|
+
const pikkuUserId = defaultPikkuUserIdResolver(wire);
|
|
15
|
+
if (pikkuUserId) {
|
|
16
|
+
wire.pikkuUserId = pikkuUserId;
|
|
17
|
+
if (sessionService instanceof PikkuSessionService) {
|
|
18
|
+
sessionService.setPikkuUserId(pikkuUserId);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const { sessionStore } = singletonServices;
|
|
22
|
+
if (!sessionStore || !pikkuUserId)
|
|
23
|
+
return;
|
|
24
|
+
if (!wire.session) {
|
|
25
|
+
const stored = await sessionStore.get(pikkuUserId);
|
|
26
|
+
if (stored) {
|
|
27
|
+
wire.session = stored;
|
|
28
|
+
sessionService?.setInitial(stored);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
12
32
|
/**
|
|
13
33
|
* Get or create singleton services for an addon package.
|
|
14
34
|
* Services are cached in pikkuState to avoid recreation on each call.
|
|
@@ -134,6 +154,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
|
|
|
134
154
|
}
|
|
135
155
|
// Helper function to run permissions and execute the function
|
|
136
156
|
const executeFunction = async () => {
|
|
157
|
+
await resolveSession(resolvedWire, resolvedSingletonServices, sessionService);
|
|
137
158
|
if (sessionService) {
|
|
138
159
|
resolvedWire.session = sessionService.freezeInitial();
|
|
139
160
|
resolvedWire.setSession = (s) => sessionService.set(s);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CoreUserSession } from '../types/core.types.js';
|
|
2
|
+
import type { SessionStore } from './session-store.js';
|
|
3
|
+
export declare class InMemorySessionStore<UserSession extends CoreUserSession = CoreUserSession> implements SessionStore<UserSession> {
|
|
4
|
+
private sessions;
|
|
5
|
+
get(pikkuUserId: string): Promise<UserSession | undefined>;
|
|
6
|
+
set(pikkuUserId: string, session: UserSession): Promise<void>;
|
|
7
|
+
clear(pikkuUserId: string): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class InMemorySessionStore {
|
|
2
|
+
sessions = new Map();
|
|
3
|
+
async get(pikkuUserId) {
|
|
4
|
+
return this.sessions.get(pikkuUserId);
|
|
5
|
+
}
|
|
6
|
+
async set(pikkuUserId, session) {
|
|
7
|
+
this.sessions.set(pikkuUserId, session);
|
|
8
|
+
}
|
|
9
|
+
async clear(pikkuUserId) {
|
|
10
|
+
this.sessions.delete(pikkuUserId);
|
|
11
|
+
}
|
|
12
|
+
}
|
package/dist/services/index.d.ts
CHANGED
|
@@ -36,4 +36,6 @@ export { TypedCredentialService } from './typed-credential-service.js';
|
|
|
36
36
|
export type { CredentialStatusInfo, CredentialMetaInfo, } from './typed-credential-service.js';
|
|
37
37
|
export type { VariableStatus, VariableMeta } from './typed-variables-service.js';
|
|
38
38
|
export type { MetaService } from './meta-service.js';
|
|
39
|
+
export type { SessionStore } from './session-store.js';
|
|
40
|
+
export { InMemorySessionStore } from './in-memory-session-store.js';
|
|
39
41
|
export type { MCPMeta, RPCMetaRecord, ServiceMeta, ServicesMetaRecord, MiddlewareDefinitionMeta, MiddlewareInstanceMeta, GroupMeta, MiddlewareGroupsMeta, PermissionDefinitionMeta, PermissionsGroupsMeta, FunctionsMeta, FunctionMeta, MiddlewareMeta, PermissionMeta, AgentsMeta, AgentMeta, } from './meta-service.js';
|
package/dist/services/index.js
CHANGED
|
@@ -17,3 +17,4 @@ export { InMemoryTriggerService } from './in-memory-trigger-service.js';
|
|
|
17
17
|
export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js';
|
|
18
18
|
export { LocalGatewayService } from './local-gateway-service.js';
|
|
19
19
|
export { TypedCredentialService } from './typed-credential-service.js';
|
|
20
|
+
export { InMemorySessionStore } from './in-memory-session-store.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CoreUserSession } from '../types/core.types.js';
|
|
2
|
+
export interface SessionStore<UserSession extends CoreUserSession = CoreUserSession> {
|
|
3
|
+
get(pikkuUserId: string): Promise<UserSession | undefined>;
|
|
4
|
+
set(pikkuUserId: string, session: UserSession): Promise<void>;
|
|
5
|
+
clear(pikkuUserId: string): Promise<void>;
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ChannelStore } from '../wirings/channel/channel-store.js';
|
|
2
1
|
import type { CoreUserSession } from '../types/core.types.js';
|
|
2
|
+
import type { SessionStore } from './session-store.js';
|
|
3
3
|
export interface SessionService<UserSession extends CoreUserSession> {
|
|
4
4
|
sessionChanged: boolean;
|
|
5
5
|
initial: UserSession | undefined;
|
|
@@ -7,31 +7,33 @@ export interface SessionService<UserSession extends CoreUserSession> {
|
|
|
7
7
|
freezeInitial(): UserSession | undefined;
|
|
8
8
|
set(session: UserSession): Promise<void> | void;
|
|
9
9
|
clear(): Promise<void> | void;
|
|
10
|
-
get():
|
|
10
|
+
get(): UserSession | undefined;
|
|
11
11
|
}
|
|
12
12
|
export declare class PikkuSessionService<UserSession extends CoreUserSession> implements SessionService<UserSession> {
|
|
13
|
-
private
|
|
14
|
-
private channelId?;
|
|
13
|
+
private sessionStore?;
|
|
15
14
|
sessionChanged: boolean;
|
|
16
15
|
initial: UserSession | undefined;
|
|
17
16
|
private session;
|
|
18
|
-
|
|
17
|
+
private pikkuUserId?;
|
|
18
|
+
constructor(sessionStore?: SessionStore<UserSession> | undefined);
|
|
19
|
+
setPikkuUserId(id: string): void;
|
|
20
|
+
getPikkuUserId(): string | undefined;
|
|
19
21
|
setInitial(session: UserSession): void;
|
|
20
22
|
freezeInitial(): UserSession | undefined;
|
|
21
|
-
set(session: UserSession):
|
|
22
|
-
clear():
|
|
23
|
-
get():
|
|
23
|
+
set(session: UserSession): Promise<void>;
|
|
24
|
+
clear(): Promise<void>;
|
|
25
|
+
get(): UserSession | undefined;
|
|
24
26
|
}
|
|
25
27
|
export declare function createMiddlewareSessionWireProps<UserSession extends CoreUserSession>(session: SessionService<UserSession>): {
|
|
26
28
|
session: UserSession | undefined;
|
|
27
29
|
setSession: (s: UserSession) => void;
|
|
28
|
-
getSession: () => UserSession |
|
|
30
|
+
getSession: () => UserSession | undefined;
|
|
29
31
|
hasSessionChanged: () => boolean;
|
|
30
32
|
};
|
|
31
33
|
export declare function createFunctionSessionWireProps<UserSession extends CoreUserSession>(session: SessionService<UserSession>): {
|
|
32
34
|
session: UserSession | undefined;
|
|
33
35
|
setSession: (s: UserSession) => void | Promise<void>;
|
|
34
36
|
clearSession: () => void | Promise<void>;
|
|
35
|
-
getSession: () => UserSession |
|
|
37
|
+
getSession: () => UserSession | undefined;
|
|
36
38
|
hasSessionChanged: () => boolean;
|
|
37
39
|
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
export class PikkuSessionService {
|
|
2
|
-
|
|
3
|
-
channelId;
|
|
2
|
+
sessionStore;
|
|
4
3
|
sessionChanged = false;
|
|
5
4
|
initial;
|
|
6
5
|
session;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
pikkuUserId;
|
|
7
|
+
constructor(sessionStore) {
|
|
8
|
+
this.sessionStore = sessionStore;
|
|
9
|
+
}
|
|
10
|
+
setPikkuUserId(id) {
|
|
11
|
+
this.pikkuUserId = id;
|
|
12
|
+
}
|
|
13
|
+
getPikkuUserId() {
|
|
14
|
+
return this.pikkuUserId;
|
|
13
15
|
}
|
|
14
16
|
setInitial(session) {
|
|
15
17
|
this.session = session;
|
|
@@ -20,26 +22,21 @@ export class PikkuSessionService {
|
|
|
20
22
|
}
|
|
21
23
|
return this.initial;
|
|
22
24
|
}
|
|
23
|
-
set(session) {
|
|
25
|
+
async set(session) {
|
|
24
26
|
this.sessionChanged = true;
|
|
25
27
|
this.session = session;
|
|
26
|
-
|
|
28
|
+
if (this.sessionStore && this.pikkuUserId) {
|
|
29
|
+
await this.sessionStore.set(this.pikkuUserId, session);
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
|
-
clear() {
|
|
32
|
+
async clear() {
|
|
29
33
|
this.sessionChanged = true;
|
|
30
34
|
this.session = undefined;
|
|
31
|
-
|
|
35
|
+
if (this.sessionStore && this.pikkuUserId) {
|
|
36
|
+
await this.sessionStore.clear(this.pikkuUserId);
|
|
37
|
+
}
|
|
32
38
|
}
|
|
33
39
|
get() {
|
|
34
|
-
if (this.channelStore) {
|
|
35
|
-
const channel = this.channelStore.getChannelAndSession(this.channelId);
|
|
36
|
-
if (channel instanceof Promise) {
|
|
37
|
-
return channel.then(({ session }) => session);
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
return channel.session;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
40
|
return this.session;
|
|
44
41
|
}
|
|
45
42
|
}
|
|
@@ -8,6 +8,7 @@ import type { AIRunStateService } from '../services/ai-run-state-service.js';
|
|
|
8
8
|
import type { SecretService } from '../services/secret-service.js';
|
|
9
9
|
import type { CredentialService } from '../services/credential-service.js';
|
|
10
10
|
import type { AgentRunService } from '../wirings/ai-agent/ai-agent.types.js';
|
|
11
|
+
import type { SessionStore } from '../services/session-store.js';
|
|
11
12
|
export interface ServiceTestConfig {
|
|
12
13
|
name: string;
|
|
13
14
|
services: {
|
|
@@ -34,6 +35,7 @@ export interface ServiceTestConfig {
|
|
|
34
35
|
}) => Promise<CredentialService & {
|
|
35
36
|
rotateKEK?(): Promise<number>;
|
|
36
37
|
}>;
|
|
38
|
+
sessionStore?: () => Promise<SessionStore>;
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
export declare function defineServiceTests(config: ServiceTestConfig): void;
|
|
@@ -9,27 +9,31 @@ export function defineServiceTests(config) {
|
|
|
9
9
|
before(async () => {
|
|
10
10
|
store = await factory();
|
|
11
11
|
});
|
|
12
|
-
test('addChannel and
|
|
12
|
+
test('addChannel and getChannel', async () => {
|
|
13
13
|
await store.addChannel({
|
|
14
14
|
channelId: 'ch-1',
|
|
15
15
|
channelName: 'test-channel',
|
|
16
16
|
openingData: { foo: 'bar' },
|
|
17
17
|
});
|
|
18
|
-
const result = await store.
|
|
18
|
+
const result = await store.getChannel('ch-1');
|
|
19
19
|
assert.equal(result.channelId, 'ch-1');
|
|
20
20
|
assert.equal(result.channelName, 'test-channel');
|
|
21
21
|
assert.deepEqual(result.openingData, { foo: 'bar' });
|
|
22
|
-
assert.
|
|
22
|
+
assert.equal(result.pikkuUserId, undefined);
|
|
23
23
|
});
|
|
24
|
-
test('
|
|
25
|
-
|
|
26
|
-
await store.
|
|
27
|
-
|
|
28
|
-
assert.deepEqual(result.session, session);
|
|
24
|
+
test('setPikkuUserId', async () => {
|
|
25
|
+
await store.setPikkuUserId('ch-1', 'user-1');
|
|
26
|
+
const result = await store.getChannel('ch-1');
|
|
27
|
+
assert.equal(result.pikkuUserId, 'user-1');
|
|
29
28
|
});
|
|
30
|
-
test('
|
|
29
|
+
test('setPikkuUserId to null', async () => {
|
|
30
|
+
await store.setPikkuUserId('ch-1', null);
|
|
31
|
+
const result = await store.getChannel('ch-1');
|
|
32
|
+
assert.equal(result.pikkuUserId, undefined);
|
|
33
|
+
});
|
|
34
|
+
test('getChannel throws for missing channel', async () => {
|
|
31
35
|
await assert.rejects(async () => {
|
|
32
|
-
await store.
|
|
36
|
+
await store.getChannel('missing');
|
|
33
37
|
}, { message: 'Channel not found: missing' });
|
|
34
38
|
});
|
|
35
39
|
test('removeChannels', async () => {
|
|
@@ -39,7 +43,7 @@ export function defineServiceTests(config) {
|
|
|
39
43
|
});
|
|
40
44
|
await store.removeChannels(['ch-2']);
|
|
41
45
|
await assert.rejects(async () => {
|
|
42
|
-
await store.
|
|
46
|
+
await store.getChannel('ch-2');
|
|
43
47
|
});
|
|
44
48
|
});
|
|
45
49
|
test('removeChannels with empty array is no-op', async () => {
|
|
@@ -693,4 +697,39 @@ export function defineServiceTests(config) {
|
|
|
693
697
|
});
|
|
694
698
|
});
|
|
695
699
|
}
|
|
700
|
+
if (services.sessionStore) {
|
|
701
|
+
const factory = services.sessionStore;
|
|
702
|
+
describe(`SessionStore [${name}]`, () => {
|
|
703
|
+
let store;
|
|
704
|
+
before(async () => {
|
|
705
|
+
store = await factory();
|
|
706
|
+
});
|
|
707
|
+
test('get returns undefined for unknown user', async () => {
|
|
708
|
+
const result = await store.get('unknown-user');
|
|
709
|
+
assert.equal(result, undefined);
|
|
710
|
+
});
|
|
711
|
+
test('set and get round-trip', async () => {
|
|
712
|
+
const session = { userId: 'user-1', organizationId: 'org-1' };
|
|
713
|
+
await store.set('user-1', session);
|
|
714
|
+
const result = await store.get('user-1');
|
|
715
|
+
assert.deepEqual(result, session);
|
|
716
|
+
});
|
|
717
|
+
test('set overwrites previous session', async () => {
|
|
718
|
+
await store.set('user-2', { userId: 'user-2', role: 'admin' });
|
|
719
|
+
await store.set('user-2', { userId: 'user-2', role: 'member' });
|
|
720
|
+
const result = await store.get('user-2');
|
|
721
|
+
assert.deepEqual(result, { userId: 'user-2', role: 'member' });
|
|
722
|
+
});
|
|
723
|
+
test('clear removes session', async () => {
|
|
724
|
+
await store.set('user-3', { userId: 'user-3' });
|
|
725
|
+
assert.ok(await store.get('user-3'));
|
|
726
|
+
await store.clear('user-3');
|
|
727
|
+
const result = await store.get('user-3');
|
|
728
|
+
assert.equal(result, undefined);
|
|
729
|
+
});
|
|
730
|
+
test('clear is no-op for unknown user', async () => {
|
|
731
|
+
await store.clear('nonexistent');
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
}
|
|
696
735
|
}
|
|
@@ -25,6 +25,7 @@ import type { PikkuAIMiddlewareHooks } from '../wirings/ai-agent/ai-agent.types.
|
|
|
25
25
|
import type { WorkflowRunService } from '../wirings/workflow/workflow.types.js';
|
|
26
26
|
import type { CredentialService } from '../services/credential-service.js';
|
|
27
27
|
import type { MetaService } from '../services/meta-service.js';
|
|
28
|
+
import type { SessionStore } from '../services/session-store.js';
|
|
28
29
|
export type PikkuWiringTypes = 'http' | 'scheduler' | 'trigger' | 'channel' | 'rpc' | 'queue' | 'mcp' | 'cli' | 'workflow' | 'agent' | 'gateway';
|
|
29
30
|
export interface FunctionServicesMeta {
|
|
30
31
|
optimized: boolean;
|
|
@@ -181,6 +182,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
181
182
|
credentialService?: CredentialService;
|
|
182
183
|
/** Meta service for reading .pikku metadata files (filesystem on Node, R2/KV on CF) */
|
|
183
184
|
metaService?: MetaService;
|
|
185
|
+
/** Session store for persisting user sessions keyed by pikkuUserId */
|
|
186
|
+
sessionStore?: SessionStore;
|
|
184
187
|
}
|
|
185
188
|
/**
|
|
186
189
|
* Represents different forms of wire within Pikku and the outside world.
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import type { CoreUserSession } from '../../types/core.types.js';
|
|
2
1
|
export type Channel<ChannelType = unknown, OpeningData = unknown> = {
|
|
3
2
|
channelId: string;
|
|
4
3
|
channelName: string;
|
|
5
4
|
channelObject?: ChannelType;
|
|
6
5
|
openingData?: OpeningData;
|
|
7
6
|
};
|
|
8
|
-
export declare abstract class ChannelStore<ChannelType = unknown, OpeningData = unknown,
|
|
7
|
+
export declare abstract class ChannelStore<ChannelType = unknown, OpeningData = unknown, TypedChannel = Channel<ChannelType, OpeningData>> {
|
|
9
8
|
abstract addChannel(channel: Channel<ChannelType, OpeningData>): Promise<void> | void;
|
|
10
9
|
abstract removeChannels(channelId: string[]): Promise<void> | void;
|
|
11
|
-
abstract
|
|
12
|
-
abstract
|
|
13
|
-
|
|
10
|
+
abstract setPikkuUserId(channelId: string, pikkuUserId: string | null): Promise<void> | void;
|
|
11
|
+
abstract getChannel(channelId: string): Promise<TypedChannel & {
|
|
12
|
+
pikkuUserId?: string;
|
|
14
13
|
}> | (TypedChannel & {
|
|
15
|
-
|
|
14
|
+
pikkuUserId?: string;
|
|
16
15
|
});
|
|
17
16
|
}
|
|
@@ -15,7 +15,7 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
15
15
|
let wireServices;
|
|
16
16
|
let closedWireServices = false;
|
|
17
17
|
let channelHandler;
|
|
18
|
-
const userSession = new PikkuSessionService();
|
|
18
|
+
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
19
19
|
let http;
|
|
20
20
|
if (request) {
|
|
21
21
|
http = createHTTPWire(request, response);
|
|
@@ -79,7 +79,8 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
79
79
|
singletonServices.logger.error(`Error handling onConnect: ${e}`);
|
|
80
80
|
const errorResponse = getErrorResponse(e);
|
|
81
81
|
channel.send({
|
|
82
|
-
error: errorResponse?.message ??
|
|
82
|
+
error: errorResponse?.message ??
|
|
83
|
+
(isProduction() ? 'Internal server error' : e.message),
|
|
83
84
|
...(!isProduction() && { errorName: e.constructor?.name }),
|
|
84
85
|
});
|
|
85
86
|
}
|
|
@@ -102,7 +103,8 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
102
103
|
singletonServices.logger.error(`Error handling onDisconnect: ${e}`);
|
|
103
104
|
const errorResponse = getErrorResponse(e);
|
|
104
105
|
channel.send({
|
|
105
|
-
error: errorResponse?.message ??
|
|
106
|
+
error: errorResponse?.message ??
|
|
107
|
+
(isProduction() ? 'Internal server error' : e.message),
|
|
106
108
|
...(!isProduction() && { errorName: e.constructor?.name }),
|
|
107
109
|
});
|
|
108
110
|
}
|
|
@@ -119,7 +121,8 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
119
121
|
singletonServices.logger.error(e);
|
|
120
122
|
const errorResponse = getErrorResponse(e);
|
|
121
123
|
channel.send({
|
|
122
|
-
error: errorResponse?.message ??
|
|
124
|
+
error: errorResponse?.message ??
|
|
125
|
+
(isProduction() ? 'Internal server error' : e.message),
|
|
123
126
|
...(!isProduction() && { errorName: e.constructor?.name }),
|
|
124
127
|
});
|
|
125
128
|
setTimeout(() => channel.close(), 200);
|
|
@@ -34,7 +34,7 @@ export const runChannelConnect = async ({ channelId, channelObject, request, res
|
|
|
34
34
|
if (request instanceof Request) {
|
|
35
35
|
http = createHTTPWire(new PikkuFetchHTTPRequest(request), response);
|
|
36
36
|
}
|
|
37
|
-
const userSession = new PikkuSessionService(
|
|
37
|
+
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
38
38
|
const { channelConfig, openingData, meta } = await openChannel({
|
|
39
39
|
channelId,
|
|
40
40
|
request,
|
|
@@ -77,6 +77,11 @@ export const runChannelConnect = async ({ channelId, channelObject, request, res
|
|
|
77
77
|
channelMiddlewareMeta: meta.channelMiddleware,
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
|
+
// Store the pikkuUserId mapping after auth middleware has run
|
|
81
|
+
const pikkuUserId = userSession.getPikkuUserId();
|
|
82
|
+
if (pikkuUserId) {
|
|
83
|
+
await channelStore.setPikkuUserId(channelId, pikkuUserId);
|
|
84
|
+
}
|
|
80
85
|
http?.response?.status(101);
|
|
81
86
|
}
|
|
82
87
|
catch (e) {
|
|
@@ -98,21 +103,27 @@ export const runChannelDisconnect = async ({ channelId, channelStore, channelHan
|
|
|
98
103
|
// there's nothing to disconnect, so we can return early.
|
|
99
104
|
let channelData;
|
|
100
105
|
try {
|
|
101
|
-
channelData = await channelStore.
|
|
106
|
+
channelData = await channelStore.getChannel(channelId);
|
|
102
107
|
}
|
|
103
108
|
catch {
|
|
104
109
|
singletonServices.logger.info(`Channel ${channelId} not found during disconnect - already cleaned up`);
|
|
105
110
|
return;
|
|
106
111
|
}
|
|
107
|
-
const { openingData, channelName,
|
|
112
|
+
const { openingData, channelName, pikkuUserId } = channelData;
|
|
108
113
|
const { channel, channelConfig, meta } = getVariablesForChannel({
|
|
109
114
|
channelId,
|
|
110
115
|
channelHandlerFactory,
|
|
111
116
|
openingData,
|
|
112
117
|
channelName,
|
|
113
118
|
});
|
|
114
|
-
const userSession = new PikkuSessionService(
|
|
115
|
-
|
|
119
|
+
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
120
|
+
if (pikkuUserId) {
|
|
121
|
+
userSession.setPikkuUserId(pikkuUserId);
|
|
122
|
+
const session = await singletonServices.sessionStore?.get(pikkuUserId);
|
|
123
|
+
if (session) {
|
|
124
|
+
userSession.setInitial(session);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
116
127
|
const wire = {
|
|
117
128
|
channel,
|
|
118
129
|
...createMiddlewareSessionWireProps(userSession),
|
|
@@ -149,15 +160,21 @@ export const runChannelMessage = async ({ channelId, channelStore, channelHandle
|
|
|
149
160
|
const singletonServices = getSingletonServices();
|
|
150
161
|
const createWireServices = getCreateWireServices();
|
|
151
162
|
let wireServices;
|
|
152
|
-
const { openingData, channelName,
|
|
163
|
+
const { openingData, channelName, pikkuUserId } = await channelStore.getChannel(channelId);
|
|
153
164
|
const { channel, channelHandler, channelConfig } = getVariablesForChannel({
|
|
154
165
|
channelId,
|
|
155
166
|
channelHandlerFactory,
|
|
156
167
|
openingData,
|
|
157
168
|
channelName,
|
|
158
169
|
});
|
|
159
|
-
const userSession = new PikkuSessionService(
|
|
160
|
-
|
|
170
|
+
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
171
|
+
if (pikkuUserId) {
|
|
172
|
+
userSession.setPikkuUserId(pikkuUserId);
|
|
173
|
+
const session = await singletonServices.sessionStore?.get(pikkuUserId);
|
|
174
|
+
if (session) {
|
|
175
|
+
userSession.setInitial(session);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
161
178
|
const wire = {
|
|
162
179
|
channel,
|
|
163
180
|
...createMiddlewareSessionWireProps(userSession),
|
|
@@ -224,7 +224,7 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
|
|
|
224
224
|
},
|
|
225
225
|
state: 'open',
|
|
226
226
|
};
|
|
227
|
-
const userSession = new PikkuSessionService();
|
|
227
|
+
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
228
228
|
const wire = {
|
|
229
229
|
cli: {
|
|
230
230
|
program,
|
|
@@ -201,9 +201,9 @@ export const createHTTPWire = (request, response) => {
|
|
|
201
201
|
* @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
|
|
202
202
|
*/
|
|
203
203
|
const executeRoute = async (services, matchedRoute, http, options) => {
|
|
204
|
-
const userSession = new PikkuSessionService();
|
|
205
204
|
const { params, route, meta } = matchedRoute;
|
|
206
205
|
const { singletonServices, createWireServices, skipUserSession, requestId } = services;
|
|
206
|
+
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
207
207
|
// Attach URL parameters to the request object
|
|
208
208
|
http?.request?.setParams(params);
|
|
209
209
|
// Validate request headers if schema is defined
|
|
@@ -103,7 +103,7 @@ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncId, { mcp: mcp
|
|
|
103
103
|
throw new NotFoundError(`MCP '${type}' PikkuFunction Mapping not found for '${name}'`);
|
|
104
104
|
}
|
|
105
105
|
singletonServices.logger.debug(`Running MCP ${type}: ${name}`);
|
|
106
|
-
const mcpSessionService = new PikkuSessionService();
|
|
106
|
+
const mcpSessionService = new PikkuSessionService(singletonServices.sessionStore);
|
|
107
107
|
const wire = {
|
|
108
108
|
mcp: mcpWire,
|
|
109
109
|
...createMiddlewareSessionWireProps(mcpSessionService),
|
|
@@ -41,7 +41,7 @@ export async function runScheduledTask({ name, session, traceId, }) {
|
|
|
41
41
|
singletonServices.logger;
|
|
42
42
|
const task = pikkuState(null, 'scheduler', 'tasks').get(name);
|
|
43
43
|
const meta = pikkuState(null, 'scheduler', 'meta')[name];
|
|
44
|
-
const userSession = new PikkuSessionService();
|
|
44
|
+
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
45
45
|
if (session) {
|
|
46
46
|
userSession.set(session);
|
|
47
47
|
}
|
package/package.json
CHANGED
|
@@ -26,15 +26,44 @@ import type {
|
|
|
26
26
|
} from './functions.types.js'
|
|
27
27
|
import { parseVersionedId } from '../version.js'
|
|
28
28
|
import type { SessionService } from '../services/user-session-service.js'
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
PikkuSessionService,
|
|
31
|
+
createFunctionSessionWireProps,
|
|
32
|
+
} from '../services/user-session-service.js'
|
|
30
33
|
import { ForbiddenError, ReadonlySessionError } from '../errors/errors.js'
|
|
31
34
|
import {
|
|
32
35
|
PikkuCredentialWireService,
|
|
33
36
|
createWireServicesCredentialWireProps,
|
|
34
37
|
} from '../services/credential-wire-service.js'
|
|
38
|
+
import { defaultPikkuUserIdResolver } from '../services/pikku-user-id.js'
|
|
35
39
|
import { rpcService } from '../wirings/rpc/rpc-runner.js'
|
|
36
40
|
import { closeWireServices } from '../utils.js'
|
|
37
41
|
|
|
42
|
+
async function resolveSession(
|
|
43
|
+
wire: PikkuWire,
|
|
44
|
+
singletonServices: CoreSingletonServices,
|
|
45
|
+
sessionService?: SessionService<CoreUserSession>
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
const pikkuUserId = defaultPikkuUserIdResolver(wire)
|
|
48
|
+
if (pikkuUserId) {
|
|
49
|
+
wire.pikkuUserId = pikkuUserId
|
|
50
|
+
if (sessionService instanceof PikkuSessionService) {
|
|
51
|
+
sessionService.setPikkuUserId(pikkuUserId)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { sessionStore } = singletonServices
|
|
56
|
+
if (!sessionStore || !pikkuUserId) return
|
|
57
|
+
|
|
58
|
+
if (!wire.session) {
|
|
59
|
+
const stored = await sessionStore.get(pikkuUserId)
|
|
60
|
+
if (stored) {
|
|
61
|
+
wire.session = stored
|
|
62
|
+
sessionService?.setInitial(stored)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
38
67
|
/**
|
|
39
68
|
* Get or create singleton services for an addon package.
|
|
40
69
|
* Services are cached in pikkuState to avoid recreation on each call.
|
|
@@ -256,6 +285,12 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
256
285
|
|
|
257
286
|
// Helper function to run permissions and execute the function
|
|
258
287
|
const executeFunction = async () => {
|
|
288
|
+
await resolveSession(
|
|
289
|
+
resolvedWire,
|
|
290
|
+
resolvedSingletonServices,
|
|
291
|
+
sessionService
|
|
292
|
+
)
|
|
293
|
+
|
|
259
294
|
if (sessionService) {
|
|
260
295
|
resolvedWire.session = sessionService.freezeInitial()
|
|
261
296
|
resolvedWire.setSession = (s: any) => sessionService.set(s)
|