1+ import html10n from './vendors/html10n' ;
2+
13const checkmark = '<svg width="28" height="28" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor"><path vector-effect="non-scaling-stroke" stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/></svg>' ;
24
35function getCookie ( name : string ) {
@@ -11,46 +13,137 @@ function getCookie(name: string) {
1113
1214const cp = ( window as any ) . clientVars ?. cookiePrefix || '' ;
1315
16+ const sessionTransferErrorFallback = ( ) =>
17+ html10n . get ( 'index.sessionTransferError' ) || 'Unable to transfer the session. Please try again.' ;
18+
19+ const safeJson = async ( response : Response ) : Promise < unknown > => {
20+ try {
21+ return await response . json ( ) ;
22+ } catch {
23+ return null ;
24+ }
25+ } ;
26+
27+ const responseErrorMessage = ( responseData : unknown ) : string => {
28+ const data = responseData as Record < string , unknown > ;
29+ if (
30+ responseData &&
31+ typeof responseData === 'object' &&
32+ 'error' in responseData &&
33+ typeof data . error === 'string' &&
34+ data . error . trim ( ) !== ''
35+ ) {
36+ return data . error ;
37+ }
38+ return sessionTransferErrorFallback ( ) ;
39+ } ;
40+
41+ const showSessionTransferError = ( element : HTMLElement | null , message : string ) => {
42+ if ( ! element ) return ;
43+ element . textContent = message ;
44+ element . style . display = 'block' ;
45+ } ;
46+
47+ const hideSessionTransferError = ( element : HTMLElement | null ) => {
48+ if ( ! element ) return ;
49+ element . textContent = '' ;
50+ element . style . display = 'none' ;
51+ } ;
52+
1453function handleTransferOfSession ( ) {
1554 const transferNowButton = document . querySelector ( '[data-l10n-id="index.transferSessionNow"]' ) ! as HTMLButtonElement ;
1655
1756 transferNowButton . addEventListener ( 'click' , async ( ) => {
57+ const originalButtonContent = transferNowButton . innerHTML ;
58+ const copyLinkSection = document . getElementById ( 'copy-link-section' ) ;
59+ const errorElement = document . getElementById ( 'transfer-session-error' ) ;
60+ hideSessionTransferError ( errorElement ) ;
61+ if ( copyLinkSection ) copyLinkSection . style . display = 'none' ;
1862 transferNowButton . style . display = 'inline-flex' ;
1963 transferNowButton . style . alignItems = 'center' ;
2064 transferNowButton . style . justifyContent = 'center' ;
21- transferNowButton . innerHTML = `${ checkmark } ` ;
2265 transferNowButton . disabled = true ;
2366
24- // The author token is HttpOnly (ether/etherpad#6701 PR3) so we cannot
25- // read it via document.cookie. Send only the JS-readable prefsHttp; the
26- // server reads the token off the request's own cookie jar.
27- const responseWithId = await fetch ( "./tokenTransfer" , {
28- method : "POST" ,
29- headers : {
30- "Content-Type" : "application/json"
31- } ,
32- body : JSON . stringify ( {
33- prefsHttp : getCookie ( `${ cp } prefsHttp` ) || getCookie ( 'prefsHttp' ) ,
34- } )
35- } )
67+ try {
68+ // The author token is HttpOnly (ether/etherpad#6701 PR3) so we cannot
69+ // read it via document.cookie. Send only the JS-readable prefsHttp; the
70+ // server reads the token off the request's own cookie jar.
71+ const responseWithId = await fetch ( "./tokenTransfer" , {
72+ method : "POST" ,
73+ headers : {
74+ "Content-Type" : "application/json"
75+ } ,
76+ body : JSON . stringify ( {
77+ prefsHttp : getCookie ( `${ cp } prefsHttp` ) || getCookie ( 'prefsHttp' ) ,
78+ } )
79+ } ) ;
3680
37- const copyLinkSection = document . getElementById ( 'copy-link-section' )
38- if ( ! copyLinkSection ) return ;
39- copyLinkSection . style . display = 'block' ;
40-
41- const copyButton = document . querySelector ( '#copy-link-section .btn-secondary' ) as HTMLButtonElement
42- const responseData = await responseWithId . json ( ) ;
43- copyButton . addEventListener ( 'click' , async ( ) => {
44- await navigator . clipboard . writeText ( responseData . id ) ;
45- copyButton . style . display = 'inline-flex' ;
46- copyButton . style . alignItems = 'center' ;
47- copyButton . style . justifyContent = 'center' ;
48- copyButton . innerHTML = `${ checkmark } ` ;
49- copyButton . disabled = true ;
50- } )
81+ const responseData = await safeJson ( responseWithId ) ;
82+ if ( ! responseWithId . ok ) {
83+ throw new Error ( responseErrorMessage ( responseData ) ) ;
84+ }
85+ const transferData = responseData as Record < string , unknown > ;
86+ if ( ! responseData || typeof responseData !== 'object' ||
87+ ! ( 'id' in responseData ) || typeof transferData . id !== 'string' ||
88+ transferData . id . trim ( ) === '' ) {
89+ throw new Error ( sessionTransferErrorFallback ( ) ) ;
90+ }
91+
92+ if ( ! copyLinkSection ) throw new Error ( sessionTransferErrorFallback ( ) ) ;
93+ copyLinkSection . style . display = 'block' ;
94+
95+ const copyButton = document . querySelector ( '#copy-link-section .btn-secondary' ) as HTMLButtonElement ;
96+ copyButton . disabled = false ;
97+ copyButton . onclick = async ( ) => {
98+ await navigator . clipboard . writeText ( transferData . id as string ) ;
99+ copyButton . style . display = 'inline-flex' ;
100+ copyButton . style . alignItems = 'center' ;
101+ copyButton . style . justifyContent = 'center' ;
102+ copyButton . innerHTML = `${ checkmark } ` ;
103+ copyButton . disabled = true ;
104+ } ;
105+ transferNowButton . innerHTML = `${ checkmark } ` ;
106+ } catch ( err ) {
107+ if ( copyLinkSection ) copyLinkSection . style . display = 'none' ;
108+ transferNowButton . innerHTML = originalButtonContent ;
109+ transferNowButton . disabled = false ;
110+ showSessionTransferError (
111+ errorElement ,
112+ err instanceof Error && err . message ? err . message : sessionTransferErrorFallback ( ) ) ;
113+ }
51114 } ) ;
52115}
53116
117+ const isValidTransferCode = ( code : string ) => code . length === 36 ;
118+
119+ async function redeemTransferCode (
120+ code : string ,
121+ transferSessionButton : HTMLButtonElement ,
122+ errorElement : HTMLElement | null ) {
123+ hideSessionTransferError ( errorElement ) ;
124+ transferSessionButton . disabled = true ;
125+
126+ try {
127+ const response = await fetch ( "./tokenTransfer/" + code , {
128+ method : 'GET'
129+ } ) ;
130+ const responseData = await safeJson ( response ) ;
131+ if ( ! response . ok ) {
132+ throw new Error ( responseErrorMessage ( responseData ) ) ;
133+ }
134+ const transferData = responseData as Record < string , unknown > ;
135+ if ( ! responseData || typeof responseData !== 'object' ||
136+ ! ( 'ok' in responseData ) || transferData . ok !== true ) {
137+ throw new Error ( sessionTransferErrorFallback ( ) ) ;
138+ }
139+ window . location . reload ( )
140+ } catch ( err ) {
141+ transferSessionButton . disabled = ! isValidTransferCode ( code ) ;
142+ showSessionTransferError (
143+ errorElement ,
144+ err instanceof Error && err . message ? err . message : sessionTransferErrorFallback ( ) ) ;
145+ }
146+ }
54147
55148const handleSettingsButtonClick = ( ) => {
56149 const settingsButton = document . querySelector ( '.settings-button' ) ! ;
@@ -86,24 +179,22 @@ const handleMenuBarClicked = () => {
86179 } ) ;
87180 } )
88181
89- const transferSessionButton = document . getElementById ( 'transferSessionButton' )
182+ const transferSessionButton = document . getElementById ( 'transferSessionButton' ) as HTMLButtonElement | null ;
90183 const codeInputField = document . getElementById ( 'codeInput' ) as HTMLInputElement
91184 if ( transferSessionButton ) {
92185 transferSessionButton . addEventListener ( 'click' , ( ) => {
93- const code = codeInputField . value
94- fetch ( "./tokenTransfer/" + code , {
95- method : 'GET'
96- } )
97- . then ( res => res . json ( ) )
98- . then ( ( ) => {
99- window . location . reload ( )
100- } )
186+ const code = codeInputField . value ;
187+ redeemTransferCode (
188+ code ,
189+ transferSessionButton ,
190+ document . getElementById ( 'receive-session-error' ) ) ;
101191 } ) ;
102192 }
103193
104194 if ( codeInputField ) {
105195 codeInputField . addEventListener ( 'input' , ( e ) => {
106- if ( ( e . target as HTMLInputElement ) . value ?. length === 36 ) {
196+ hideSessionTransferError ( document . getElementById ( 'receive-session-error' ) ) ;
197+ if ( isValidTransferCode ( ( e . target as HTMLInputElement ) . value ) ) {
107198 transferSessionButton ?. removeAttribute ( 'disabled' ) ;
108199 } else {
109200 transferSessionButton ?. setAttribute ( 'disabled' , 'true' ) ;
0 commit comments