@@ -33,7 +33,7 @@ import {
3333} from "@t3tools/contracts" ;
3434import { PREVIEW_VIEWPORT_PRESETS } from "@t3tools/shared/previewViewport" ;
3535import { MoreVertical , Plus as PlusIcon } from "lucide-react" ;
36- import { useCallback , useRef , useState } from "react" ;
36+ import { useRef , useState } from "react" ;
3737
3838import { ScreenRotationIcon } from "~/browser/ScreenRotationIcon" ;
3939import { AnimatedHeight } from "~/components/AnimatedHeight" ;
@@ -189,6 +189,87 @@ export const importFailureReason = (cause: unknown): BrowserImportFailureReason
189189 ) ;
190190} ;
191191
192+ async function importBrowserCookiesIntoTarget (
193+ bridge : NonNullable < typeof previewBridge > ,
194+ source : BrowserImportSource ,
195+ environmentId : EnvironmentId ,
196+ input : { readonly sourceProfileDirectory : string ; readonly target : WizardTarget } ,
197+ ) : Promise < ImportOutcome > {
198+ try {
199+ const result = await bridge . importBrowserCookies ( {
200+ environmentId,
201+ sourceId : source . id ,
202+ sourceProfileDirectory : input . sourceProfileDirectory ,
203+ targetProfileId : input . target . profileId ,
204+ } ) ;
205+ if (
206+ input . target . kind === "existing" &&
207+ ! resolveBrowserProfiles ( getClientSettings ( ) . browserProfiles ) . some (
208+ ( profile ) => profile . id === input . target . profileId ,
209+ )
210+ ) {
211+ return { kind : "blocked" , reason : "readFailed" } ;
212+ }
213+ let targetName : string ;
214+ if ( input . target . kind === "new" ) {
215+ if ( result . imported > 0 ) {
216+ try {
217+ const persisted = await persistClientSettingsUpdate ( ( current ) => {
218+ const existing = current . browserProfiles . find (
219+ ( profile ) => profile . id === input . target . profileId ,
220+ ) ;
221+ if ( existing ) return current ;
222+ // The wizard refuses a new target at the cap, but the cap can be
223+ // reached while the import runs; the updater sees the newest
224+ // settings, so this is the check that holds.
225+ if ( current . browserProfiles . length >= BROWSER_PROFILE_MAX_COUNT ) {
226+ throw new ProfileLimitReachedError ( ) ;
227+ }
228+ const taken = new Set (
229+ resolveBrowserProfiles ( current . browserProfiles ) . map ( ( profile ) => profile . name ) ,
230+ ) ;
231+ let name = source . name ;
232+ for ( let index = 2 ; taken . has ( name ) ; index += 1 ) name = `${ source . name } ${ index } ` ;
233+ return {
234+ ...current ,
235+ browserProfiles : [
236+ ...current . browserProfiles ,
237+ { id : input . target . profileId , name, kind : "persistent" as const } ,
238+ ] ,
239+ } ;
240+ } ) ;
241+ targetName =
242+ persisted . browserProfiles . find ( ( profile ) => profile . id === input . target . profileId )
243+ ?. name ?? source . name ;
244+ } catch ( cause ) {
245+ // This target id belongs only to the attempted new profile. Clear
246+ // its partition so a failed registration cannot strand imported
247+ // cookies behind a profile that disappears on restart.
248+ await clearBrowserProfileData ( bridge , [ environmentId ] , input . target . profileId ) . catch (
249+ ( ) => undefined ,
250+ ) ;
251+ const reason =
252+ cause instanceof ProfileLimitReachedError ? "profileLimitReached" : "profileNotSaved" ;
253+ throw new Error ( `Importing cookies from ${ source . id } failed: ${ reason } .` , { cause } ) ;
254+ }
255+ } else {
256+ targetName = source . name ;
257+ }
258+ } else {
259+ targetName = input . target . name ;
260+ }
261+ return {
262+ kind : "imported" ,
263+ imported : result . imported ,
264+ skipped : result . skipped ,
265+ skippedDomains : result . skippedDomains ,
266+ targetName,
267+ } ;
268+ } catch ( cause ) {
269+ return { kind : "blocked" , reason : importFailureReason ( cause ) } ;
270+ }
271+ }
272+
192273const viewportSelectValue = ( viewport : PreviewViewportSetting ) : string => {
193274 if ( viewport . _tag === "fill" ) return FILL_VALUE ;
194275 if (
@@ -690,34 +771,31 @@ function DeviceIntegrationControls({
690771 ) => {
691772 if ( ! environmentId || ! readEnvironmentScope ( environmentId , AuthSettingsWriteScope ) ) return ;
692773 setPending ( kind ) ;
693- try {
694- const results = await Promise . allSettled (
695- environments . map ( async ( environment ) => {
696- if ( environment . connection . phase !== "connected" || ! environment . serverConfig ) {
697- throw new Error ( "Environment disconnected" ) ;
698- }
699- if ( ! readEnvironmentScope ( environment . environmentId , AuthSettingsWriteScope ) ) {
700- throw new Error ( "This connection cannot change device settings." ) ;
701- }
702- return configure ( {
703- environmentId : environment . environmentId ,
704- input : { ...input , ...( input . enabled ? { onboardingCompleted : true } : { } ) } ,
705- } ) ;
706- } ) ,
707- ) ;
708- const failed = environments . filter ( ( _ , index ) => {
709- const result = results [ index ] ;
710- return result ?. status !== "fulfilled" || result . value . _tag === "Failure" ;
711- } ) ;
712- if ( failed . length > 0 ) {
713- toastManager . add ( {
714- type : "error" ,
715- title : "Device settings not saved on all environments" ,
716- description : `Could not update ${ failed . map ( ( environment ) => environment . label ) . join ( ", " ) } .` ,
774+ const results = await Promise . allSettled (
775+ environments . map ( async ( environment ) => {
776+ if ( environment . connection . phase !== "connected" || ! environment . serverConfig ) {
777+ throw new Error ( "Environment disconnected" ) ;
778+ }
779+ if ( ! readEnvironmentScope ( environment . environmentId , AuthSettingsWriteScope ) ) {
780+ throw new Error ( "This connection cannot change device settings." ) ;
781+ }
782+ return configure ( {
783+ environmentId : environment . environmentId ,
784+ input : { ...input , ...( input . enabled ? { onboardingCompleted : true } : { } ) } ,
717785 } ) ;
718- }
719- } finally {
720- setPending ( null ) ;
786+ } ) ,
787+ ) ;
788+ setPending ( null ) ;
789+ const failed = environments . filter ( ( _ , index ) => {
790+ const result = results [ index ] ;
791+ return result ?. status !== "fulfilled" || result . value . _tag === "Failure" ;
792+ } ) ;
793+ if ( failed . length > 0 ) {
794+ toastManager . add ( {
795+ type : "error" ,
796+ title : "Device settings not saved on all environments" ,
797+ description : `Could not update ${ failed . map ( ( environment ) => environment . label ) . join ( ", " ) } .` ,
798+ } ) ;
721799 }
722800 } ;
723801
@@ -1065,13 +1143,13 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
10651143 source . unavailable !== "notInstalled" && source . unavailable !== "unsupportedPlatform" ,
10661144 ) ;
10671145
1068- const loadSources = useCallback ( ( ) => {
1146+ const loadSources = ( ) => {
10691147 if ( ! previewBridge ) return ;
10701148 void previewBridge
10711149 . listBrowserImportSources ( )
10721150 . then ( setSources )
10731151 . catch ( ( ) => setSources ( ( previous ) => previous ?? [ ] ) ) ;
1074- } , [ ] ) ;
1152+ } ;
10751153
10761154 const runWizardImport = async (
10771155 source : BrowserImportSource ,
@@ -1091,84 +1169,12 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
10911169 if ( importInFlightRef . current ) return { kind : "blocked" , reason : "readFailed" } ;
10921170 importInFlightRef . current = true ;
10931171 setImportInFlight ( true ) ;
1094- try {
1095- const result = await previewBridge . importBrowserCookies ( {
1096- environmentId,
1097- sourceId : source . id ,
1098- sourceProfileDirectory : input . sourceProfileDirectory ,
1099- targetProfileId : input . target . profileId ,
1100- } ) ;
1101- if (
1102- input . target . kind === "existing" &&
1103- ! resolveBrowserProfiles ( getClientSettings ( ) . browserProfiles ) . some (
1104- ( profile ) => profile . id === input . target . profileId ,
1105- )
1106- ) {
1107- return { kind : "blocked" , reason : "readFailed" } ;
1108- }
1109- let targetName : string ;
1110- if ( input . target . kind === "new" ) {
1111- if ( result . imported > 0 ) {
1112- try {
1113- const persisted = await persistClientSettingsUpdate ( ( current ) => {
1114- const existing = current . browserProfiles . find (
1115- ( profile ) => profile . id === input . target . profileId ,
1116- ) ;
1117- if ( existing ) return current ;
1118- // The wizard refuses a new target at the cap, but the cap can be
1119- // reached while the import runs; the updater sees the newest
1120- // settings, so this is the check that holds.
1121- if ( current . browserProfiles . length >= BROWSER_PROFILE_MAX_COUNT ) {
1122- throw new ProfileLimitReachedError ( ) ;
1123- }
1124- const taken = new Set (
1125- resolveBrowserProfiles ( current . browserProfiles ) . map ( ( profile ) => profile . name ) ,
1126- ) ;
1127- let name = source . name ;
1128- for ( let index = 2 ; taken . has ( name ) ; index += 1 ) name = `${ source . name } ${ index } ` ;
1129- return {
1130- ...current ,
1131- browserProfiles : [
1132- ...current . browserProfiles ,
1133- { id : input . target . profileId , name, kind : "persistent" as const } ,
1134- ] ,
1135- } ;
1136- } ) ;
1137- targetName =
1138- persisted . browserProfiles . find ( ( profile ) => profile . id === input . target . profileId )
1139- ?. name ?? source . name ;
1140- } catch ( cause ) {
1141- // This target id belongs only to the attempted new profile. Clear
1142- // its partition so a failed registration cannot strand imported
1143- // cookies behind a profile that disappears on restart.
1144- await clearBrowserProfileData (
1145- previewBridge ,
1146- [ environmentId ] ,
1147- input . target . profileId ,
1148- ) . catch ( ( ) => undefined ) ;
1149- const reason =
1150- cause instanceof ProfileLimitReachedError ? "profileLimitReached" : "profileNotSaved" ;
1151- throw new Error ( `Importing cookies from ${ source . id } failed: ${ reason } .` , { cause } ) ;
1152- }
1153- } else {
1154- targetName = source . name ;
1155- }
1156- } else {
1157- targetName = input . target . name ;
1158- }
1159- return {
1160- kind : "imported" ,
1161- imported : result . imported ,
1162- skipped : result . skipped ,
1163- skippedDomains : result . skippedDomains ,
1164- targetName,
1165- } ;
1166- } catch ( cause ) {
1167- return { kind : "blocked" , reason : importFailureReason ( cause ) } ;
1168- } finally {
1169- importInFlightRef . current = false ;
1170- setImportInFlight ( false ) ;
1171- }
1172+ return importBrowserCookiesIntoTarget ( previewBridge , source , environmentId , input ) . finally (
1173+ ( ) => {
1174+ importInFlightRef . current = false ;
1175+ setImportInFlight ( false ) ;
1176+ } ,
1177+ ) ;
11721178 } ;
11731179
11741180 const refreshImportSource = async (
0 commit comments