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
20 changes: 6 additions & 14 deletions apps/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
import { analytics } from '@pinback/analytics';
import { router } from '@routes/router';
import { useGetAmplitudeUserProperties } from '@shared/apis/queries';
import { useEffect } from 'react';
import AmplitudeProvider from 'src/providers/AmplitudeProvider';
import { RouterProvider } from 'react-router-dom';
import './App.css';

function App() {
const { data: userProperties } = useGetAmplitudeUserProperties();

useEffect(() => {
if (!userProperties) return;
analytics.identify(
String(userProperties.userId),
userProperties.jobRole ? { job_role: userProperties.jobRole } : undefined
);
}, [userProperties]);

return <RouterProvider router={router} />;
return (
<AmplitudeProvider>
<RouterProvider router={router} />
</AmplitudeProvider>
);
}

export default App;
77 changes: 36 additions & 41 deletions apps/client/src/pages/onBoarding/GoogleCallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,76 +6,71 @@ import { useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';

const REDIRECT_URI = import.meta.env.VITE_GOOGLE_REDIRECT_URI;

Comment on lines +9 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ˆ„๋ฝ ์‹œ ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๋ณด๋‚ด์ง€ ์•Š๋„๋ก ๊ฐ€๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Line 45์—์„œ uri: REDIRECT_URI๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, REDIRECT_URI ๋ฏธ์„ค์ • ์‹œ undefined๋กœ ์š”์ฒญ์ด ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ๋ฐฐํฌ/ํ™˜๊ฒฝ์„ค์ • ์‹ค์ˆ˜์—์„œ ์ „๋ฉด ๋กœ๊ทธ์ธ ์‹คํŒจ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์–ด ์‚ฌ์ „ ์ฐจ๋‹จ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ˆ˜์ • ์ œ์•ˆ diff
 const GoogleCallback = () => {
@@
   const loginWithCode = async (code: string) => {
+    if (!REDIRECT_URI) {
+      console.error('VITE_GOOGLE_REDIRECT_URI is not configured');
+      alert('๋กœ๊ทธ์ธ ์„ค์ • ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.');
+      navigate('/onboarding?step=SOCIAL_LOGIN');
+      return;
+    }
+
     try {
       const res = await apiRequest.post(
         '/api/v3/auth/google',
         { code, uri: REDIRECT_URI },
         { withCredentials: true }
       );

Also applies to: 45-47

๐Ÿค– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/client/src/pages/onBoarding/GoogleCallback.tsx` around lines 9 - 10, The
component uses REDIRECT_URI (const REDIRECT_URI =
import.meta.env.VITE_GOOGLE_REDIRECT_URI) directly when building the login
request (the code that sets uri: REDIRECT_URI around the GoogleCallback logic),
so add a guard that checks if REDIRECT_URI is a non-empty string before sending
the request; if missing, log an error (or call the app's error handler), show a
user-friendly message and return early to avoid making the request. Update the
block that constructs/sends the request (the place that sets uri: REDIRECT_URI)
to validate REDIRECT_URI and abort the operation when undefined/empty.

const GoogleCallback = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const queryClient = useQueryClient();

useEffect(() => {
const code = searchParams.get('code');
const saveSession = (params: {
accessToken: string | null;
refreshToken: string | null;
email: string;
userId: string;
hasJob?: boolean;
}) => {
const { accessToken, refreshToken, email, userId, hasJob } = params;

if (!code) {
alert('๋กœ๊ทธ์ธ ์‹คํŒจ. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.');
navigate('/onboarding?step=SOCIAL_LOGIN');
return;
authStorage.setUserIdentity(email, userId);

if (accessToken) {
authStorage.setAccessToken(accessToken);
extensionBridge.syncToken(accessToken);
}

loginWithCode(code);
}, []);
if (refreshToken) {
authStorage.setRefreshToken(refreshToken);
}

const handleUserLogin = (
isUser: boolean,
accessToken: string | null,
refreshToken: string | null,
hasJob?: boolean
) => {
if (isUser) {
if (accessToken) {
authStorage.setAccessToken(accessToken);
extensionBridge.syncToken(accessToken);
}

if (refreshToken) {
authStorage.setRefreshToken(refreshToken);
}

if (typeof hasJob === 'boolean') {
authStorage.setHasJob(hasJob);
}
navigate('/');
} else {
navigate('/onboarding?step=JOB');
if (typeof hasJob === 'boolean') {
authStorage.setHasJob(hasJob);
}
};

const redirectUri = import.meta.env.VITE_GOOGLE_REDIRECT_URI;

const loginWithCode = async (code: string) => {
try {
const res = await apiRequest.post(
'/api/v3/auth/google',
{
code,
uri: redirectUri,
},
{
withCredentials: true,
}
{ code, uri: REDIRECT_URI },
{ withCredentials: true }
);

const { isUser, userId, email, accessToken, refreshToken, hasJob } =
res.data.data;

authStorage.setUserIdentity(email, userId);
saveSession({ accessToken, refreshToken, email, userId, hasJob });
queryClient.invalidateQueries({ queryKey: ['amplitudeUserProperties'] });

handleUserLogin(isUser, accessToken, refreshToken, hasJob);
navigate(isUser ? '/' : '/onboarding?step=JOB');
} catch (error) {
console.error('๋กœ๊ทธ์ธ ์˜ค๋ฅ˜:', error);
navigate('/onboarding?step=SOCIAL_LOGIN');
}
};

useEffect(() => {
const code = searchParams.get('code');

if (!code) {
alert('๋กœ๊ทธ์ธ ์‹คํŒจ. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.');
navigate('/onboarding?step=SOCIAL_LOGIN');
return;
}

loginWithCode(code);
}, []);

return (
<div className="flex h-screen flex-col items-center justify-center">
<LoadingChippi className="mb-6" />
Expand Down
19 changes: 19 additions & 0 deletions apps/client/src/providers/AmplitudeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { analytics } from '@pinback/analytics';
import { useGetAmplitudeUserProperties } from '@shared/apis/queries';
import { useEffect, type PropsWithChildren } from 'react';

const AmplitudeProvider = ({ children }: PropsWithChildren) => {
const { data: userProperties } = useGetAmplitudeUserProperties();

useEffect(() => {
if (!userProperties) return;
analytics.identify(
String(userProperties.userId),
userProperties.jobRole ? { job_role: userProperties.jobRole } : undefined
);
Comment on lines +11 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

userId nullish ๊ฐ€๋“œ ์—†์ด ๋ฌธ์ž์—ด ๋ณ€ํ™˜ํ•˜๋ฉด ์ž˜๋ชป๋œ ์‹๋ณ„์ž๊ฐ€ ์ „์†ก๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Line 11์—์„œ String(...) ๊ฐ•์ œ ๋ณ€ํ™˜ ์‹œ "undefined" ๊ฐ™์€ ๊ฐ’์ด ์ „์†ก๋  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ตœ์†Œ nullish ์ฒดํฌ๋ฅผ ๋„ฃ๋Š” ํŽธ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

์ˆ˜์ • ์˜ˆ์‹œ
   useEffect(() => {
     if (!userProperties) return;
+    if (userProperties.userId == null) return;
     analytics.identify(
       String(userProperties.userId),
       userProperties.jobRole ? { job_role: userProperties.jobRole } : undefined
     );
   }, [userProperties]);
๐Ÿค– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/client/src/providers/AmplitudeProvider.tsx` around lines 11 - 13, The
code currently forces String(userProperties.userId) which will serialize
null/undefined to "null"/"undefined" and send invalid identifiers; update the
call that uses String(userProperties.userId) (the userId argument where
userProperties.userId is referenced) to first nullish-check
userProperties.userId and only convert when presentโ€”e.g., use
userProperties.userId != null ? String(userProperties.userId) : undefined (or
omit the argument) so no literal "undefined" is sent.

}, [userProperties]);

return <>{children}</>;
};

export default AmplitudeProvider;
1 change: 1 addition & 0 deletions apps/client/src/shared/apis/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export const useGetAmplitudeUserProperties =
queryFn: getAmplitudeUserProperties,
staleTime: Infinity,
retry: false,
enabled: authStorage.hasAccessToken(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash
# ๋ชฉ์ : ํ† ํฐ ์ €์žฅ/์„ธ์…˜ ์ดˆ๊ธฐํ™” ์‹œ amplitudeUserProperties ์ฟผ๋ฆฌ ์žฌ์‹คํ–‰ ๊ฒฝ๋กœ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
rg -n -C3 --type=ts --type=tsx "setAccessToken\(|clearSession\(|useGetAmplitudeUserProperties\(|amplitudeUserProperties|invalidateQueries\("

# ๋ชฉ์ : authStorage ๊ธฐ๋ฐ˜ enabled ํŒจํ„ด์ด ๋‹ค๋ฅธ ๊ณณ์—์„œ๋„ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉ๋˜๋Š”์ง€ ํ™•์ธ
rg -n -C2 --type=ts --type=tsx "enabled:\s*authStorage\."

Repository: Pinback-Team/pinback-client

Length of output: 126


๐Ÿ Script executed:

#!/bin/bash
# Search for patterns related to token storage, session clearing, and amplitude query
# Using correct rg syntax without problematic file type declarations

echo "=== Searching for setAccessToken, clearSession, and query invalidation patterns ==="
rg -n -C3 "setAccessToken|clearSession|amplitudeUserProperties|invalidateQueries" \
  --include="*.ts" --include="*.tsx"

echo ""
echo "=== Searching for enabled conditions with authStorage ==="
rg -n -C2 "enabled.*authStorage|authStorage.*enabled" \
  --include="*.ts" --include="*.tsx"

Repository: Pinback-Team/pinback-client

Length of output: 1114


๐Ÿ Script executed:

#!/bin/bash
# Find the queries.ts file and read around line 232
echo "=== Finding queries.ts file ==="
fd -t f "queries.ts"

echo ""
echo "=== Read queries.ts to see context around line 232 ==="
if [ -f "apps/client/src/shared/apis/queries.ts" ]; then
  cat -n "apps/client/src/shared/apis/queries.ts" | sed -n '200,270p'
fi

echo ""
echo "=== Search for amplitudeUserProperties and authStorage in all ts/tsx files ==="
cd apps/client/src && find . -name "*.ts" -o -name "*.tsx" | xargs grep -l "amplitudeUserProperties\|authStorage" 2>/dev/null | head -20

Repository: Pinback-Team/pinback-client

Length of output: 2509


๐Ÿ Script executed:

#!/bin/bash
echo "=== Read authStorage.ts to understand token storage ==="
cat -n "apps/client/src/shared/utils/authStorage.ts"

echo ""
echo "=== Search for setAccessToken in the codebase ==="
grep -rn "setAccessToken" apps/client/src --include="*.ts" --include="*.tsx" | head -30

echo ""
echo "=== Search for token invalidation or refetch patterns ==="
grep -rn "invalidateQueries\|refetch" apps/client/src --include="*.ts" --include="*.tsx" | grep -i "token\|auth\|amplitude" | head -20

echo ""
echo "=== Check GoogleCallback.tsx to see how amplitudeUserProperties is used ==="
cat -n "apps/client/src/pages/onBoarding/GoogleCallback.tsx"

Repository: Pinback-Team/pinback-client

Length of output: 4995


๐Ÿ Script executed:

#!/bin/bash
echo "=== Check token refresh flow in queries.ts ==="
cat -n "apps/client/src/shared/apis/queries.ts" | sed -n '80,120p'

echo ""
echo "=== Check axiosInstance.ts token setter context ==="
cat -n "apps/client/src/shared/apis/setting/axiosInstance.ts" | head -50

echo ""
echo "=== Search for other places where token is set or session changes ==="
grep -rn "setAccessToken\|clearSession" apps/client/src --include="*.ts" --include="*.tsx"

echo ""
echo "=== Check Layout.tsx for how it uses amplitudeUserProperties ==="
grep -n "amplitudeUserProperties\|useGetAmplitudeUserProperties" apps/client/src/layout/Layout.tsx -B3 -A3

Repository: Pinback-Team/pinback-client

Length of output: 3862


๐Ÿ Script executed:

#!/bin/bash
echo "=== Try reading Layout.tsx directly ==="
if [ -f "apps/client/src/layout/Layout.tsx" ]; then
  cat -n "apps/client/src/layout/Layout.tsx"
else
  echo "File not found, searching for Layout file..."
  find apps/client/src -name "Layout*" -type f
fi

echo ""
echo "=== Check if query is used anywhere with explicit invalidation ==="
grep -rn "invalidateQueries.*amplitudeUserProperties\|amplitudeUserProperties.*invalidateQueries" apps/client/src

echo ""
echo "=== Check full context of token refresh in axiosInstance ==="
cat -n "apps/client/src/shared/apis/setting/axiosInstance.ts" | sed -n '50,150p'

Repository: Pinback-Team/pinback-client

Length of output: 5453


enabled ํ•„๋“œ๊ฐ€ localStorage ์Šค๋ƒ…์ƒท์œผ๋กœ๋งŒ ํ‰๊ฐ€๋˜๋ฏ€๋กœ, ํ† ํฐ ๊ฐฑ์‹  ํ›„ ์ฟผ๋ฆฌ๊ฐ€ ์˜๊ตฌ ๋น„ํ™œ์„ฑํ™”๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

enabled: authStorage.hasAccessToken()๋Š” Hook ํ˜ธ์ถœ ์‹œ์ ์˜ ๋™๊ธฐ ์Šค๋ƒ…์ƒท์ด๋ฉฐ ๋ฐ˜์‘ํ˜•์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ๋‹ค์Œ ๊ฒฝ๋กœ์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค:

  1. ํ† ํฐ ๊ฐฑ์‹  ์‹œ (axiosInstance.ts ์‘๋‹ต ์ธํ„ฐ์…‰ํ„ฐ โ†’ syncAccessToken): ์ฟผ๋ฆฌ ๋ฌดํšจํ™” ์—†์Œ
  2. ํšŒ์›๊ฐ€์ž… ์™„๋ฃŒ ์‹œ (queries.ts usePostSignUp onSuccess โ†’ setAccessToken): ์ฟผ๋ฆฌ ๋ฌดํšจํ™” ์—†์Œ
  3. ๋กœ๊ทธ์ธ ์‹œ (GoogleCallback.tsx): ๋ช…์‹œ์  invalidateQueries ์žˆ์Œ โœ“

staleTime: Infinity์™€ ํ•จ๊ป˜, ํ† ํฐ์ด ์„ค์ •๋˜๋”๋ผ๋„ Hook์„ ํฌํ•จํ•œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์œผ๋ฉด enabled ์กฐ๊ฑด์ด ์žฌํ‰๊ฐ€๋˜์ง€ ์•Š์•„ identify ์‚ฌ์ด๋“œ์ดํŽ™ํŠธ๊ฐ€ ๋ˆ„๋ฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ† ํฐ ๊ฐฑ์‹  ๊ฒฝ๋กœ 2๊ณณ์—์„œ๋„ amplitudeUserProperties ์ฟผ๋ฆฌ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋ฌดํšจํ™”ํ•˜๊ฑฐ๋‚˜, enabled ์กฐ๊ฑด์„ ๋ฐ˜์‘ํ˜• ์ƒํƒœ์— ์—ฐ๋™ํ•ด์ฃผ์„ธ์š”.

๐Ÿค– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/client/src/shared/apis/queries.ts` at line 232, The enabled check for
the amplitudeUserProperties query uses a one-time snapshot
authStorage.hasAccessToken() (in queries.ts) which can remain false after token
refresh paths (axiosInstance.ts โ†’ syncAccessToken and usePostSignUp onSuccess โ†’
setAccessToken) and thus never re-enable identify; fix by either making the
enabled condition reactive (read from a reactive auth state/store or subscribe
to authStorage changes instead of calling hasAccessToken() once in useQuery) or
explicitly invalidate the amplitudeUserProperties query when tokens are
set/renewed (call queryClient.invalidateQueries for the amplitudeUserProperties
key from syncAccessToken and from usePostSignUp onSuccess where setAccessToken
is called) so the identify side-effect always runs after login/signup/token
refresh.

});
};

Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/shared/apis/setting/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ apiRequest.interceptors.response.use(

if (
error.response &&
(error.response.status === 401 || error.response.status === 403) &&
error.response.status === 401 &&
!originalRequest._retry &&
!isNoAuth &&
!isLoginPage
Expand Down
14 changes: 7 additions & 7 deletions packages/analytics/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { analytics } from './src/analytics';
import { amplitudeProvider } from './src/providers/amplitude';
import { consoleProvider } from './src/providers/console';
import { amplitudeAdapter } from './src/adapters/amplitude';
import { consoleAdapter } from './src/adapters/console';

export type { AnalyticsProvider, UserProperties } from './src/types';
export type * from './src/ampli';
Expand All @@ -13,16 +13,16 @@ interface InitAnalyticsOptions {

export const initAnalytics = ({ apiKey, isDev }: InitAnalyticsOptions): void => {
if (isDev || !apiKey) {
analytics.setProvider(consoleProvider);
analytics.setProvider(consoleAdapter);
return;
}

try {
amplitudeProvider.init(apiKey);
analytics.setProvider(amplitudeProvider);
amplitudeAdapter.init(apiKey);
analytics.setProvider(amplitudeAdapter);
} catch (error) {
Comment on lines +21 to 23
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash
set -euo pipefail

echo "== amplitude adapter ํŒŒ์ผ ์œ„์น˜ ๋ฐ init ์ •์˜ ํ™•์ธ =="
fd -i 'amplitude.ts' | while read -r f; do
  echo "-- $f"
  rg -n -C3 'init\s*[:(]|amplitudeAdapter' "$f"
done

echo
echo "== init ํ˜ธ์ถœ๋ถ€ ํ™•์ธ (await ์‚ฌ์šฉ ์—ฌ๋ถ€) =="
rg -n -C2 'amplitudeAdapter\.init\('

echo
echo "== Promise ๋ฐ˜ํ™˜ ํƒ€์ž… ํžŒํŠธ ๊ฒ€์ƒ‰ =="
rg -n -C2 'init\s*:\s*\(.*\)\s*=>\s*Promise|async\s+init\s*\('

Repository: Pinback-Team/pinback-client

Length of output: 712


๐Ÿ Script executed:

cat packages/analytics/src/adapters/amplitude.ts

Repository: Pinback-Team/pinback-client

Length of output: 1311


๐Ÿ Script executed:

cat packages/analytics/src/types.ts

Repository: Pinback-Team/pinback-client

Length of output: 353


๐Ÿ Script executed:

rg -A 5 'function initAll|export.*initAll' packages/analytics/

Repository: Pinback-Team/pinback-client

Length of output: 53


๐Ÿ Script executed:

# Check if node_modules or package.json has amplitude info
rg -A 3 '"@amplitude/unified"' packages/analytics/package.json

Repository: Pinback-Team/pinback-client

Length of output: 168


๐Ÿ Script executed:

# Search for any usage of initAll in the codebase to see if it's awaited elsewhere
rg -n 'initAll' packages/

Repository: Pinback-Team/pinback-client

Length of output: 277


๐Ÿ Script executed:

# Check the index.ts file context more broadly to understand error handling intent
cat -n packages/analytics/index.ts | head -30

Repository: Pinback-Team/pinback-client

Length of output: 1150


๐ŸŒ Web query:

@amplitude/unified v1.0.10 initAll function return type Promise async

๐Ÿ’ก Result:

The initAll function in @amplitude/unified v1.0.10 returns a Promise. It is an asynchronous function that resolves when the SDKs (Analytics, Experiment, Session Replay, etc.) are initialized. From the TypeScript definitions (unified.d.ts), the signature is: initAll(apiKey: string, unifiedOptions?: UnifiedOptions): Promise<void> (inferred void since no generic type specified). This is consistent across documented versions (1.0.1+), official Amplitude docs, and npm package files. Usage examples show it can be awaited: await initAll('API_KEY', options);

Citations:


๐ŸŒ Web query:

@amplitude/unified initAll documentation TypeScript types

๐Ÿ’ก Result:

The @amplitude/unified package provides the initAll function for initializing Amplitude's Unified SDK for web, which handles Analytics, Experiment, Session Replay, Guides & Surveys, and more in a single entry point. ## Installation npm install @amplitude/unified ## Import typescript import { initAll } from '@amplitude/unified'; ## Usage typescript initAll('YOUR_API_KEY', { // Shared options (optional) serverZone: 'US', // or 'EU' instanceName: 'my-instance', // Product-specific options analytics: { // Options from @amplitude/analytics-browser, e.g., { autocapture: true } }, sessionReplay: { // Options from @amplitude/plugin-session-replay-browser, e.g., { sampleRate: 1 } }, experiment: { // Options from @amplitude/plugin-experiment-browser, e.g., { deploymentKey: 'your-key' } }, engagement: { // Options for Guides & Surveys }, }); Returns Promise. After initialization, import and use features: typescript import { track, identify, experiment, sessionReplay } from '@amplitude/unified'; track('Button Clicked'); ## TypeScript Types From unified.d.ts: typescript interface UnifiedOptions { serverZone?: 'US' | 'EU'; instanceName?: string; analytics?: Omit<BrowserOptions, 'apiKey'>; // From @amplitude/analytics-browser sessionReplay?: /* Session Replay config /; experiment?: / Experiment config /; engagement?: / Engagement config /; } interface UnifiedClient { initAll(apiKey: string, unifiedOptions?: UnifiedOptions): Promise; // Plus methods like sessionReplay: AmplitudeSessionReplay, etc. } Full types are in the package's lib//unified.d.ts and generated docs at Amplitude-TypeScript GitHub. Analytics options reference Browser SDK 2 docs. Package latest versions from 2025-2026 (e.g., 1.0.14 as of Mar 2026).

Citations:


amplitudeAdapter.init์€ ๋น„๋™๊ธฐ ํ•จ์ˆ˜์ด๋ฉฐ, ํ˜„์žฌ ๊ตฌํ˜„์—์„œ Promise๊ฐ€ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

@amplitude/unified์˜ initAll์€ Promise<void>๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ amplitude.ts์˜ init ๋ฉ”์„œ๋“œ์—์„œ initAll ํ˜ธ์ถœ ์‹œ await๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„, Promise๊ฐ€ ๋Œ€๊ธฐ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด index.ts์˜ try/catch ๋ธ”๋ก์ด ๋น„๋™๊ธฐ ์—๋Ÿฌ๋ฅผ ํฌ์ฐฉํ•˜์ง€ ๋ชปํ•˜๊ณ , ์ดˆ๊ธฐํ™” ์‹คํŒจ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด provider๊ฐ€ ์„ค์ •๋ฉ๋‹ˆ๋‹ค(Line 22).

ํ•„์š”ํ•œ ์ˆ˜์ •:

  1. AnalyticsProvider ์ธํ„ฐํŽ˜์ด์Šค์˜ init ๋ฐ˜ํ™˜ ํƒ€์ž…์„ void์—์„œ Promise<void>๋กœ ๋ณ€๊ฒฝ
  2. amplitude.ts์˜ init ๋ฉ”์„œ๋“œ๋ฅผ ๋น„๋™๊ธฐ๋กœ ์ „ํ™˜: async init(apiKey: string) { await initAll(...) }
  3. index.ts์˜ ํ˜ธ์ถœ๋ถ€์— await ์ถ”๊ฐ€: await amplitudeAdapter.init(apiKey)
  4. initAnalytics ํ•จ์ˆ˜๋ฅผ ๋น„๋™๊ธฐ๋กœ ๋ณ€๊ฒฝ: async initAnalytics(...): Promise<void>
๐Ÿค– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/analytics/index.ts` around lines 21 - 23, The AnalyticsProvider init
signature should return Promise<void> (not void); update the interface
accordingly, make amplitude.ts's init method async and await the
`@amplitude/unified` initAll call (e.g., async init(apiKey: string) { await
initAll(...) }), then update index.ts to await amplitudeAdapter.init(apiKey) and
mark initAnalytics as async returning Promise<void> so the try/catch can catch
initialization errors before calling analytics.setProvider.

console.error('[Analytics] Failed to initialize Amplitude, falling back to console provider', error);
analytics.setProvider(consoleProvider);
console.error('[Analytics] Failed to initialize Amplitude, falling back to console adapter', error);
analytics.setProvider(consoleAdapter);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Identify, identify, setUserId, reset, track, initAll } from '@amplitude

import type { AnalyticsProvider, UserProperties } from '../types';

export const amplitudeProvider: AnalyticsProvider = {
export const amplitudeAdapter: AnalyticsProvider = {
init(apiKey: string) {
initAll(apiKey, {
analytics: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AnalyticsProvider } from '../types';

export const consoleProvider: AnalyticsProvider = {
export const consoleAdapter: AnalyticsProvider = {
init: (_apiKey) => console.log('[Analytics] init'),
track: (name, props) => console.log('[Analytics] track', name, props ?? {}),
identify: (userId, userProperties) => console.log('[Analytics] identify', { userId, ...userProperties }),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AnalyticsProvider } from '../types';

export const noopProvider: AnalyticsProvider = {
export const noopAdapter: AnalyticsProvider = {
init: () => {},
track: () => {},
identify: (_userId: string) => {},
Expand Down
4 changes: 2 additions & 2 deletions packages/analytics/src/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { noopProvider } from './providers/noop';
import { noopAdapter } from './adapters/noop';
import type { AnalyticsProvider, UserProperties } from './types';

let provider: AnalyticsProvider = noopProvider;
let provider: AnalyticsProvider = noopAdapter;

export const analytics = {
setProvider(newProvider: AnalyticsProvider): void {
Expand Down
Loading