Skip to content

Commit d761856

Browse files
author
teycir
committed
feat: add newPulseToken to API and enhance error handling with logging
- Add newPulseToken field to the pulse API response for better token management - Import and integrate ErrorLogger for structured error logging in create seal flow - Enhance error response type to include debug details and log errors with component context - Add error details state and current token tracking in pulse page for improved error reporting and token consistency - Use currentToken instead of re-decoding params.token to avoid redundant operations These changes improve debugging capabilities and API consistency by providing more detailed error information and a new token field.
1 parent 8d0d562 commit d761856

8 files changed

Lines changed: 238 additions & 24 deletions

File tree

app/api/pulse/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export async function POST(request: NextRequest) {
1818
return jsonResponse({
1919
success: true,
2020
newUnlockTime: result.newUnlockTime,
21+
newPulseToken: result.newPulseToken,
2122
message: 'Pulse updated successfully',
2223
});
2324
}, { rateLimit: { limit: 20, window: 60000 } })(request);

app/page.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { motion, AnimatePresence } from 'framer-motion';
99
import { useDropzone } from 'react-dropzone';
1010
import confetti from 'canvas-confetti';
1111
import dynamic from 'next/dynamic';
12+
import { ErrorLogger } from '@/lib/errorLogger';
1213

1314
const Turnstile = dynamic(() => import('@marsidev/react-turnstile').then(mod => mod.Turnstile), { ssr: false });
1415

@@ -390,7 +391,7 @@ export default function HomePage() {
390391
});
391392
setEncryptionProgress(90);
392393

393-
const data = await response.json() as { success: boolean; publicUrl: string; pulseToken?: string; receipt?: any; error?: string | { code: string; message: string } };
394+
const data = await response.json() as { success: boolean; publicUrl: string; pulseToken?: string; receipt?: any; error?: string | { code: string; message: string; details?: string; debugInfo?: any } };
394395

395396
console.log('[CREATE-SEAL] Response:', response.status, data);
396397

@@ -417,19 +418,34 @@ export default function HomePage() {
417418
toast.dismiss(loadingToast);
418419
// Handle both string and nested error object formats
419420
let errorMsg = 'Failed to create seal';
421+
let debugInfo = null;
420422
if (data.error) {
421423
if (typeof data.error === 'string') {
422424
errorMsg = data.error;
423-
} else if (typeof data.error === 'object' && data.error.message) {
424-
errorMsg = data.error.message;
425+
} else if (typeof data.error === 'object') {
426+
errorMsg = data.error.message || errorMsg;
427+
debugInfo = {
428+
code: data.error.code,
429+
details: data.error.details,
430+
debugInfo: data.error.debugInfo,
431+
status: response.status
432+
};
425433
}
426434
}
435+
console.error('[CREATE-SEAL] Error:', { errorMsg, debugInfo, fullResponse: data });
436+
ErrorLogger.log(data.error, { component: 'CreateSeal', action: 'create', debugInfo });
427437
toast.error(errorMsg);
438+
if (debugInfo) {
439+
console.error('[CREATE-SEAL] Debug Info:', debugInfo);
440+
}
428441
}
429442
} catch (error) {
430443
toast.dismiss(loadingToast);
431444
console.error('[CREATE-SEAL] Error:', error);
432445
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
446+
const errorStack = error instanceof Error ? error.stack : undefined;
447+
ErrorLogger.log(error, { component: 'CreateSeal', action: 'create', stack: errorStack });
448+
console.error('[CREATE-SEAL] Stack:', errorStack);
433449
toast.error(`Failed to create seal: ${errorMessage}`);
434450
} finally {
435451
setIsCreating(false);

app/pulse/[token]/page.tsx

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ export default function PulsePage({ params }: { params: { token: string } }) {
1111
"loading" | "confirm" | "success" | "error"
1212
>("loading");
1313
const [message, setMessage] = useState("");
14+
const [errorDetails, setErrorDetails] = useState<any>(null);
1415
const [sealInfo, setSealInfo] = useState<any>(null);
1516
const [pulseInterval, setPulseInterval] = useState(10);
1617
const [pulseUnit, setPulseUnit] = useState<"minutes" | "days">("days");
1718
const [isUpdating, setIsUpdating] = useState(false);
19+
const [currentToken, setCurrentToken] = useState("");
1820

1921
useEffect(() => {
2022
const fetchSealInfo = async () => {
2123
try {
2224
const token = decodeURIComponent(params.token);
25+
setCurrentToken(token);
2326
const sealId = token.split(":")[0];
2427

2528
const res = await fetch(`/api/seal/${sealId}`);
@@ -41,6 +44,7 @@ export default function PulsePage({ params }: { params: { token: string } }) {
4144
} catch (err) {
4245
setStatus("error");
4346
setMessage("Failed to load seal information");
47+
setErrorDetails({ error: err instanceof Error ? err.message : String(err) });
4448
}
4549
};
4650

@@ -55,7 +59,7 @@ export default function PulsePage({ params }: { params: { token: string } }) {
5559
method: "POST",
5660
headers: { "Content-Type": "application/json" },
5761
body: JSON.stringify({
58-
pulseToken: decodeURIComponent(params.token),
62+
pulseToken: currentToken,
5963
}),
6064
});
6165
const data = await res.json();
@@ -70,14 +74,15 @@ export default function PulsePage({ params }: { params: { token: string } }) {
7074
? data.error
7175
: data.error?.message || "Failed to unlock seal";
7276
setMessage(errorMsg);
77+
setErrorDetails({ status: res.status, data });
7378
toast.error("Unlock failed");
7479
}
7580
} else {
7681
const res = await fetch("/api/pulse", {
7782
method: "POST",
7883
headers: { "Content-Type": "application/json" },
7984
body: JSON.stringify({
80-
pulseToken: decodeURIComponent(params.token),
85+
pulseToken: currentToken,
8186
newInterval:
8287
pulseUnit === "minutes"
8388
? pulseInterval / (24 * 60)
@@ -86,6 +91,9 @@ export default function PulsePage({ params }: { params: { token: string } }) {
8691
});
8792
const data = await res.json();
8893
if (res.ok) {
94+
if (data.newPulseToken) {
95+
setCurrentToken(data.newPulseToken);
96+
}
8997
setStatus("success");
9098
setMessage(data.message || "Pulse renewed successfully");
9199
toast.success("Pulse renewed!");
@@ -96,12 +104,14 @@ export default function PulsePage({ params }: { params: { token: string } }) {
96104
? data.error
97105
: data.error?.message || "Failed to renew pulse";
98106
setMessage(errorMsg);
107+
setErrorDetails({ status: res.status, data });
99108
toast.error("Renewal failed");
100109
}
101110
}
102111
} catch (err) {
103112
setStatus("error");
104113
setMessage("Network error occurred");
114+
setErrorDetails({ error: err instanceof Error ? err.message : String(err), stack: err instanceof Error ? err.stack : undefined });
105115
toast.error("Network error");
106116
} finally {
107117
setIsUpdating(false);
@@ -216,9 +226,17 @@ export default function PulsePage({ params }: { params: { token: string } }) {
216226
Next pulse required in {pulseInterval} {pulseUnit}
217227
</p>
218228
</Card>
219-
<a href="/" className="cyber-button inline-block">
220-
RETURN HOME
221-
</a>
229+
<div className="flex gap-3 mb-4">
230+
<button
231+
onClick={() => setStatus("confirm")}
232+
className="cyber-button flex-1"
233+
>
234+
PULSE AGAIN
235+
</button>
236+
<a href="/" className="cyber-button flex-1 bg-neon-green/10">
237+
RETURN HOME
238+
</a>
239+
</div>
222240
</>
223241
)}
224242

@@ -229,11 +247,29 @@ export default function PulsePage({ params }: { params: { token: string } }) {
229247
PULSE FAILED
230248
</h1>
231249
<Card className="mb-8 border-red-500/30">
232-
<p className="text-red-400/90 font-mono">{message}</p>
250+
<p className="text-red-400/90 font-mono mb-4">{message}</p>
251+
{errorDetails && (
252+
<details className="text-left">
253+
<summary className="text-red-400/60 text-xs cursor-pointer hover:text-red-400/80 mb-2">
254+
Debug Info (click to expand)
255+
</summary>
256+
<pre className="text-red-400/70 text-xs bg-black/30 p-3 rounded overflow-x-auto">
257+
{JSON.stringify(errorDetails, null, 2)}
258+
</pre>
259+
</details>
260+
)}
233261
</Card>
234-
<a href="/" className="cyber-button inline-block">
235-
RETURN HOME
236-
</a>
262+
<div className="flex gap-3 mb-4">
263+
<button
264+
onClick={() => { setStatus("confirm"); setErrorDetails(null); }}
265+
className="cyber-button flex-1"
266+
>
267+
TRY AGAIN
268+
</button>
269+
<a href="/" className="cyber-button flex-1 bg-neon-green/10">
270+
RETURN HOME
271+
</a>
272+
</div>
237273
</>
238274
)}
239275
</div>

app/v/[id]/page.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { BackgroundBeams } from '../../components/ui/background-beams';
99
import { Card } from '../../components/Card';
1010
import { toast } from 'sonner';
1111
import { Lock, AlertTriangle, Hourglass, Copy, Download } from 'lucide-react';
12+
import { ErrorLogger } from '@/lib/errorLogger';
1213

1314
interface SealStatus {
1415
id: string;
@@ -29,6 +30,7 @@ function VaultPageClient({ id }: { id: string }) {
2930
const [status, setStatus] = useState<SealStatus | null>(null);
3031
const [decryptedContent, setDecryptedContent] = useState<string | null>(null);
3132
const [error, setError] = useState<string | null>(null);
33+
const [errorDetails, setErrorDetails] = useState<any>(null);
3234
const [timeLeft, setTimeLeft] = useState<number>(0);
3335

3436
useEffect(() => {
@@ -69,6 +71,7 @@ function VaultPageClient({ id }: { id: string }) {
6971
const keyA = globalThis.window.location.hash.substring(1);
7072
if (!keyA) {
7173
setError('Key A not found in URL. Invalid vault link.');
74+
setErrorDetails({ reason: 'missing_key_a', url: globalThis.window.location.href });
7275
return;
7376
}
7477

@@ -82,6 +85,7 @@ function VaultPageClient({ id }: { id: string }) {
8285

8386
if (!blobData) {
8487
setError('Encrypted content not found');
88+
setErrorDetails({ reason: 'missing_blob', sealId: id });
8589
return;
8690
}
8791

@@ -98,20 +102,26 @@ function VaultPageClient({ id }: { id: string }) {
98102
try {
99103
const content = new TextDecoder('utf-8', { fatal: true }).decode(decrypted);
100104
setDecryptedContent(content);
101-
} catch {
105+
} catch (decodeErr) {
106+
console.error('[VAULT] UTF-8 decode failed:', decodeErr);
107+
ErrorLogger.log(decodeErr, { component: 'Vault', action: 'utf8_decode', sealId: id });
102108
setError('Decryption succeeded but content is corrupted');
109+
setErrorDetails({ reason: 'utf8_decode_failed', error: decodeErr instanceof Error ? decodeErr.message : String(decodeErr) });
103110
}
104111
} catch (err) {
105112
console.error('[VAULT] Decryption failed:', err);
106113
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
114+
const errorStack = err instanceof Error ? err.stack : undefined;
115+
ErrorLogger.log(err, { component: 'Vault', action: 'decrypt', sealId: id });
107116
setError(`Failed to decrypt message: ${errorMessage}`);
117+
setErrorDetails({ reason: 'decryption_failed', error: errorMessage, stack: errorStack, sealId: id });
108118
}
109119
}, [id]);
110120

111121
const fetchSealStatus = useCallback(async () => {
112122
try {
113123
const response = await fetch(`/api/seal/${id}`);
114-
const data = await response.json() as SealStatus & { encryptedBlob?: string; error?: string | { code: string; message: string } };
124+
const data = await response.json() as SealStatus & { encryptedBlob?: string; error?: string | { code: string; message: string; details?: string; debugInfo?: any } };
115125

116126
console.log('[VAULT] API Response:', response.status, data);
117127

@@ -125,20 +135,32 @@ function VaultPageClient({ id }: { id: string }) {
125135
} else {
126136
// Handle both string and nested error object formats
127137
let errorMsg = 'Seal not found';
138+
let debugInfo = null;
128139
if (data.error) {
129140
if (typeof data.error === 'string') {
130141
errorMsg = data.error;
131-
} else if (typeof data.error === 'object' && data.error.message) {
132-
errorMsg = data.error.message;
142+
} else if (typeof data.error === 'object') {
143+
errorMsg = data.error.message || errorMsg;
144+
debugInfo = {
145+
code: data.error.code,
146+
details: data.error.details,
147+
debugInfo: data.error.debugInfo,
148+
status: response.status
149+
};
133150
}
134151
}
135-
console.error('[VAULT] Error:', errorMsg, data);
152+
console.error('[VAULT] Error:', errorMsg, debugInfo, data);
153+
ErrorLogger.log(data.error, { component: 'Vault', action: 'fetchStatus', sealId: id, debugInfo });
136154
setError(errorMsg);
155+
setErrorDetails(debugInfo);
137156
}
138157
} catch (err) {
139158
console.error('[VAULT] Fetch failed:', err);
140159
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
160+
const errorStack = err instanceof Error ? err.stack : undefined;
161+
ErrorLogger.log(err, { component: 'Vault', action: 'fetchStatus', sealId: id });
141162
setError(`Failed to fetch seal: ${errorMessage}`);
163+
setErrorDetails({ reason: 'fetch_failed', error: errorMessage, stack: errorStack, sealId: id });
142164
}
143165
}, [id, decryptMessage]);
144166

@@ -192,7 +214,17 @@ function VaultPageClient({ id }: { id: string }) {
192214
<AlertTriangle className="w-16 h-16 text-red-500 mx-auto mb-6" />
193215
<h1 className="text-2xl sm:text-3xl font-bold mb-4 glow-text text-red-500 px-2">VAULT ERROR</h1>
194216
<Card className="mb-8 border-red-500/30">
195-
<p className="text-red-400/90 font-mono">{error}</p>
217+
<p className="text-red-400/90 font-mono mb-4">{error}</p>
218+
{errorDetails && (
219+
<details className="text-left">
220+
<summary className="text-red-400/60 text-xs cursor-pointer hover:text-red-400/80 mb-2">
221+
Debug Info (click to expand)
222+
</summary>
223+
<pre className="text-red-400/70 text-xs bg-black/30 p-3 rounded overflow-x-auto">
224+
{JSON.stringify(errorDetails, null, 2)}
225+
</pre>
226+
</details>
227+
)}
196228
</Card>
197229
<a href="/" className="cyber-button inline-block hover:shadow-[0_0_30px_rgba(255,0,0,0.4)] hover:border-red-500/50">
198230
CREATE NEW SEAL

lib/apiHandler.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { withRateLimit } from './rateLimit';
33
import { handleError } from './errors';
44
import { logger } from './logger';
5+
import { ErrorLogger } from './errorLogger';
56

67
export interface HandlerContext {
78
request: Request;
@@ -57,10 +58,24 @@ export const withErrorHandling: Middleware = async (ctx, next) => {
5758
try {
5859
return await next(ctx);
5960
} catch (error) {
60-
logger.error('request_error', error as Error, {
61+
const errorDetails = {
6162
url: ctx.request.url,
63+
method: ctx.request.method,
6264
ip: ctx.ip,
65+
message: error instanceof Error ? error.message : String(error),
66+
stack: error instanceof Error ? error.stack : undefined,
67+
};
68+
69+
logger.error('request_error', error as Error, errorDetails);
70+
71+
// Enhanced error logging
72+
ErrorLogger.log(error, {
73+
component: 'API',
74+
action: `${ctx.request.method} ${new URL(ctx.request.url).pathname}`,
75+
ip: ctx.ip,
76+
url: ctx.request.url,
6377
});
78+
6479
return handleError(error);
6580
}
6681
};

0 commit comments

Comments
 (0)