feat: embed Chatwoot live-chat widget (destek.sase.tr)
Site-wide live-chat widget served from the self-hosted Chatwoot at destek.sase.tr, with verified user identity and vehicle context. - apps/web: lib/chatwoot.ts loads the SDK lazily (mirrors the PostHog init pattern), init in main.tsx, identify logged-in users in __root via a server-computed HMAC, and attach the viewed vehicle (VIN/brand/ model) as contact custom attributes on the vehicle detail page. - apps/api: GET /api/chatwoot/identity (AuthGuard-protected) returns HMAC-SHA256(user.id) so the widget can use verified identity. - env: VITE_CHATWOOT_BASE_URL + VITE_CHATWOOT_WEBSITE_TOKEN (build-time, wired through docker-compose.coolify.yml build args + Dockerfile ARG) and CHATWOOT_HMAC_TOKEN (api runtime). All optional — widget and endpoint no-op when unset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import { BrandsModule } from "./brands/brands.module";
|
||||
import { CatalogModule } from "./catalog/catalog.module";
|
||||
import { CategoriesModule } from "./categories/categories.module";
|
||||
import { ChangelogModule } from "./changelog/changelog.module";
|
||||
import { ChatwootModule } from "./chatwoot/chatwoot.module";
|
||||
import { HttpExceptionFilter } from "./common/filters/http-exception.filter";
|
||||
import { AuthGuard } from "./common/guards/auth.guard";
|
||||
import { ImpersonationReadonlyGuard } from "./common/guards/impersonation-readonly.guard";
|
||||
@@ -89,6 +90,7 @@ import { VehiclesModule } from "./vehicles/vehicles.module";
|
||||
AnalyticsModule,
|
||||
CatalogModule,
|
||||
ChangelogModule,
|
||||
ChatwootModule,
|
||||
BlogModule,
|
||||
ContactModule,
|
||||
PostHogModule,
|
||||
|
||||
23
apps/api/src/chatwoot/chatwoot.controller.ts
Normal file
23
apps/api/src/chatwoot/chatwoot.controller.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createHmac } from "node:crypto";
|
||||
import { Controller, Get, ServiceUnavailableException } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { CurrentUser } from "../common/decorators/current-user.decorator";
|
||||
|
||||
@Controller("chatwoot")
|
||||
export class ChatwootController {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
// HMAC-SHA256 of the authenticated user's id, consumed by the Chatwoot
|
||||
// live-chat widget as `identifier_hash` for verified identity (setUser).
|
||||
// The global AuthGuard protects this route — only logged-in users reach it,
|
||||
// so a visitor can never forge another user's hash.
|
||||
@Get("identity")
|
||||
getIdentity(@CurrentUser("id") userId: string) {
|
||||
const token = this.configService.get<string>("chatwoot.hmacToken");
|
||||
if (!token) {
|
||||
throw new ServiceUnavailableException("Chatwoot identity not configured");
|
||||
}
|
||||
const identifierHash = createHmac("sha256", token).update(userId).digest("hex");
|
||||
return { identifier: userId, identifierHash };
|
||||
}
|
||||
}
|
||||
7
apps/api/src/chatwoot/chatwoot.module.ts
Normal file
7
apps/api/src/chatwoot/chatwoot.module.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { ChatwootController } from "./chatwoot.controller";
|
||||
|
||||
@Module({
|
||||
controllers: [ChatwootController],
|
||||
})
|
||||
export class ChatwootModule {}
|
||||
@@ -66,6 +66,9 @@ export default () => ({
|
||||
mailtrack: {
|
||||
secret: process.env.MAILTRACK_SECRET,
|
||||
},
|
||||
chatwoot: {
|
||||
hmacToken: process.env.CHATWOOT_HMAC_TOKEN,
|
||||
},
|
||||
otel: {
|
||||
enabled: process.env.OTEL_ENABLED === "true",
|
||||
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
||||
|
||||
Reference in New Issue
Block a user