@@ -67,12 +67,14 @@ export interface HandoffManagerDeps {
6767export interface ConnectScope {
6868 workspaceId ?: string
6969 credentialId ?: string
70+ chatAttemptId ?: string
7071}
7172
7273export interface HandoffManager {
7374 begin ( ) : Promise < boolean >
7475 beginConnect ( providerId : string , scope ?: ConnectScope ) : Promise < boolean >
7576 consume ( state : string , kind : HandoffKind ) : boolean
77+ consumeConnect ( state : string ) : ConnectScope | null
7678 clear ( ) : void
7779}
7880
@@ -92,7 +94,12 @@ export function createHandoffManager(
9294 const now = deps . now ?? Date . now
9395 let loopbackServer : Server | null = null
9496 let loopbackTimer : NodeJS . Timeout | undefined
95- let pending : { state : string ; createdAt : number ; kind : HandoffKind } | null = null
97+ let pending : {
98+ state : string
99+ createdAt : number
100+ kind : HandoffKind
101+ connectScope ?: ConnectScope
102+ } | null = null
96103
97104 const stopLoopback = ( ) => {
98105 clearTimeout ( loopbackTimer )
@@ -214,10 +221,23 @@ export function createHandoffManager(
214221 pending = null
215222 }
216223
224+ const consumePending = ( state : string , kind : HandoffKind ) : NonNullable < typeof pending > | null => {
225+ if ( ! pending || pending . kind !== kind ) return null
226+ if ( now ( ) - pending . createdAt > HANDOFF_TTL_MS ) {
227+ clear ( )
228+ return null
229+ }
230+ if ( ! safeCompare ( pending . state , state ) ) return null
231+ const consumed = pending
232+ clear ( )
233+ return consumed
234+ }
235+
217236 const beginFlow = async (
218237 kind : HandoffKind ,
219238 landingPath : string ,
220- params : Record < string , string >
239+ params : Record < string , string > ,
240+ connectScope ?: ConnectScope
221241 ) : Promise < boolean > => {
222242 const state = generateShortId ( STATE_LENGTH )
223243 // startLoopback() already tore down any prior server; if this bind fails,
@@ -228,7 +248,12 @@ export function createHandoffManager(
228248 clear ( )
229249 return false
230250 }
231- pending = { state, createdAt : now ( ) , kind }
251+ pending = {
252+ state,
253+ createdAt : now ( ) ,
254+ kind,
255+ ...( connectScope ? { connectScope : { ...connectScope } } : { } ) ,
256+ }
232257 const landing = new URL ( landingPath , deps . origin ( ) )
233258 for ( const [ key , value ] of Object . entries ( params ) ) {
234259 landing . searchParams . set ( key , value )
@@ -259,26 +284,24 @@ export function createHandoffManager(
259284 // unknown (offline, signed out): the page then falls back to its normal
260285 // login redirect rather than blocking a connect on a failed probe.
261286 const userId = await deps . currentUserId ( )
262- return beginFlow ( 'connect' , '/desktop/connect' , {
263- provider : providerId ,
264- ...( userId ? { user : userId } : { } ) ,
265- ...( scope . workspaceId ? { workspaceId : scope . workspaceId } : { } ) ,
266- ...( scope . credentialId ? { credentialId : scope . credentialId } : { } ) ,
267- } )
287+ return beginFlow (
288+ 'connect' ,
289+ '/desktop/connect' ,
290+ {
291+ provider : providerId ,
292+ ...( userId ? { user : userId } : { } ) ,
293+ ...( scope . workspaceId ? { workspaceId : scope . workspaceId } : { } ) ,
294+ ...( scope . credentialId ? { credentialId : scope . credentialId } : { } ) ,
295+ } ,
296+ scope
297+ )
268298 } ,
269299 consume ( state : string , kind : HandoffKind ) {
270- if ( ! pending || pending . kind !== kind ) {
271- return false
272- }
273- if ( now ( ) - pending . createdAt > HANDOFF_TTL_MS ) {
274- clear ( )
275- return false
276- }
277- if ( ! safeCompare ( pending . state , state ) ) {
278- return false
279- }
280- clear ( )
281- return true
300+ return consumePending ( state , kind ) !== null
301+ } ,
302+ consumeConnect ( state : string ) {
303+ const consumed = consumePending ( state , 'connect' )
304+ return consumed ? { ...( consumed . connectScope ?? { } ) } : null
282305 } ,
283306 clear,
284307 }
@@ -443,6 +466,8 @@ export function createAuthFlow(deps: AuthFlowDeps): AuthFlow {
443466export interface ConnectHandoffResult {
444467 ok : boolean
445468 error ?: string
469+ /** Exact Mothership chat attempt, or null for ordinary integration flows. */
470+ chatAttemptId : string | null
446471}
447472
448473export interface ConnectFlowDeps {
@@ -476,19 +501,24 @@ export function createConnectFlow(deps: ConnectFlowDeps): ConnectFlow {
476501 return opened
477502 } ,
478503 handleCallback ( callback : ConnectHandoffCallback ) {
479- if ( ! deps . handoff . consume ( callback . state , 'connect' ) ) {
504+ const scope = deps . handoff . consumeConnect ( callback . state )
505+ if ( ! scope ) {
480506 deps . events . record ( 'connect_handoff_state_fail' )
481507 return
482508 }
483509 if ( callback . error === undefined ) {
484510 deps . events . record ( 'connect_handoff_ok' )
485511 deps . focusMainWindow ( )
486- deps . notifyRenderer ( { ok : true } )
512+ deps . notifyRenderer ( { ok : true , chatAttemptId : scope . chatAttemptId ?? null } )
487513 return
488514 }
489515 deps . events . record ( 'connect_handoff_error' , { error : callback . error } )
490516 deps . focusMainWindow ( )
491- deps . notifyRenderer ( { ok : false , error : callback . error } )
517+ deps . notifyRenderer ( {
518+ ok : false ,
519+ error : callback . error ,
520+ chatAttemptId : scope . chatAttemptId ?? null ,
521+ } )
492522 } ,
493523 }
494524}
0 commit comments