This repository was archived by the owner on Jul 1, 2026. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy patheffectivePathsMobileGrantFlow.ts
More file actions
138 lines (124 loc) · 4.29 KB
/
Copy patheffectivePathsMobileGrantFlow.ts
File metadata and controls
138 lines (124 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import type { EffectivePathsIdsGateway } from './assembleEffectivePathsListResult'
import type { EpMobileResumePayload } from './effectivePathsMobileGrantSession'
import { readCachedLinkedSpreadsheetIds } from './effectivePathsLinkedSpreadsheetCache'
import { fetchEffectivePathsIdsGateway } from './exportEffectivePathsApi'
import { redirectToMobilePickerAuth } from './googleDrivePickerMobile'
import {
collectSpreadsheetIdsFromGateway,
type EnsureEffectivePathsSpreadsheetAccessResult,
type EnsureEffectivePathsSpreadsheetAccessTitles,
linkedWorkbookNamesFromGateway,
} from './grantEffectivePathsSpreadsheetAccess'
export type BeginMobileEffectivePathsGrantOptions = {
masterSpreadsheetId: string
masterSheetGid: number | null
titles: EnsureEffectivePathsSpreadsheetAccessTitles
}
function pickerTitleWithWorkbookNames(
baseTitle: string,
gateway: EffectivePathsIdsGateway,
): string {
const names = linkedWorkbookNamesFromGateway(gateway)
return names.length > 0 ? `${baseTitle} (${names.join(', ')})` : baseTitle
}
function grantFailure(
reason: Exclude<EnsureEffectivePathsSpreadsheetAccessResult, { ok: true }>['reason'],
extra?: Partial<Extract<EnsureEffectivePathsSpreadsheetAccessResult, { ok: false }>>,
): EnsureEffectivePathsSpreadsheetAccessResult {
return { ok: false, reason, ...extra } as EnsureEffectivePathsSpreadsheetAccessResult
}
function validateRequiredMaster(
pickedIds: readonly string[],
requiredMaster?: string,
): EnsureEffectivePathsSpreadsheetAccessResult | null {
const required = requiredMaster?.trim()
if (!required) return null
if (!pickedIds.includes(required)) {
return grantFailure('wrong_spreadsheet')
}
return null
}
/** Start mobile redirect grant (page navigates away; does not return). */
export async function beginMobileEffectivePathsGrant(
options: BeginMobileEffectivePathsGrantOptions,
): Promise<void> {
const { masterSpreadsheetId, masterSheetGid, titles } = options
const cachedLinked = readCachedLinkedSpreadsheetIds(masterSpreadsheetId)
if (cachedLinked.length > 0) {
await redirectToMobilePickerAuth({
phase: 'all_workbooks',
masterSpreadsheetId,
masterSheetGid,
spreadsheetIds: [masterSpreadsheetId, ...cachedLinked],
multiselect: true,
requireMasterSpreadsheetId: masterSpreadsheetId,
titles,
})
return
}
await redirectToMobilePickerAuth({
phase: 'master',
masterSpreadsheetId,
masterSheetGid,
spreadsheetIds: [masterSpreadsheetId],
multiselect: false,
requireMasterSpreadsheetId: masterSpreadsheetId,
titles,
})
}
export type ResumeMobileEffectivePathsGrantResult =
| EnsureEffectivePathsSpreadsheetAccessResult
| { ok: false; reason: 'mobile_resume_missing' }
/**
* Continue grant after Google redirects back. May start another redirect for linked workbooks.
*/
export async function resumeMobileEffectivePathsGrant(
resume: EpMobileResumePayload,
): Promise<ResumeMobileEffectivePathsGrantResult> {
const {
accessToken,
pickedSpreadsheetIds,
phase,
masterSpreadsheetId,
masterSheetGid,
requireMasterSpreadsheetId,
titles,
} = resume
const masterCheck = validateRequiredMaster(pickedSpreadsheetIds, requireMasterSpreadsheetId)
if (masterCheck) return masterCheck
if (phase === 'all_workbooks' || phase === 'linked_workbooks') {
return { ok: true }
}
const gatewayResult = await fetchEffectivePathsIdsGateway({
googleAccessToken: accessToken,
masterSpreadsheetId,
sheetGid: masterSheetGid,
})
if (!gatewayResult.ok) {
return {
ok: false,
reason: 'gateway_failed',
error: gatewayResult.error,
message: gatewayResult.message,
}
}
const gateway = gatewayResult.gateway
const allIds = collectSpreadsheetIdsFromGateway(gateway, masterSpreadsheetId)
const linkedOnly = allIds.filter((id) => id !== masterSpreadsheetId)
if (linkedOnly.length === 0) {
return { ok: true }
}
await redirectToMobilePickerAuth({
phase: 'linked_workbooks',
masterSpreadsheetId,
masterSheetGid,
spreadsheetIds: allIds,
multiselect: true,
requireMasterSpreadsheetId: masterSpreadsheetId,
titles: {
...titles,
linkedWorkbooks: pickerTitleWithWorkbookNames(titles.linkedWorkbooks, gateway),
},
})
throw new Error('mobile_picker_redirect')
}