@@ -19,9 +19,11 @@ import { Readable } from "node:stream";
1919export class GoogleDriveProvider implements Provider {
2020 private drive : drive_v3 . Drive ;
2121 private accessToken : string ;
22+ private isEdgeRuntime : boolean ;
2223
23- constructor ( accessToken : string ) {
24+ constructor ( accessToken : string , isEdgeRuntime : boolean = false ) {
2425 this . accessToken = accessToken ;
26+ this . isEdgeRuntime = isEdgeRuntime ;
2527 const oauth2Client = new OAuth2Client ( ) ;
2628 oauth2Client . setCredentials ( { access_token : accessToken } ) ;
2729 this . drive = new drive_v3 . Drive ( {
@@ -57,17 +59,82 @@ export class GoogleDriveProvider implements Provider {
5759 // fields: this.getFileFields(),
5860 } ) ;
5961 } else {
60- // For files with content
61- const media = {
62- mimeType,
63- body : this . normalizeContent ( content ) ,
64- } ;
65-
66- response = await this . drive . files . create ( {
67- requestBody : fileMetadata ,
68- media,
69- // fields: this.getFileFields(),
70- } ) ;
62+ // ! Note: when attempting to use the sdk, cloudflare envrionments error on upload. Using the api via fetch directly allows for uploads on cloudflare environments
63+ // ! Also, this code was made with AI
64+ if ( this . isEdgeRuntime ) {
65+ const initRes = await fetch ( "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable" , {
66+ method : "POST" ,
67+ headers : {
68+ Authorization : `Bearer ${ this . accessToken } ` ,
69+ "Content-Type" : "application/json; charset=UTF-8" ,
70+ } ,
71+ body : JSON . stringify ( fileMetadata ) ,
72+ } ) ;
73+
74+ if ( ! initRes . ok ) {
75+ const details = await initRes . text ( ) . catch ( ( ) => initRes . statusText ) ;
76+ throw new Error ( `Failed to initiate resumable upload. Status: ${ initRes . status } . ${ details } ` ) ;
77+ }
78+
79+ const uploadUrl = initRes . headers . get ( "Location" ) ;
80+ if ( ! uploadUrl ) {
81+ throw new Error ( "Resumable upload URL was not returned by Google Drive API" ) ;
82+ }
83+
84+ let contentBuffer : Uint8Array ;
85+ if ( typeof Buffer !== "undefined" && Buffer . isBuffer ( content ) ) {
86+ contentBuffer = new Uint8Array ( content as Buffer ) ;
87+ } else if ( content instanceof Uint8Array ) {
88+ contentBuffer = content ;
89+ } else {
90+ const chunks : Uint8Array [ ] = [ ] ;
91+ for await ( const chunk of content as NodeJS . ReadableStream ) {
92+ chunks . push ( typeof chunk === "string" ? new TextEncoder ( ) . encode ( chunk ) : new Uint8Array ( chunk ) ) ;
93+ }
94+ const total = chunks . reduce ( ( n , c ) => n + c . byteLength , 0 ) ;
95+ contentBuffer = new Uint8Array ( total ) ;
96+ let offset = 0 ;
97+ for ( const c of chunks ) {
98+ contentBuffer . set ( c , offset ) ;
99+ offset += c . byteLength ;
100+ }
101+ }
102+
103+ // Create a concrete ArrayBuffer to avoid SharedArrayBuffer typing issues
104+ const arrayBufferForBody = ( ( ) => {
105+ const ab = new ArrayBuffer ( contentBuffer . byteLength ) ;
106+ new Uint8Array ( ab ) . set ( contentBuffer ) ;
107+ return ab ;
108+ } ) ( ) ;
109+
110+ const uploadRes = await fetch ( uploadUrl , {
111+ method : "PUT" ,
112+ headers : {
113+ "Content-Type" : mimeType ,
114+ "Content-Length" : String ( contentBuffer . byteLength ) ,
115+ } ,
116+ body : arrayBufferForBody ,
117+ } ) ;
118+
119+ if ( ! uploadRes . ok ) {
120+ const details = await uploadRes . text ( ) . catch ( ( ) => uploadRes . statusText ) ;
121+ throw new Error ( `Resumable upload failed. Status: ${ uploadRes . status } . ${ details } ` ) ;
122+ }
123+
124+ const created : drive_v3 . Schema$File = await uploadRes . json ( ) ;
125+ return created ? this . mapToFile ( created ) : null ;
126+ } else {
127+ const media = {
128+ mimeType,
129+ body : this . normalizeContent ( content ) ,
130+ } ;
131+
132+ response = await this . drive . files . create ( {
133+ requestBody : fileMetadata ,
134+ media,
135+ // fields: this.getFileFields(),
136+ } ) ;
137+ }
71138 }
72139
73140 return response . data ? this . mapToFile ( response . data ) : null ;
0 commit comments