Skip to content
Merged
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
89 changes: 88 additions & 1 deletion src/common/KakaoShareButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@ type KakaoShareButtonProps = {
offsetBottomPx?: number;
};

// Kakao SDK 로더
const loadKakao = (key: string) =>
new Promise<void>((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const w = window as any;
if (w.Kakao) {
if (!w.Kakao.isInitialized()) w.Kakao.init(key);
resolve();
return;
}
const s = document.createElement('script');
s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js';
s.integrity =
'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm';
s.crossOrigin = 'anonymous';
Comment on lines +21 to +24
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

SDK 버전/무결성(SRI) 해시 최신 여부 확인

고정 버전(2.7.6)과 SRI 해시가 실제 배포 파일과 불일치하면 로드가 실패합니다. 최신 권장 버전과 해시를 확인해 주세요. 또한 변경 시 반드시 해시도 같이 갱신해야 합니다.


🌐 Web query:

Kakao JavaScript SDK의 최신 안정 버전은 무엇인가요? 또한 https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js 의 공식 SRI 해시가 무엇인지 확인해 주세요.

💡 Result:

요약:

  • 최신 안정 버전: 2.7.8 (배포일: 2025-09-03). [1]
  • https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js 의 공식 SRI 값은 카카오 개발자 문서의 다운로드 페이지에서 제공되지만(각 버전 항목의 "Copy Integrity Value"), 제가 해당 CDN 파일이나 페이지의 복사 버튼을 직접 조회하지 못해 현재 공식 SRI 해시를 바로 가져오지 못했습니다. 다운로드 페이지에서 2.7.6 항목의 "Copy Integrity Value"를 클릭하면 integrity 속성 값을 복사할 수 있습니다. [1]

원문 참조:

  1. Kakao Developers — JavaScript SDK 다운로드 (버전 목록 및 "Copy Integrity Value" 안내). [https://developers.kakao.com/docs/latest/ko/javascript/download] [https://developers.kakao.com/docs/latest/en/javascript/download]

🏁 Script executed:

#!/bin/bash
# Fetch Kakao JS SDK v2.7.8 and compute its official sha384 SRI hash
curl -s https://t1.kakaocdn.net/kakao_js_sdk/2.7.8/kakao.min.js \
  | openssl dgst -sha384 -binary \
  | openssl base64 -A \
  | sed 's/^/sha384-/'

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Python으로 Kakao JS SDK v2.7.6 및 v2.7.8의 sha384 SRI 해시 계산
python3 - << 'EOF'
import hashlib, base64, urllib.request

def calc_sri(version):
    url = f'https://t1.kakaocdn.net/kakao_js_sdk/{version}/kakao.min.js'
    data = urllib.request.urlopen(url).read()
    h = hashlib.sha384(data).digest()
    print(version, 'sha384-' + base64.b64encode(h).decode())

for v in ['2.7.6', '2.7.8']:
    calc_sri(v)
EOF

Length of output: 2620


🏁 Script executed:

#!/bin/bash
# Python으로 SSL 인증 무시하고 Kakao JS SDK v2.7.6 및 v2.7.8의 sha384 SRI 해시 계산
python3 - << 'EOF'
import hashlib, base64, urllib.request, ssl

ctx = ssl._create_unverified_context()

def calc_sri(version):
    url = f'https://t1.kakaocdn.net/kakao_js_sdk/{version}/kakao.min.js'
    data = urllib.request.urlopen(url, context=ctx).read()
    h = hashlib.sha384(data).digest()
    print(version, 'sha384-' + base64.b64encode(h).decode())

for v in ['2.7.6', '2.7.8']:
    calc_sri(v)
EOF

Length of output: 155


Kakao JS SDK를 v2.7.8로 업데이트하고 SRI 해시도 갱신하세요
현재 2.7.6(sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm)을 사용 중인데, 최신 안정 버전은 2.7.8(2025-09-03 배포)이므로 아래와 같이 변경해야 로드 실패를 방지할 수 있습니다.

- s.src       = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js';
- s.integrity = 'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm';
+ s.src       = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.8/kakao.min.js';
+ s.integrity = 'sha384-WUSirVbD0ASvo37f3qQZuDap8wy76aJjmGyXKOYgPL/NdAs8HhgmPlk9dz2XQsNv';

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/common/KakaoShareButton.tsx around lines 21–24, the included Kakao JS SDK
version and its SRI hash are outdated (2.7.6); update the script src to v2.7.8
and replace the integrity attribute with the official sha384 SRI for the 2.7.8
release (fetch the exact hash from the provider/CDN or the Kakao release notes),
keeping crossOrigin='anonymous' intact so the script loads with the correct
subresource integrity.

s.onload = () => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).Kakao.init(key);
resolve();
} catch (e) {
reject(e);
}
};
s.onerror = reject;
document.head.appendChild(s);
});
Comment on lines +10 to +36
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

SDK 중복 로딩/경합 방지: script ID로 디듀플하고 기존 스크립트에 리스너만 부착하세요

빠른 연속 클릭 시 window.Kakao가 생성되기 전까지 <script>가 중복 삽입될 수 있습니다. 스크립트에 고정 ID를 부여하고, 존재 시에는 기존 태그의 load/error에만 리스너를 추가해 디듀플링하세요.

적용 diff:

-    const s = document.createElement('script');
-    s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js';
-    s.integrity =
-      'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm';
-    s.crossOrigin = 'anonymous';
-    s.onload = () => {
-      try {
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        (window as any).Kakao.init(key);
-        resolve();
-      } catch (e) {
-        reject(e);
-      }
-    };
-    s.onerror = reject;
-    document.head.appendChild(s);
+    const existing = document.getElementById('kakao-sdk') as HTMLScriptElement | null;
+    if (existing) {
+      existing.addEventListener('load', () => {
+        try {
+          // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        (window as any).Kakao.init(key);
+          resolve();
+        } catch (e) {
+          reject(e);
+        }
+      }, { once: true });
+      existing.addEventListener('error', reject, { once: true });
+      return;
+    }
+    const s = document.createElement('script');
+    s.id = 'kakao-sdk';
+    s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js';
+    s.integrity =
+      'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm';
+    s.crossOrigin = 'anonymous';
+    s.onload = () => {
+      try {
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        (window as any).Kakao.init(key);
+        resolve();
+      } catch (e) {
+        reject(e);
+      }
+    };
+    s.onerror = reject;
+    document.head.appendChild(s);
📝 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
// Kakao SDK 로더
const loadKakao = (key: string) =>
new Promise<void>((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const w = window as any;
if (w.Kakao) {
if (!w.Kakao.isInitialized()) w.Kakao.init(key);
resolve();
return;
}
const s = document.createElement('script');
s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js';
s.integrity =
'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm';
s.crossOrigin = 'anonymous';
s.onload = () => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).Kakao.init(key);
resolve();
} catch (e) {
reject(e);
}
};
s.onerror = reject;
document.head.appendChild(s);
});
// Kakao SDK 로더
const loadKakao = (key: string) =>
new Promise<void>((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const w = window as any;
if (w.Kakao) {
if (!w.Kakao.isInitialized()) w.Kakao.init(key);
resolve();
return;
}
const existing = document.getElementById('kakao-sdk') as HTMLScriptElement | null;
if (existing) {
existing.addEventListener(
'load',
() => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).Kakao.init(key);
resolve();
} catch (e) {
reject(e);
}
},
{ once: true }
);
existing.addEventListener('error', reject, { once: true });
return;
}
const s = document.createElement('script');
s.id = 'kakao-sdk';
s.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.6/kakao.min.js';
s.integrity =
'sha384-WAtVcQYcmTO/N+C1N+1m6Gp8qxh+3NlnP7X1U7qP6P5dQY/MsRBNTh+e1ahJrkEm';
s.crossOrigin = 'anonymous';
s.onload = () => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).Kakao.init(key);
resolve();
} catch (e) {
reject(e);
}
};
s.onerror = reject;
document.head.appendChild(s);
});
🤖 Prompt for AI Agents
In src/common/KakaoShareButton.tsx around lines 10 to 36, the loader can append
duplicate <script> tags on rapid calls causing race conditions; add a fixed id
to the script, check document.getElementById(id) first, and if the element
exists attach onload/onerror listeners to that existing element
(resolving/rejecting appropriately) instead of creating a new tag; if it doesn't
exist create the script with that id, attach listeners, append once, and ensure
window.Kakao is only initialized once (guard with isInitialized) and that all
callers get resolved/rejected from the same event handlers.


// 328 width container 기준: 좌우 padding 18px, 상하 18px, 세로 여백 90px
const KakaoShareButton = ({
onClick,
Expand All @@ -26,7 +54,66 @@ const KakaoShareButton = ({
return (
<button
type="button"
onClick={onClick}
onClick={async () => {
if (onClick) return onClick();
const jsKey = import.meta.env.VITE_KAKAO_JS_KEY as string;
if (!jsKey) {
console.warn('VITE_KAKAO_JS_KEY is not set.');
return;
}
await loadKakao(jsKey);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Kakao = (window as any).Kakao;
const templateId = Number(import.meta.env.VITE_KAKAO_TEMPLATE_ID);
const shareUrl = 'https://www.dodream.site/';
try {
if (templateId) {
Kakao.Share.sendCustom({
templateId,
templateArgs: { url: shareUrl },
});
} else {
Kakao.Share.sendDefault({
objectType: 'feed',
content: {
title: '두드림 – 내게 딱 맞는 직업 찾기',
description: '직업/학원/구직 정보를 한 곳에서!',
imageUrl: 'https://www.dodream.site/og-image.png',
link: { mobileWebUrl: shareUrl, webUrl: shareUrl },
},
buttons: [
{
title: '두드림 열기',
link: { mobileWebUrl: shareUrl, webUrl: shareUrl },
},
],
});
}
} catch (e) {
console.error('Kakao share failed', e);
// Fallback: Web Share API or clipboard copy
try {
if (navigator.share) {
await navigator.share({
title: '두드림 – 내게 딱 맞는 직업 찾기',
text: '직업/학원/구직 정보를 한 곳에서!',
url: shareUrl,
});
return;
}
} catch {
// ignore and continue to clipboard
}
try {
await navigator.clipboard.writeText(shareUrl);
alert(
'카카오 공유가 불가능하여 링크를 클립보드에 복사했습니다. 붙여넣기로 공유해 주세요.'
);
} catch (err) {
console.error('Clipboard write failed', err);
}
}
}}
className={`${layout} ${styles} ${className ?? ''}`}
style={fixed ? { bottom: bottomWithSafeArea } : undefined}
>
Expand Down