fix: iOS 인스타그램 스토리 공유가 미지원 안내로 막힘 - #503
Conversation
일반 이미지 복사 + App ID 없는 딥링크로는 인스타그램이 요청을 거부한다. 안드로이드는 인텐트 경로라 영향이 없어 그동안 드러나지 않았다. - 전용 pasteboard 키(com.instagram.sharedSticker.backgroundImage)로 이미지를 넘기는 네이티브 모듈 추가 (expo-clipboard 로는 지정 불가) - Facebook App ID 를 Info.plist 와 딥링크에 주입 (2023-01 부터 필수) - App ID 는 app.json 플러그인 설정에 두고 런타임이 같은 값을 읽어 중복 제거
html-to-image 가 이미지를 다시 fetch 할 때 mode:'cors' 로 요청하는데 /_next/image 프록시 응답에는 CORS 헤더가 없어 브라우저가 거부했다. 브라우저 캐시에 우연히 맞는 항목이 있을 때만 보여 산발적으로 재현됐다. 동일 origin 이라 CORS 가 불필요하고, img 태그도 crossOrigin 없이 로드되므로 mode 를 빼면 같은 캐시 항목을 재사용한다.
앱 여부를 useSyncExternalStore 로만 판정해 SSR 스냅샷이 항상 false 였다. hydration 이 끝나야 true 가 되어, 시트가 열린 뒤 스토리 공유 버튼이 갑자기 추가되며 버튼 줄이 다시 배치됐다. 서버에서 User-Agent 로 판정해 내려주고, UA 가 없는 구버전 앱을 위해 클라 판정을 fallback 으로 남긴다.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Discord 스레드 연동용 메타데이터입니다. discord-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Discord 알림 연동이 끊깁니다. |
|
Warning Review limit reached
Next review available in: 48 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 selected for processing (4)
📝 WalkthroughWalkthroughiOS Instagram Story 공유를 네이티브 Expo 모듈 기반으로 변경했습니다. Facebook App ID와 전용 pasteboard를 사용합니다. 결과 화면은 서버에서 판정한 앱 여부를 공유 컴포넌트에 전달합니다. ChangesInstagram Story 공유
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant ResultPage
participant ResultClient
participant ReceiptShareDialog
participant handleInstagramStory
participant InstagramStoryModule
ResultPage->>ResultPage: user-agent로 isApp 판정
ResultPage->>ResultClient: isApp 전달
ResultClient->>ReceiptShareDialog: isApp 전달
ReceiptShareDialog->>handleInstagramStory: Instagram Story 공유 요청
handleInstagramStory->>InstagramStoryModule: base64 이미지와 Facebook App ID 전달
InstagramStoryModule->>InstagramStoryModule: 전용 pasteboard 저장 및 Stories URL 생성
InstagramStoryModule-->>handleInstagramStory: 공유 상태 반환
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/app/modules/instagram-story/src/index.ts`:
- Around line 23-26: Update the multiline parameter lists for
shareBackgroundImage and the similarly structured declaration around lines 38–41
by adding trailing commas after their final parameters, including facebookAppId,
to comply with the TypeScript style rule.
In `@apps/app/plugins/withInstagramStoryShare.js`:
- Line 1: Apply a file-level ESLint override to the CommonJS entry point using
Node globals and disabling `@typescript-eslint/no-require-imports`, while
preserving the existing withInfoPlist import and module.exports 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: 061d4faa-6536-4ec1-9408-1160ff7b652d
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
apps/app/app.jsonapps/app/modules/instagram-story/expo-module.config.jsonapps/app/modules/instagram-story/ios/InstagramStory.podspecapps/app/modules/instagram-story/ios/InstagramStoryModule.swiftapps/app/modules/instagram-story/package.jsonapps/app/modules/instagram-story/src/index.tsapps/app/package.jsonapps/app/plugins/withInstagramStoryShare.jsapps/app/utils/handleInstagramStory.tsapps/web/src/app/tournament/[id]/result/_components/ResultClient.tsxapps/web/src/app/tournament/[id]/result/_components/receipt-share-dialog/ReceiptShareDialog.tsxapps/web/src/app/tournament/[id]/result/_utils/shareReceiptImage.tsapps/web/src/app/tournament/[id]/result/page.tsxapps/web/src/utils/getIsApp.ts
| shareBackgroundImage: ( | ||
| base64: string, | ||
| facebookAppId: string | ||
| ) => Promise<ShareInstagramStoryStatusT>; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
멀티라인 매개변수에 후행 쉼표를 추가하세요.
Line 25와 Line 40의 마지막 매개변수 뒤에 ES5 후행 쉼표가 없습니다. TypeScript 스타일 규칙에 맞게 추가하세요.
수정 예시
shareBackgroundImage: (
base64: string,
- facebookAppId: string
+ facebookAppId: string,
) => Promise<ShareInstagramStoryStatusT>;
@@
export const shareInstagramStoryBackground = async (
base64: string,
- facebookAppId: string
+ facebookAppId: string,
): Promise<ShareInstagramStoryStatusT> => {Also applies to: 38-41
🤖 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/modules/instagram-story/src/index.ts` around lines 23 - 26, Update
the multiline parameter lists for shareBackgroundImage and the similarly
structured declaration around lines 38–41 by adding trailing commas after their
final parameters, including facebookAppId, to comply with the TypeScript style
rule.
Source: Coding guidelines
| @@ -0,0 +1,26 @@ | |||
| const { withInfoPlist } = require('@expo/config-plugins'); | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
pnpm install --frozen-lockfile
pnpm exec eslint apps/app/plugins/withInstagramStoryShare.jsRepository: TeamPiKi/client
Length of output: 2860
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- plugin ---'
cat -n apps/app/plugins/withInstagramStoryShare.js
printf '%s\n' '--- nearby config plugins ---'
find apps/app/plugins -maxdepth 1 -type f -print | sort | while read -r file; do
printf '\n### %s\n' "$file"
sed -n '1,12p' "$file"
done
printf '%s\n' '--- ESLint configuration references ---'
rg -n --hidden --glob '!node_modules/**' \
'no-require-imports|no-undef|eslint|withInfoPlist|module\.exports|export default' \
.eslintrc* eslint.config.* package.json apps/app/package.json apps/app/plugins 2>/dev/null || true
printf '%s\n' '--- package scripts ---'
node -e "const p=require('./package.json'); console.log(JSON.stringify(p.scripts,null,2))"Repository: TeamPiKi/client
Length of output: 5707
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- ESLint configuration ---'
cat -n eslint.config.mjs | sed -n '1,90p'
printf '%s\n' '--- app scripts and lint configuration ---'
node - <<'JS'
const root = require('./package.json');
const app = require('./apps/app/package.json');
console.log('root scripts:', JSON.stringify(root.scripts, null, 2));
console.log('app scripts:', JSON.stringify(app.scripts, null, 2));
JS
printf '%s\n' '--- effective rules for the plugin ---'
pnpm exec eslint --print-config apps/app/plugins/withInstagramStoryShare.js |
node -e "
let s=''; process.stdin.on('data',d=>s+=d).on('end',()=>{
const c=JSON.parse(s);
for (const k of ['no-undef','`@typescript-eslint/no-require-imports`']) {
console.log(k + ':', JSON.stringify(c.rules[k]));
}
});
"
printf '%s\n' '--- all CommonJS plugin entry points ---'
rg -n -U '^const .*require\\(|^module\\.exports' apps/app/pluginsRepository: TeamPiKi/client
Length of output: 3825
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- lint results for all Config Plugin files ---'
pnpm exec eslint apps/app/plugins --format stylish || true
printf '%s\n' '--- CommonJS entry points ---'
rg -n '^(const .*require\\(|module\\.exports)' apps/app/plugins
printf '%s\n' '--- Node globals available to ESLint ---'
node - <<'JS'
const globals = require('globals');
console.log(JSON.stringify({
require: globals.node.require,
module: globals.node.module,
}, null, 2));
JS
printf '%s\n' '--- Expo lint command resolution ---'
pnpm --filter piki-app exec expo lint --help 2>&1 | sed -n '1,80p' || trueRepository: TeamPiKi/client
Length of output: 2712
Config Plugin 파일에 CommonJS ESLint 예외를 적용하세요.
apps/app/plugins의 CommonJS 엔트리 포인트에서 require()와 module.exports가 lint 오류를 발생시킵니다. 해당 디렉터리에 Node 전역을 적용하고 @typescript-eslint/no-require-imports를 비활성화하세요.
🧰 Tools
🪛 ESLint
[error] 1-1: A require() style import is forbidden.
(@typescript-eslint/no-require-imports)
🤖 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/withInstagramStoryShare.js` at line 1, Apply a file-level
ESLint override to the CommonJS entry point using Node globals and disabling
`@typescript-eslint/no-require-imports`, while preserving the existing
withInfoPlist import and module.exports behavior.
Source: Linters/SAST tools
종이 폭은 그대로인데 내용이 시안보다 작았다. 시안 실측 기준 썸네일이 종이 폭의 23.1% 여야 하는데 16.2% 였다. - zoom 1.28 → 1.42 (렌더 폭이 줄어 종이 폭 370px 은 유지) - fitZoom 축소 시 종이 폭까지 좁아지던 문제도 함께 보정
게이트가 1.1.2 로 잡혀 있었지만 앱쪽 핸들러(handleInstagramStory.ts)는 app-v1.2.0 에 처음 들어갔다. 1.1.x 유저는 게이트를 통과해 메시지를 보내지만 앱에 수신 case 가 없어 응답이 오지 않고, 15 초 타임아웃 뒤 실패 토스트만 떴다. iOS 는 1.2.0 에서도 동작하지 않는다. 인스타그램 전용 pasteboard 키를 쓰는 네이티브 모듈(#503)이 app-v1.2.0 태그 이후에 추가돼 아직 어떤 릴리즈에도 포함되지 않았다. 따라서 실제로 동작하는 최초 버전은 1.2.1 이다. - BRIDGE_GATE 의 minAppVersion 을 1.2.1 로 정정 - app.json version 을 1.2.1 로 올려 게이트 기준과 다음 빌드를 일치시킴 (buildNumber/versionCode 는 eas.json 의 remote + autoIncrement 가 처리) 이제 1.2.1 미만에서는 전송 전에 차단돼 "앱을 업데이트하면 사용할 수 있어요" 안내가 뜬다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
작업 요약
작업 세부 내용
iOS 스토리 공유를 검증하다 영수증 공유 시트에서 두 가지 문제를 더 발견해 함께 고쳤습니다.
1. iOS 인스타그램 스토리 공유 (#502)
스토리 공유를 누르면 인스타그램이 열리지만 "이 앱은 스토리 공유를 지원하지 않습니다" 안내만 떴습니다. 안드로이드는 인텐트 경로라 영향이 없어 그동안 드러나지 않았습니다.
원인이 두 가지였습니다.
Facebook App ID 누락 — 2023-01 부터 인스타그램이 필수로 요구합니다.
Info.plist에FacebookAppID가 없었고 딥링크에도 붙지 않았습니다. config plugin 으로 주입하고 딥링크에?source_application=을 추가했습니다.pasteboard 키 불일치 — 기존에는
expo-clipboard로 일반 이미지 복사만 했습니다. 인스타그램은 전용 키를 읽습니다.expo-clipboard로는 이 키를 지정할 수 없어 로컬 Expo 모듈(modules/instagram-story)을 추가했습니다.App ID 는
app.json플러그인 설정에만 두고, 런타임은expo-constants로 같은 값을 읽어 중복을 없앴습니다. 비밀값이 아니라 카카오 네이티브 키와 같은 성격입니다.2. 영수증 공유 이미지에 상품 사진 누락
html-to-image가 캡처 시점에 이미지를 다시 fetch 하는데,mode: 'cors'로 요청하고 있었습니다./_next/image프록시 응답에는 CORS 헤더가 없어 브라우저가 거부했고, 해당 이미지가 빈 칸으로 남았습니다.cache: 'force-cache'덕에 브라우저 캐시가 맞아떨어질 때만 보여서 산발적으로 재현됐습니다.동일 origin 이라 CORS 가 불필요하고,
<img>도crossOrigin없이 로드되므로mode를 빼면 같은 캐시 항목을 재사용합니다.3. 스토리 버튼이 뒤늦게 나타남
앱 여부를
useSyncExternalStore로만 판정해 SSR 스냅샷이 항상false였습니다. hydration 이 끝나야true가 되어, 시트가 이미 올라온 뒤 버튼이 하나 추가되며 줄 전체가 다시 배치됐습니다.서버에서 User-Agent 로 판정해 내려주도록 바꿨습니다 (
getIsApp,getIsGuest와 같은 패턴). UA 를 안 붙이는 구버전 앱을 위해 클라 판정은 fallback 으로 남겼습니다.참고
expo-modules-core를 명시적 의존성으로 추가했습니다 — pnpm 스토어에만 있어 로컬 모듈에서 import 가 안 됐습니다.expo-clipboard는 이제 사용처가 없습니다. 제거는 별도로 판단하는 게 좋겠습니다.스크린샷
연관 이슈
closes #502
Summary by CodeRabbit
새 기능
개선
버그 수정