feat: 영수증 인스타그램 스토리 공유 추가 (웹브릿지 + 앱 핸들러) - #429
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 54 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (16)
📝 WalkthroughWalkthrough영수증 공유 다이얼로그에 Instagram Stories 공유를 추가했습니다. 웹은 WebBridge로 이미지를 앱에 전달하고, 앱은 iOS와 Android에서 Instagram Stories 편집 화면을 엽니다. 지원 버전과 설치 상태를 확인하며, 브라우저와 WebView 동작을 E2E 테스트로 검증합니다. ChangesInstagram Stories 공유
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ReceiptShareDialog
participant useInstagramStoryShare
participant WebBridge
participant handleShareInstagramStory
participant Instagram
ReceiptShareDialog->>useInstagramStoryShare: 영수증 Blob 전달
useInstagramStoryShare->>WebBridge: base64 공유 요청
WebBridge->>handleShareInstagramStory: 요청 payload 전달
handleShareInstagramStory->>Instagram: 이미지와 함께 Stories 편집 화면 실행
Instagram-->>handleShareInstagramStory: 실행 결과
handleShareInstagramStory-->>WebBridge: 공유 상태 응답
WebBridge-->>useInstagramStoryShare: requestId 기반 결과
useInstagramStoryShare-->>ReceiptShareDialog: 성공 또는 오류 상태
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
e4b26a7 to
2940950
Compare
2940950 to
5b169de
Compare
|
@coderabbitai full review |
✅ Action performedFull review finished. Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 48 minutes. |
✅ Action performedFull review finished. Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 46 minutes. |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
apps/app/utils/handleInstagramStory.ts (1)
1-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuetype-only import를 분리하고 trailing comma를 추가하십시오.
프로젝트 규칙은 type-only import에
import type선언을 사용하도록 요구합니다. 현재 declaration은 값과 타입을 혼합합니다. multiline type import의 마지막 항목에도 trailing comma를 추가하십시오.수정 예시
-import { - type ShareInstagramStoryPayloadT, - type ShareInstagramStoryStatusT, - WEBBRIDGE_MESSAGE_TYPE, -} from '`@piki/core`'; +import { WEBBRIDGE_MESSAGE_TYPE } from '`@piki/core`'; +import type { + ShareInstagramStoryPayloadT, + ShareInstagramStoryStatusT, +} from '`@piki/core`';As per coding guidelines, type-only imports must use
import type, and multiline imports must use ES5 trailing commas.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/app/utils/handleInstagramStory.ts` around lines 1 - 5, Update the imports in handleInstagramStory.ts to place ShareInstagramStoryPayloadT and ShareInstagramStoryStatusT in a separate import type declaration, keep WEBBRIDGE_MESSAGE_TYPE in the value import, and add trailing commas to the multiline import lists.Source: Coding guidelines
apps/app/plugins/withInstagramQueries.js (1)
9-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value콜백 파라미터 이름이 바깥 파라미터를 가립니다.
바깥 화살표 함수의
config와withAndroidManifest콜백의config가 같은 이름입니다. 동작은 정상이지만, 어떤config를 읽는지 혼동됩니다. 콜백 파라미터를modConfig로 바꾸면 의도가 명확해집니다.♻️ 제안 변경
const withInstagramQueries = config => - withAndroidManifest(config, config => { - const manifest = config.modResults.manifest; + withAndroidManifest(config, modConfig => { + const manifest = modConfig.modResults.manifest;그리고 26행의
return config;를return modConfig;로 변경하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/app/plugins/withInstagramQueries.js` around lines 9 - 10, Rename the inner callback parameter of withAndroidManifest in withInstagramQueries from config to modConfig, and update its corresponding return config statement to return modConfig.apps/web/e2e/specs/tournament/tournamentResult.spec.ts (1)
89-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win브릿지 메시지 타입을 상수로 주입하세요.
94행과 99행은 와이어 값을 문자열로 직접 적었습니다. 71-72행은 게이트 값을 상수에서 읽는데, 메시지 타입만 하드코딩입니다.
WEBBRIDGE_MESSAGE_TYPE의 값이 바뀌면 mock이 early return 합니다. 그러면 응답이 오지 않고 15초 타임아웃 뒤 다른 토스트가 뜹니다. 실패 원인 파악이 어려워집니다.
page.addInitScript는 두 번째 인자로 직렬화 가능한 값을 전달할 수 있습니다. 상수를 인자로 넘기세요.♻️ 제안 변경
- await page.addInitScript(() => { + await page.addInitScript( + ({ reqType, resType }) => { Object.defineProperty(window, 'ReactNativeWebView', { value: { postMessage: (raw: string) => { const message = JSON.parse(raw); - if (message.type !== 'WEB_REQ_SHARE_INSTAGRAM_STORY') return; + if (message.type !== reqType) return; window.dispatchEvent( new MessageEvent('message', { data: JSON.stringify({ - type: 'APP_RES_SHARE_INSTAGRAM_STORY', + type: resType, payload: { requestId: message.payload.requestId, status: 'notInstalled' }, }), }) ); }, }, }); - }); + }, + { + reqType: WEBBRIDGE_MESSAGE_TYPE.WEB_REQ_SHARE_INSTAGRAM_STORY, + resType: WEBBRIDGE_MESSAGE_TYPE.APP_RES_SHARE_INSTAGRAM_STORY, + } + );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/e2e/specs/tournament/tournamentResult.spec.ts` around lines 89 - 107, Update the page.addInitScript mock to receive WEBBRIDGE_MESSAGE_TYPE through its serializable argument instead of hardcoding the bridge message strings. Use the injected constant for both the request-type early-return check and the response message type, preserving the existing requestId and notInstalled payload behavior.apps/web/src/app/tournament/[id]/result/_components/receipt-share-dialog/ReceiptShareDialog.tsx (1)
84-88: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value
useSyncExternalStore인자를 모듈 스코프로 올리세요.
subscribe와getSnapshot인자를 렌더마다 새로 만듭니다. React는subscribe참조가 바뀔 때마다 재구독합니다. 동작은 정상이지만 불필요한 작업이 반복됩니다. 두 함수를 모듈 스코프 상수로 정의하면 참조가 고정됩니다. 이 패턴은 다른 화면에서도 쓸 수 있으므로 공용 훅으로 빼는 방법도 있습니다.♻️ 제안 변경
+const subscribeNoop = () => () => {}; +const getIsWebviewSnapshot = () => isWebview(); +const getIsWebviewServerSnapshot = () => false; + function ReceiptShareDialog({- const isAppEnvironment = useSyncExternalStore( - () => () => {}, - () => isWebview(), - () => false - ); + const isAppEnvironment = useSyncExternalStore( + subscribeNoop, + getIsWebviewSnapshot, + getIsWebviewServerSnapshot + );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/app/tournament/`[id]/result/_components/receipt-share-dialog/ReceiptShareDialog.tsx around lines 84 - 88, Move the subscribe and getSnapshot callbacks used by isAppEnvironment in useSyncExternalStore to module-scope constants, keeping the existing server snapshot behavior unchanged. Update the hook call to reuse those stable references on every render; do not alter the isWebview detection behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/hooks/useInstagramStoryShare.ts`:
- Around line 65-70: Move requestId generation and setIsSharing(true) inside the
try block of shareToStory so failures from crypto.randomUUID() are handled by
the existing error/finally flow. Preserve the hook’s non-throwing contract and
ensure isSharing is reset when any setup or sharing operation fails.
---
Nitpick comments:
In `@apps/app/plugins/withInstagramQueries.js`:
- Around line 9-10: Rename the inner callback parameter of withAndroidManifest
in withInstagramQueries from config to modConfig, and update its corresponding
return config statement to return modConfig.
In `@apps/app/utils/handleInstagramStory.ts`:
- Around line 1-5: Update the imports in handleInstagramStory.ts to place
ShareInstagramStoryPayloadT and ShareInstagramStoryStatusT in a separate import
type declaration, keep WEBBRIDGE_MESSAGE_TYPE in the value import, and add
trailing commas to the multiline import lists.
In `@apps/web/e2e/specs/tournament/tournamentResult.spec.ts`:
- Around line 89-107: Update the page.addInitScript mock to receive
WEBBRIDGE_MESSAGE_TYPE through its serializable argument instead of hardcoding
the bridge message strings. Use the injected constant for both the request-type
early-return check and the response message type, preserving the existing
requestId and notInstalled payload behavior.
In
`@apps/web/src/app/tournament/`[id]/result/_components/receipt-share-dialog/ReceiptShareDialog.tsx:
- Around line 84-88: Move the subscribe and getSnapshot callbacks used by
isAppEnvironment in useSyncExternalStore to module-scope constants, keeping the
existing server snapshot behavior unchanged. Update the hook call to reuse those
stable references on every render; do not alter the isWebview detection
behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7311e4ee-229c-48c4-8da5-168507e97620
⛔ Files ignored due to path filters (2)
apps/web/src/assets/icons/social/instagram.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (16)
apps/app/app.jsonapps/app/app/index.tsxapps/app/package.jsonapps/app/plugins/withInstagramQueries.jsapps/app/plugins/withShareExtensionKeychain.jsapps/app/utils/handleInstagramStory.tsapps/web/e2e/specs/tournament/tournamentResult.spec.tsapps/web/src/app/tournament/[id]/result/_components/receipt-share-dialog/ReceiptShareCaptureLayer.tsxapps/web/src/app/tournament/[id]/result/_components/receipt-share-dialog/ReceiptShareDialog.tsxapps/web/src/hooks/useInstagramStoryShare.tsapps/web/src/utils/handleImage.tspackages/core/src/consts/appVersion.tspackages/core/src/consts/webBridge.tspackages/core/src/index.tspackages/core/src/types/instagramStory.tspackages/core/src/types/webBridge.ts
WEB_REQ_SHARE_INSTAGRAM_STORY / APP_RES_SHARE_INSTAGRAM_STORY 를 정의하고 BRIDGE_GATE 에 등록한다. 미설치(notInstalled) 를 별도 상태로 내려받아 웹에서 설치 안내 토스트를 띄울 수 있게 했다.
- iOS: pasteboard 에 이미지 등록 후 instagram-stories://share 오픈 - Android: 캐시 파일의 content:// URI 를 ADD_TO_STORY 인텐트로 전달 - 미설치 시 notInstalled 로 응답해 웹이 설치 안내 토스트를 띄우게 함 - iOS LSApplicationQueriesSchemes, Android queries 에 인스타그램 등록
앱 환경에서만 노출하고, 앱 응답에 따라 미설치·실패를 토스트로 안내한다. 인스타그램 아이콘은 벡터로 새로 작성했다.
@expo/plist 0.4.8 이 export 를 default 아래로 옮기면서 plist.parse 가 undefined 가 돼 prebuild 가 실패했다.
전달받은 SVG 는 마스크만 벡터고 내부는 2497x2497 PNG(10.9MB) 라 사용할 수 없어, 로고 패스만 추출하고 시안에서 샘플링한 색으로 그라데이션을 재현했다 (3.4KB). 아이콘 자체에 색이 있어 배경은 다른 액션과 동일한 회색으로 통일.
- 웹 브라우저에서는 스토리 공유 버튼이 노출되지 않는지 검증 - 웹뷰 UA 에서는 버튼이 노출되고, notInstalled 응답 시 설치 안내 토스트를 띄우는지 검증
상품명 길이에 따라 영수증 높이가 달라져 위아래 여백이 들쭉날쭉했다. 영수증 영역을 고정 높이(748px)로 두고, 내용이 넘치면 zoom 을 낮춰 안에 맞춘다.
5b169de to
d873f4b
Compare
작업 요약
작업 세부 내용
스토리 공유는 딥링크 + pasteboard(iOS) / 파일 URI(Android)가 필요한 네이티브 동작이라, 웹뷰 → 앱 브릿지로 이미지를 넘기고 결과만 돌려받는 구조로 구현했습니다.
1. 웹브릿지 메시지 (
packages/core)WEB_REQ_SHARE_INSTAGRAM_STORY/APP_RES_SHARE_INSTAGRAM_STORY신규 추가requestId+ base64 본문 + mimeType (dataURL prefix 제외)success | notInstalled | error로 정의 — 미설치를 별도 상태로 내려받아 웹에서 설치 안내 토스트를 띄웁니다BRIDGE_GATE에 신규 2개 등록 (기존 항목 변경 없음). 사용자가 직접 누르는 동작이라notifyOnBlock: true2. 네이티브 핸들러 (
apps/app)instagram-stories://share오픈 (인스타그램이 pasteboard 에서 읽어가므로 딥링크보다 먼저 복사)content://URI →ADD_TO_STORY인텐트notInstalled로 응답LSApplicationQueriesSchemes에instagram-stories추가<queries>주입 config plugin 신규 (withInstagramQueries.js) — Android 11+ 는 선언하지 않은 패키지가 보이지 않아canOpenURL이 항상 false 가 됩니다expo-file-system/expo-intent-launcher/expo-clipboard추가3. 웹 UI (
apps/web)useSyncExternalStore로 hydration mismatch 회피)notInstalled→인스타그램 앱을 설치하면 스토리에 공유할 수 있어요.error→스토리 공유에 실패했어요. 잠시 후 다시 시도해주세요.WebBridge가 이미 업데이트 안내를 띄우므로 조용히 종료clamp()로 조정 (좁은 화면 대응)4. 아이콘
전달받은 SVG 가 마스크만 벡터고 내부는 2497x2497 PNG(10.9MB) 여서 그대로 쓸 수 없었습니다. 로고 패스만 추출하고 시안에서 샘플링한 색으로 그라데이션을 재현했습니다.
5. 영수증 이미지 흰 박스 크기 고정
상품명 길이에 따라 영수증 높이가 달라져 위아래 여백이 들쭉날쭉했습니다. 영수증 영역을 고정 높이(748px)로 두고, 내용이 넘치면
zoom을 낮춰 안에 맞추도록 했습니다.검증
리뷰 포인트
minAppVersion값 확정이 필요합니다. 새 메시지를BRIDGE_GATE에 등록하면서'1.1.2'로 넣어뒀는데, 앱 핸들러가 실제 포함되는 릴리즈 버전을 아직 알 수 없어 추정한 값입니다. 릴리즈 후git tag --contains <sha>로 확인해 교체해야 합니다.@expo/plist수정이 함께 들어있습니다. 인스타그램 기능과 무관한 별개 버그로, 이게 없으면 현재 dev 에서도 iOS prebuild 가plist.parse is not a function으로 실패합니다.스크린샷
_talkv_dJMcblSTtl0_BKmqonkq3rSIuKmKeBJyrK_talkv_high.mov
연관 이슈
closes #419
Summary by CodeRabbit
새로운 기능
개선
테스트