다운로드를 다시 시도하려고 작업 중인 페이지를 새로 열면 초안이나 화면 위치까지 흔들릴 수 있다. 북극성에서는 원래 다운로드 주소를 원래 프로필 세션에 요청했다. 계정 쿠키를 복제하거나 다른 창의 로그인에 기대는 대신, 실패 기록이 가진 맥락을 새 전송으로 연결하는 방식이다.

다시 받을 주소를 화면에서 추측하지 않는다

retryURL은 기록에 저장된 sourceURL을 읽는다. 문자열 길이와 URL 형식, HTTP(S) 프로토콜, 사용자명·비밀번호 필드를 검사한다. 지금 화면에 비슷한 링크가 보인다고 그 주소를 원래 파일로 추측하거나 폼 전송을 재생하지 않는다. 원본 자원이 명확할 때만 작은 재시도를 제공하려는 경계다.

상태도 검사한다. 실패·취소·중단 기록이어야 하고 canCancel이나 retrying이 남아 있으면 새 요청을 허용하지 않는다. 원본이 살아 있는 전송이면 그 제어를 먼저 써야 한다. 주소를 갖고 있다는 사실만으로 서버가 같은 내용을 돌려주거나 같은 권한이 유효하다는 보장까지 생기지는 않는다.

프로필의 세션에 새 네이티브 요청을 보낸다

BrowserHost의 retry 분기는 현재 프로필 소유 기록을 찾고 getSession(d.profileId).downloadURL을 호출한다. 작성 중인 tab의 navigate를 호출하지 않고 새 탭도 만들지 않는다. 이 코드에서 원래 프로필을 유지한다는 것은 그 세션의 네이티브 다운로드 경로를 사용한다는 구체적인 뜻이다.

쿠키를 꺼내 새 브라우저로 옮기는 동작은 없다. 다만 같은 세션이라고 로그인 상태가 영원히 유효한 것은 아니다. 서비스가 재인증을 요구하거나 링크가 만료되면 새 요청도 실패할 수 있다. 나는 계정 맥락 보존과 원격 인증 성공을 같은 단어로 설명하지 않았다. 후자는 실제 응답과 파일 결과를 봐야 한다.

중복 클릭이 새 전송을 늘리지 않게 한다

RetryDownloads.start는 profile과 원본 기록 ID로 pending을 잡고, 같은 프로필에서 같은 원본 URL을 다시 받는 다른 요청도 막는다. 같은 파일이 서로 다른 프로젝트 기록에 있어도 동시 요청을 직렬화해야 새 전송의 소속을 정확히 붙이기 쉽기 때문이다.

dispatch가 즉시 실패하면 pending을 해제해 원본을 다시 시도 가능한 상태로 돌린다. child가 연결되고 끝나도 해제한다. 이 Map은 현재 실행 중인 재시도를 조정하는 메모리 상태다. 앱 재시작을 넘는 영속 중복 방지 원장이나 모든 서버 요청의 정확히 한 번 실행 보장은 아니다. 구현이 제공하지 않는 보장을 수식어로 보태지 않았다.

새 전송의 소속은 같은 프로필과 주소 체인으로 찾는다

새 네이티브 다운로드에서 source가 없는 경우 claim은 같은 프로필의 pending과 URL chain을 대조한다. 일치한 첫 child에 원래 tabId와 workspaceId를 붙인다. 주소가 리다이렉트됐을 때도 chain에 원본이 있는지 확인할 수 있는 구조다. 다른 프로필의 같은 URL은 소속을 가져가지 못한다.

연결된 child는 별도의 ID와 상태를 가진다. 원래 실패 기록을 완료라고 고쳐 쓰지 않는다. 이 소속 연결은 업무 내용의 정확성까지 확인하지 않으며 새 파일의 크기·내용은 따로 읽어야 한다. 재시도 결과가 어느 작업에서 비롯됐는지와 파일이 실제로 맞는지는 서로 다른 질문으로 남겨 두었다.

초안 보존은 화면과 요청을 함께 비교해야 한다

기존 UI 시험은 로컬 수신 서버가 시험용 쿠키를 받았는지, 중복 클릭 뒤 요청이 한 번만 늘었는지, 새 파일의 해시가 맞는지 검사하도록 작성돼 있다. 동시에 원래 activeTabId와 입력한 초안, 페이지의 timeOrigin이 그대로인지도 본다. 다운로드 성공만 보면 화면 보존 문제를 놓칠 수 있다.

이번에는 그 선택 범위를 읽었으며 실제 서비스나 시험 서버를 새로 실행하지 않았다. 합성 파일 영수증 모듈은 공급된 bytes의 비교만 제공하고 로그인·retry는 구현하지 않는다. 다른 제품에 적용한다면 원본 주소 보존, 세션 선택, 중복 요청 잠금, 새 결과 소속과 내용 확인을 따로 연결하는 설계부터 검토하면 된다.