feat(mobile): bootstrap mobile app (PWA + Capacitor + backend auth/CORS)

Implements the §12 bootstrap from docs/mobile-app-plan.md.

Backend (§6):
- auth: optional returnToken flag on login returns the JWT in the body
  (data.authToken) for native Keychain/Keystore + Bearer; web cookie flow
  unchanged.
- main.ts: explicit CORS allowlist (APP_URL + CORS_ALLOWED_ORIGINS env +
  Capacitor WebView origins), credentials enabled, replaces open enableCors().
- optional OpenAPI/Swagger at /api/docs behind SWAGGER_ENABLED.
- env: CORS_ALLOWED_ORIGINS, SWAGGER_ENABLED, CAP_SERVER_URL.

PWA:
- manifest metadata, hand-rolled service worker (network-first nav, SWR
  assets, never intercepts /api,/socket.io,/collab), prod-only registration,
  apple-touch-icon.

Capacitor:
- capacitor.config.ts (webDir apps/client/dist; iOS via CAP_SERVER_URL to
  avoid bundling the AGPL client in the .ipa, see plan §9), cap:* scripts,
  deps, .gitignore for native dirs.
- docs/mobile-bootstrap.md documenting what is done and the remaining manual
  steps (cap add ios/android, APNs/FCM, stores).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude_code
2026-06-21 14:08:29 +03:00
parent e5bc82c7f1
commit 9319bc7356
14 changed files with 288 additions and 23 deletions
+43 -1
View File
@@ -15,6 +15,7 @@ import { InternalLogFilter } from './common/logger/internal-log-filter';
import { EnvironmentService } from './integrations/environment/environment.service';
import { resolveFrameHeader } from './common/helpers';
import { resolveTrustProxy } from './integrations/environment/trust-proxy.util';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
@@ -142,8 +143,49 @@ async function bootstrap() {
}),
);
app.enableCors();
// Configure CORS explicitly (replaces the previous unconfigured enableCors()).
// The web client is same-origin in production; an explicit allowlist lets
// native/mobile WebView origins (Capacitor) and any configured cross-origin
// clients call the API, while everything else is rejected.
const corsAllowedOrigins = new Set<string>([
environmentService.getAppUrl(),
...environmentService.getCorsAllowedOrigins(),
// Capacitor / Ionic WebView origins used by the native shell.
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'https://localhost',
]);
app.enableCors({
// Allow requests with no Origin header (curl, server-to-server, some native
// WebView requests) and any origin in the allowlist; reject the rest.
origin: (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => {
if (!origin || corsAllowedOrigins.has(origin)) {
callback(null, true);
return;
}
callback(null, false);
},
credentials: true,
methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
});
app.useGlobalInterceptors(new TransformHttpResponseInterceptor(reflector));
if (environmentService.isSwaggerEnabled()) {
// Optional OpenAPI docs to speed up typed mobile-client generation.
const swaggerConfig = new DocumentBuilder()
.setTitle('Gitmost API')
.setDescription('Gitmost REST API (RPC-style POST endpoints).')
.setVersion(process.env.APP_VERSION || '0.0.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('api/docs', app, document);
}
app.enableShutdownHooks();
const logger = new Logger('NestApplication');