feat(demo): P2.7 Vite React two-pane realtime demo + dev token endpoint (P2 complete)
apps/message-demo: Alice creates a thread, Bob joins; live two-way chat, typing, read receipts (useMessages now exposes reads[]). Dev-only /v1/dev/token endpoint (gated by IIOS_DEV_TOKENS) so the browser can auth. .env autoloaded (dotenv). Realtime smoke script passes end-to-end (message + unread + receipt). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { BadRequestException, Body, Controller, ForbiddenException, Post } from '@nestjs/common';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
/**
|
||||
* Dev-only helper: mints an HS256 token a host app would normally sign, so the
|
||||
* browser demo can authenticate. Gated by IIOS_DEV_TOKENS=1 — never enable in
|
||||
* production (the host app signs its own tokens there).
|
||||
*/
|
||||
@Controller('v1/dev')
|
||||
export class DevController {
|
||||
@Post('token')
|
||||
token(@Body() body: { appId: string; userId: string; name?: string; orgId?: string }): { token: string } {
|
||||
if (process.env.IIOS_DEV_TOKENS !== '1') throw new ForbiddenException('dev tokens disabled');
|
||||
let secrets: Record<string, string>;
|
||||
try {
|
||||
secrets = JSON.parse(process.env.APP_SECRETS ?? '{}');
|
||||
} catch {
|
||||
secrets = {};
|
||||
}
|
||||
const secret = secrets[body.appId];
|
||||
if (!secret) throw new BadRequestException(`unknown app: ${body.appId}`);
|
||||
const token = jwt.sign(
|
||||
{ sub: body.userId, name: body.name ?? body.userId, appId: body.appId, orgId: body.orgId ?? `org_${body.appId}` },
|
||||
secret,
|
||||
{ algorithm: 'HS256', expiresIn: '2h' },
|
||||
);
|
||||
return { token };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user