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:
2026-07-01 01:36:14 +05:30
parent eff3c615d5
commit 4d05f7c1b1
14 changed files with 960 additions and 3 deletions
+2 -1
View File
@@ -6,9 +6,10 @@ import { OutboxModule } from './outbox/outbox.module';
import { ThreadsModule } from './threads/threads.module';
import { MessageModule } from './messaging/message.module';
import { HealthController } from './health.controller';
import { DevController } from './dev/dev.controller';
@Module({
imports: [PrismaModule, PlatformModule, InteractionsModule, OutboxModule, ThreadsModule, MessageModule],
controllers: [HealthController],
controllers: [HealthController, DevController],
})
export class AppModule {}
@@ -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 };
}
}
+1
View File
@@ -1,3 +1,4 @@
import 'dotenv/config';
import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';