@nexttylabs/echo 0.3.0 → 0.4.0

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @nexttylabs/echo
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5cfdeb7: feat: support editing user profile
8
+
9
+ ### Patch Changes
10
+
11
+ - 7dba561: remove legacy changeset files
12
+ - e3c4e1c: fix workflow errors
13
+ - daf9abb: first release
14
+
3
15
  ## 0.3.0
4
16
 
5
17
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexttylabs/echo",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "license": "AGPL-3.0",
5
5
  "private": false,
6
6
  "publishConfig": {
package/proxy.ts CHANGED
@@ -30,19 +30,6 @@ const publicRoutes = ["/login", "/register", "/invite", "/invite/", "/api/auth",
30
30
  const protectedRoutes = ["/dashboard", "/feedback", "/settings"];
31
31
  const LOCALE_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
32
32
 
33
- // Primary app hosts (custom domains will not match these)
34
- const APP_HOSTS = new Set([
35
- "localhost",
36
- "localhost:3000",
37
- "127.0.0.1:3000",
38
- // Add production domains when deployed
39
- ]);
40
-
41
- // Simple in-memory cache for domain lookups
42
- const domainCache = new Map<string, { orgSlug: string; projectSlug: string } | null>();
43
- const CACHE_TTL = 60 * 1000; // 1 minute
44
- const cacheTimestamps = new Map<string, number>();
45
-
46
33
  function generateRequestId(): string {
47
34
  return crypto.randomUUID();
48
35
  }
@@ -86,44 +73,6 @@ function isAuthenticated(req: NextRequest): boolean {
86
73
  return testAuth === '1';
87
74
  }
88
75
 
89
- async function lookupCustomDomain(hostname: string, requestUrl: string): Promise<{ orgSlug: string; projectSlug: string } | null> {
90
- const now = Date.now();
91
- const cachedResult = domainCache.get(hostname);
92
- const cacheTime = cacheTimestamps.get(hostname);
93
-
94
- if (cachedResult !== undefined && cacheTime && now - cacheTime < CACHE_TTL) {
95
- return cachedResult;
96
- }
97
-
98
- try {
99
- const lookupUrl = new URL("/api/internal/domain-lookup", requestUrl);
100
- lookupUrl.searchParams.set("domain", hostname);
101
-
102
- const response = await fetch(lookupUrl, {
103
- headers: {
104
- "x-middleware-secret": process.env.MIDDLEWARE_SECRET || "",
105
- },
106
- });
107
-
108
- if (response.ok) {
109
- const data = await response.json();
110
- if (data.orgSlug && data.projectSlug) {
111
- const result = { orgSlug: data.orgSlug, projectSlug: data.projectSlug };
112
- domainCache.set(hostname, result);
113
- cacheTimestamps.set(hostname, now);
114
- return result;
115
- }
116
- }
117
-
118
- domainCache.set(hostname, null);
119
- cacheTimestamps.set(hostname, now);
120
- } catch (error) {
121
- console.error("Domain lookup failed:", error);
122
- }
123
-
124
- return null;
125
- }
126
-
127
76
  export async function proxy(req: NextRequest) {
128
77
  const startTime = Date.now();
129
78
  const reqId = req.headers.get("x-request-id") || generateRequestId();
@@ -135,29 +84,6 @@ export async function proxy(req: NextRequest) {
135
84
  console.log(`[${reqId}] ${req.method} ${req.nextUrl.pathname}`);
136
85
 
137
86
  const pathname = req.nextUrl.pathname;
138
- const hostname = req.headers.get("host") || "";
139
- const hostnameWithoutPort = hostname.split(":")[0];
140
-
141
- // Custom domain routing - check if this is a custom domain request
142
- if (!APP_HOSTS.has(hostname) && !APP_HOSTS.has(hostnameWithoutPort)) {
143
- // Skip API routes and static assets
144
- if (!pathname.startsWith("/api/") && !pathname.startsWith("/_next/") && !pathname.includes(".")) {
145
- const domainInfo = await lookupCustomDomain(hostname, req.url);
146
- if (domainInfo) {
147
- const url = req.nextUrl.clone();
148
- url.pathname = `/portal/${domainInfo.orgSlug}/${domainInfo.projectSlug}${pathname === "/" ? "" : pathname}`;
149
-
150
- const response = NextResponse.rewrite(url, {
151
- request: { headers: requestHeaders },
152
- });
153
- maybeSetLocaleCookie(req, response, pathname);
154
- response.headers.set("x-request-id", reqId);
155
- const duration = Date.now() - startTime;
156
- console.log(`[${reqId}] ${response.status} ${duration}ms (rewrite)`);
157
- return response;
158
- }
159
- }
160
- }
161
87
 
162
88
  const isPublic = isRouteMatch(pathname, publicRoutes);
163
89
  const isProtected = isRouteMatch(pathname, protectedRoutes);
@@ -1,67 +0,0 @@
1
- /*
2
- * Copyright (c) 2026 Echo Team
3
- *
4
- * This program is free software: you can redistribute it and/or modify
5
- * it under the terms of the GNU Affero General Public License as published by
6
- * the Free Software Foundation, either version 3 of the License, or
7
- * (at your option) any later version.
8
- *
9
- * This program is distributed in the hope that it will be useful,
10
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- * GNU Affero General Public License for more details.
13
- *
14
- * You should have received a copy of the GNU Affero General Public License
15
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
- */
17
-
18
- import { NextRequest, NextResponse } from "next/server";
19
- import { db } from "@/lib/db";
20
- import { organizationSettings, organizations } from "@/lib/db/schema";
21
- import { eq } from "drizzle-orm";
22
-
23
- /**
24
- * Internal API for domain lookup (used by middleware)
25
- * GET /api/internal/domain-lookup?domain=feedback.acme.com
26
- */
27
- export async function GET(req: NextRequest) {
28
- // Verify middleware secret to prevent external access
29
- const secret = req.headers.get("x-middleware-secret");
30
- if (secret !== process.env.MIDDLEWARE_SECRET) {
31
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
32
- }
33
-
34
- const domain = req.nextUrl.searchParams.get("domain");
35
- if (!domain) {
36
- return NextResponse.json({ error: "Domain required" }, { status: 400 });
37
- }
38
-
39
- if (!db) {
40
- return NextResponse.json({ error: "Database not configured" }, { status: 500 });
41
- }
42
-
43
- try {
44
- const [organization] = await db
45
- .select({
46
- organizationId: organizations.id,
47
- orgSlug: organizations.slug,
48
- })
49
- .from(organizationSettings)
50
- .innerJoin(organizations, eq(organizationSettings.organizationId, organizations.id))
51
- .where(eq(organizationSettings.customDomain, domain))
52
- .limit(1);
53
-
54
- if (!organization) {
55
- return NextResponse.json({ found: false });
56
- }
57
-
58
- return NextResponse.json({
59
- found: true,
60
- orgSlug: organization.orgSlug,
61
- organizationId: organization.organizationId,
62
- });
63
- } catch (error) {
64
- console.error("Domain lookup error:", error);
65
- return NextResponse.json({ error: "Lookup failed" }, { status: 500 });
66
- }
67
- }