Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion components/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client'

import type { AI } from '@/lib/chat/actions'

import { cn } from '@/lib/utils'
import { ChatList } from '@/components/chat-list'
import { ChatPanel } from '@/components/chat-panel'
Expand All @@ -24,7 +26,7 @@ export function Chat({ id, className, session, missingKeys }: ChatProps) {
const path = usePathname()
const [input, setInput] = useState('')
const [messages] = useUIState()
const [aiState] = useAIState()
const [aiState] = useAIState<typeof AI>()

const [_, setNewChatId] = useLocalStorage('newChatId', id)

Expand All @@ -40,6 +42,8 @@ export function Chat({ id, className, session, missingKeys }: ChatProps) {
const messagesLength = aiState.messages?.length
if (messagesLength === 2) {
router.refresh()
} else if (messagesLength === 3 && aiState.messages[2].role === 'tool') {
router.refresh()
}
}, [aiState.messages, router])

Expand Down
59 changes: 53 additions & 6 deletions components/stocks/stock-purchase.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
'use client'

import { useId, useState } from 'react'
import { useEffect, useId, useState } from 'react';
import { useActions, useAIState, useUIState } from 'ai/rsc'
import { formatNumber } from '@/lib/utils'
import { formatNumber, unixTsNow } from '@/lib/utils'

import type { AI } from '@/lib/chat/actions'

interface Purchase {
numberOfShares?: number
symbol: string
price: number
toolCallId: string
status: 'requires_action' | 'completed' | 'expired'
}

export function Purchase({
props: { numberOfShares, symbol, price, status = 'expired' }
props: { numberOfShares, symbol, price, toolCallId, status = 'requires_action' }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Purchase Status

Default status is set to 'requires_action' but the actual status from props is ignored. This creates inconsistency between initial prop value and default parameter, potentially causing incorrect purchase state display.

Standards
  • Business-Rule-Validation
  • Logic-Verification-Consistency
  • Algorithm-Correctness-State-Management

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purchase Status Inconsistency

Default status 'requires_action' overrides actual status passed from parent component. This forces all purchases to start in 'requires_action' state even when they should be 'completed' or 'expired', breaking status continuity.

Standards
  • Business-Rule-State-Management
  • Logic-Verification-Props-Consistency

}: {
props: Purchase
}) {
const [value, setValue] = useState(numberOfShares || 100)
const [purchaseStatus, setPurchaseStatus] = useState(status);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purchase Status Manipulation

Client-side purchase status state can be manipulated by attackers. Initial status from props can be overridden by malicious client-side code, potentially bypassing purchase controls.

Standards
  • CWE-642
  • OWASP-A01

const [purchasingUI, setPurchasingUI] = useState<null | React.ReactNode>(null)
const [aiState, setAIState] = useAIState<typeof AI>()
const [, setMessages] = useUIState<typeof AI>()
Expand Down Expand Up @@ -59,6 +61,51 @@ export function Purchase({
setAIState({ ...aiState, messages: [...aiState.messages, message] })
}

useEffect(() => {
const checkPurchaseStatus = () => {
if (purchaseStatus !== 'requires_action') {
return;
}
Comment on lines +65 to +68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Error Handling

The checkPurchaseStatus function lacks try/catch error handling. If any part of the function throws an exception, the interval will continue running but the status checking will stop working, potentially leaving purchases in a pending state.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Error-Handling

// check for purchase completion
// Find the tool message with the matching toolCallId
const toolMessage = aiState.messages.find(
message =>
message.role === 'tool' &&
message.content.some(part => part.toolCallId === toolCallId)
);
Comment on lines +64 to +75
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Error Handling

The code accesses message.content without checking if it's an array or if it exists. If message.content is undefined or not an array, this will cause a runtime error. This could lead to the purchase status check failing silently, leaving transactions in an inconsistent state.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Error-Handling


if (toolMessage) {
const toolMessageIndex = aiState.messages.indexOf(toolMessage);
// Check if the next message is a system message containing "purchased"
const nextMessage = aiState.messages[toolMessageIndex + 1];
if (
nextMessage?.role === 'system' &&
nextMessage.content.includes('purchased')
) {
setPurchaseStatus('completed');
} else {
// Check for expiration
const requestedAt = toolMessage.createdAt;
if (!requestedAt || unixTsNow() - requestedAt > 30) {
setPurchaseStatus('expired');
}
Comment on lines +89 to +91
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete Status Check

The expiration logic checks if createdAt is missing or exceeds 30 seconds, but doesn't validate the timestamp type or handle invalid timestamps. This could cause incorrect expiration behavior with malformed timestamp data.

Standards
  • Data-Validation-Completeness
  • Logic-Verification-Boundary-Conditions
  • Algorithm-Correctness-Input-Validation

Comment on lines +88 to +91
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Expiration Validation

Hardcoded 30-second expiration lacks unit specification (seconds/minutes). If seconds, short timeout could cause premature expiration. If minutes, long timeout increases attack window.

Standards
  • CWE-613
  • OWASP-A02

}
}
Comment on lines +65 to +93

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic within checkPurchaseStatus could benefit from additional comments to clarify the order of operations and the conditions under which the purchase status is updated. This would improve readability and maintainability.

Comment on lines +71 to +93
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purchase Status Validation

The purchase status validation relies on string matching 'purchased' in message content. An attacker could potentially craft a message containing this string to falsely indicate purchase completion.

Standards
  • CWE-20
  • OWASP-A03

};
checkPurchaseStatus();

let intervalId: NodeJS.Timeout | null = null;
if (purchaseStatus === 'requires_action') {
intervalId = setInterval(checkPurchaseStatus, 5000);
Comment on lines +97 to +99
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purchase Status Leak

The interval is created but might not be cleared if the component unmounts while purchaseStatus is still 'requires_action'. This can cause memory leaks and continued execution of the checkPurchaseStatus function after component unmount.

Standards
  • ISO-IEC-25010-Reliability-Resource-Utilization
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Resource-Management

}

return () => {
if (intervalId) {
clearInterval(intervalId);
}
};
Comment on lines +97 to +106
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup Missing

The cleanup function correctly clears the interval, but there's no handling for errors that might occur during checkPurchaseStatus execution. If an error occurs in the interval callback, it could cause unhandled promise rejections and leave the component in an inconsistent state.

Standards
  • ISO-IEC-25010-Reliability-Recoverability
  • SRE-Error-Handling

}, [purchaseStatus, toolCallId, aiState.messages]);
Comment on lines +64 to +107

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The useEffect hook depends on aiState.messages. If setPurchaseStatus triggers a re-render and subsequently modifies aiState.messages (directly or indirectly), it could lead to an infinite loop. While the current logic seems to prevent this, it's a pattern that warrants careful consideration. Consider using a more stable dependency or a ref to store the previous state.

Comment on lines +64 to +107
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purchase Status Race

Purchase status expiration uses fixed 30-second timeout without configuration. Long-running operations may incorrectly expire. System reliability depends on arbitrary timeout value.

Standards
  • ISO-IEC-25010-Reliability-Maturity
  • ISO-IEC-25010-Functional-Correctness-Appropriateness


return (
<div className="p-4 text-green-400 border rounded-xl bg-zinc-950">
<div className="inline-block float-right px-2 py-1 text-xs rounded-full bg-white/10">
Expand All @@ -68,7 +115,7 @@ export function Purchase({
<div className="text-3xl font-bold">${price}</div>
{purchasingUI ? (
<div className="mt-4 text-zinc-200">{purchasingUI}</div>
) : status === 'requires_action' ? (
) : purchaseStatus === 'requires_action' ? (
<>
<div className="relative pb-6 mt-6">
<p>Shares to purchase</p>
Expand Down Expand Up @@ -133,12 +180,12 @@ export function Purchase({
Purchase
</button>
</>
) : status === 'completed' ? (
) : purchaseStatus === 'completed' ? (
<p className="mb-2 text-white">
You have successfully purchased {value} ${symbol}. Total cost:{' '}
{formatNumber(value * price)}
</p>
) : status === 'expired' ? (
) : purchaseStatus === 'expired' ? (
<p className="mb-2 text-white">Your checkout session has expired!</p>
) : null}
</div>
Expand Down
89 changes: 53 additions & 36 deletions lib/chat/actions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'server-only'

import type { MutableAIState } from '@/lib/types'
import {
createAI,
createStreamableUI,
Expand Down Expand Up @@ -29,7 +30,8 @@ import {
formatNumber,
runAsyncFnWithoutBlocking,
sleep,
nanoid
nanoid,
unixTsNow
} from '@/lib/utils'
import { saveChat } from '@/app/actions'
import { SpinnerMessage, UserMessage } from '@/components/stocks/message'
Expand Down Expand Up @@ -82,7 +84,7 @@ async function confirmPurchase(symbol: string, price: number, amount: number) {
</SystemMessage>
)

aiState.done({
aiStateDone(aiState, {
...aiState.get(),
messages: [
...aiState.get().messages,
Expand Down Expand Up @@ -159,7 +161,7 @@ async function submitUserMessage(content: string) {

if (done) {
textStream.done()
aiState.done({
aiStateDone(aiState, {
...aiState.get(),
messages: [
...aiState.get().messages,
Expand Down Expand Up @@ -199,7 +201,7 @@ async function submitUserMessage(content: string) {

const toolCallId = nanoid()

aiState.done({
aiStateDone(aiState, {
...aiState.get(),
messages: [
...aiState.get().messages,
Expand Down Expand Up @@ -259,8 +261,7 @@ async function submitUserMessage(content: string) {
await sleep(1000)

const toolCallId = nanoid()

aiState.done({
aiStateDone(aiState, {
...aiState.get(),
messages: [
...aiState.get().messages,
Expand Down Expand Up @@ -319,7 +320,7 @@ async function submitUserMessage(content: string) {
const toolCallId = nanoid()

if (numberOfShares <= 0 || numberOfShares > 1000) {
aiState.done({
aiStateDone(aiState, {
...aiState.get(),
messages: [
...aiState.get().messages,
Expand Down Expand Up @@ -362,7 +363,7 @@ async function submitUserMessage(content: string) {

return <BotMessage content={'Invalid amount'} />
} else {
aiState.done({
aiStateDone(aiState, {
...aiState.get(),
messages: [
...aiState.get().messages,
Expand All @@ -381,6 +382,7 @@ async function submitUserMessage(content: string) {
{
id: nanoid(),
role: 'tool',
createdAt: unixTsNow(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timestamp Inconsistency

Inconsistent timestamp format between message createdAt (Unix timestamp) and chat createdAt (Date object). This creates confusion and potential bugs when processing timestamps. Consider standardizing timestamp formats across the application.

Standards
  • Clean-Code-Consistency

content: [
{
type: 'tool-result',
Expand All @@ -404,6 +406,7 @@ async function submitUserMessage(content: string) {
numberOfShares,
symbol,
price: +price,
toolCallId,
status: 'requires_action'
}}
/>
Expand Down Expand Up @@ -437,7 +440,7 @@ async function submitUserMessage(content: string) {

const toolCallId = nanoid()

aiState.done({
aiStateDone(aiState, {
...aiState.get(),
messages: [
...aiState.get().messages,
Expand Down Expand Up @@ -517,36 +520,45 @@ export const AI = createAI<AIState, UIState>({
return
}
},
onSetAIState: async ({ state }) => {
'use server'
})

const session = await auth()
const updateChat = async (state: AIState) => {
'use server'

if (session && session.user) {
const { chatId, messages } = state

const createdAt = new Date()
const userId = session.user.id as string
const path = `/chat/${chatId}`

const firstMessageContent = messages[0].content as string
const title = firstMessageContent.substring(0, 100)

const chat: Chat = {
id: chatId,
title,
userId,
createdAt,
messages,
path
}
const session = await auth()

await saveChat(chat)
} else {
return
if (session && session.user) {
const { chatId, messages } = state

const createdAt = new Date()
const userId = session.user.id as string
const path = `/chat/${chatId}`

const firstMessageContent = messages[0].content as string
const title = firstMessageContent.substring(0, 100)

const chat: Chat = {
id: chatId,
title,
userId,
createdAt,
messages,
path
}

await saveChat(chat)
Comment on lines +525 to +549
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

updateChat omits the chatId field and is fragile when the first message is non-text

  1. The Chat object you persist contains id but not chatId, whereas downstream code (see getUIStateFromAIState) relies on chatId. When the chat is read back from the DB the field will be missing and aiState.chatId will be undefined, breaking UI-key generation and router paths.

  2. title is produced via messages[0].content.substring(...).
    • If messages is empty this will throw.
    • For tool calls or any structured message, content is an array, not a string – calling substring will crash.

A robust fix:

-  const firstMessageContent = messages[0].content as string
-  const title = firstMessageContent.substring(0, 100)
+  const firstContent =
+    typeof messages[0]?.content === 'string' ? messages[0].content : ''
+  const title = firstContent.slice(0, 100)
   const chat: Chat = {
+    chatId,               // keep in sync with UI layer
     id: chatId,
@@
     path
   }
🧰 Tools
🪛 Biome (1.9.4)

[error] 530-530: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

} else {
return
}
Comment on lines +525 to 552
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract Chat Persistence

The updateChat function duplicates logic from the original onSetAIState method. This violates DRY principle and creates maintenance burden when chat persistence logic changes. Consider refactoring to reuse the existing saveChat functionality.

Standards
  • Clean-Code-DRY
  • SOLID-SRP

})
};

const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
await updateChat(newState);
aiState.done(newState);
});
Comment on lines +556 to +560
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Error Propagation

aiStateDone doesn't handle database update failures. If updateChat fails, aiState.done still executes, potentially causing state inconsistency between UI and database.

Suggested change
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
await updateChat(newState);
aiState.done(newState);
});
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
try {
// resolves race condition in aiState.done - the UI refreshed before db was updated
await updateChat(newState);
aiState.done(newState);
} catch (error) {
console.error('Failed to update chat before state update:', error);
// Still update UI state to prevent UI freezing, but log the inconsistency
aiState.done(newState);
}
});
};
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness

};
Comment on lines +555 to +561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

aiStateDone needs error handling & preserves race-condition fix

Good idea to gate aiState.done behind updateChat, but if updateChat throws, aiState.done is silently skipped and UI never updates. Wrap the await in try / catch / finally so the UI is never blocked by a persistence failure while still logging the error:

-runAsyncFnWithoutBlocking(async () => {
-  await updateChat(newState);
-  aiState.done(newState);
-});
+runAsyncFnWithoutBlocking(async () => {
+  try {
+    await updateChat(newState);
+  } catch (err) {
+    console.error('[aiStateDone] failed to persist chat', err);
+    // TODO: surface non-fatal toast to the user if desired
+  } finally {
+    aiState.done(newState);
+  }
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
await updateChat(newState);
aiState.done(newState);
});
};
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
try {
await updateChat(newState);
} catch (err) {
console.error('[aiStateDone] failed to persist chat', err);
// TODO: surface non-fatal toast to the user if desired
} finally {
aiState.done(newState);
}
});
};

Comment on lines +555 to +561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race Condition Fix Needs Error Handling

Race condition fix properly sequences database updates before UI state changes, but lacks error handling. If updateChat fails, the UI state may not be updated, potentially causing inconsistency between database and UI state.

Suggested change
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
await updateChat(newState);
aiState.done(newState);
});
};
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
try {
// resolves race condition in aiState.done - the UI refreshed before db was updated
await updateChat(newState);
aiState.done(newState);
} catch (error) {
console.error('Failed to update chat:', error);
// Still update UI state to prevent blocking the interface
aiState.done(newState);
}
});
};
Standards
  • CWE-362
  • OWASP-A01

Comment on lines +555 to +561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race Condition Fix

This code correctly resolves a race condition where UI refreshes before database updates complete. However, the implementation has a potential performance issue - if updateChat() fails, aiState.done() never executes, potentially leaving the UI in a loading state indefinitely. Adding error handling would ensure UI responsiveness even during database failures.

Suggested change
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
await updateChat(newState);
aiState.done(newState);
});
};
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
try {
await updateChat(newState);
} catch (error) {
console.error('Failed to update chat:', error);
} finally {
aiState.done(newState);
}
});
};
Standards
  • ISO-IEC-25010-Performance-Time-Behaviour
  • Netflix-Error-Handling

Comment on lines +555 to +561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race Condition Fix Needs Error Handling

The aiStateDone function correctly addresses a race condition but introduces a potential reliability issue. If updateChat fails, aiState.done will still be called, potentially causing state inconsistency between UI and database. This could lead to data loss or incorrect UI state.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Error-Handling

Comment on lines +556 to +561
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Race Condition

While this code aims to fix a race condition, it introduces a new one. If multiple aiStateDone calls occur in quick succession, they may execute concurrently, potentially causing database consistency issues. Consider adding a mechanism to queue or debounce these operations.

Standards
  • ISO-IEC-25010-Reliability-Maturity
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Concurrency-Management


export const getUIStateFromAIState = (aiState: Chat) => {
return aiState.messages
Expand All @@ -569,8 +581,13 @@ export const getUIStateFromAIState = (aiState: Chat) => {
</BotCard>
) : tool.toolName === 'showStockPurchase' ? (
<BotCard>
{/* @ts-expect-error */}
<Purchase props={tool.result} />
<Purchase
// @ts-expect-error
props={{
...(tool.result as object),
toolCallId: tool.toolCallId,
}}
/>
</BotCard>
) : tool.toolName === 'getEvents' ? (
<BotCard>
Expand Down
7 changes: 7 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CoreMessage } from 'ai'

export type Message = CoreMessage & {
id: string
createdAt?: number;
}

export interface Chat extends Record<string, any> {
Expand Down Expand Up @@ -39,3 +40,9 @@ export interface User extends Record<string, any> {
password: string
salt: string
}

export type MutableAIState<AIState> = {
get: () => AIState;
update: (newState: AIState | ((current: AIState) => AIState)) => void;
done: ((newState: AIState) => void) | (() => void);
};
8 changes: 6 additions & 2 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ export const formatNumber = (value: number) =>
export const runAsyncFnWithoutBlocking = (
fn: (...args: any) => Promise<any>
) => {
fn()
}
fn().catch(error => {
console.error('An error occurred in the async function:', error);
});
Comment on lines +54 to +56

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This catch block logs errors that occur within the async function. This is good for debugging. Consider adding more context to the error message, such as the function name or arguments.

Suggested change
fn().catch(error => {
console.error('An error occurred in the async function:', error);
});
fn().catch(error => {
console.error(`An error occurred in the async function ${fn.name}:`, error);
});

Comment on lines +54 to +56
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error Handling Gap

Error is caught but only logged, without recovery mechanism. Unhandled errors in async functions could lead to incomplete operations without proper recovery path.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • SRE-Error-Handling

};
Comment on lines +54 to +57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unhandled Promise Rejection

Error is logged but not propagated. System continues execution after async failure without proper recovery. Could lead to inconsistent state and silent failures.

Suggested change
fn().catch(error => {
console.error('An error occurred in the async function:', error);
});
};
fn().catch(error => {
console.error('An error occurred in the async function:', error);
// Notify application about failure for potential recovery
window.dispatchEvent(new CustomEvent('async-error', { detail: error }));
});
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Error-Handling


export const sleep = (ms: number) =>
new Promise(resolve => setTimeout(resolve, ms))
Expand Down Expand Up @@ -87,3 +89,5 @@ export const getMessageFromCode = (resultCode: string) => {
return 'Logged in!'
}
}

export const unixTsNow = () => Math.floor(Date.now() / 1000);