11import crypto from "node:crypto" ;
2+ import { fetchWithSsrFGuard } from "openclaw/plugin-sdk" ;
23import type { ResolvedGoogleChatAccount } from "./accounts.js" ;
34import { getGoogleChatAccessToken } from "./auth.js" ;
45import type { GoogleChatReaction } from "./types.js" ;
@@ -19,19 +20,27 @@ async function fetchJson<T>(
1920 init : RequestInit ,
2021) : Promise < T > {
2122 const token = await getGoogleChatAccessToken ( account ) ;
22- const res = await fetch ( url , {
23- ...init ,
24- headers : {
25- ...headersToObject ( init . headers ) ,
26- Authorization : `Bearer ${ token } ` ,
27- "Content-Type" : "application/json" ,
23+ const { response : res , release } = await fetchWithSsrFGuard ( {
24+ url,
25+ init : {
26+ ...init ,
27+ headers : {
28+ ...headersToObject ( init . headers ) ,
29+ Authorization : `Bearer ${ token } ` ,
30+ "Content-Type" : "application/json" ,
31+ } ,
2832 } ,
33+ auditContext : "googlechat.api.json" ,
2934 } ) ;
30- if ( ! res . ok ) {
31- const text = await res . text ( ) . catch ( ( ) => "" ) ;
32- throw new Error ( `Google Chat API ${ res . status } : ${ text || res . statusText } ` ) ;
35+ try {
36+ if ( ! res . ok ) {
37+ const text = await res . text ( ) . catch ( ( ) => "" ) ;
38+ throw new Error ( `Google Chat API ${ res . status } : ${ text || res . statusText } ` ) ;
39+ }
40+ return ( await res . json ( ) ) as T ;
41+ } finally {
42+ await release ( ) ;
3343 }
34- return ( await res . json ( ) ) as T ;
3544}
3645
3746async function fetchOk (
@@ -40,16 +49,24 @@ async function fetchOk(
4049 init : RequestInit ,
4150) : Promise < void > {
4251 const token = await getGoogleChatAccessToken ( account ) ;
43- const res = await fetch ( url , {
44- ...init ,
45- headers : {
46- ...headersToObject ( init . headers ) ,
47- Authorization : `Bearer ${ token } ` ,
52+ const { response : res , release } = await fetchWithSsrFGuard ( {
53+ url,
54+ init : {
55+ ...init ,
56+ headers : {
57+ ...headersToObject ( init . headers ) ,
58+ Authorization : `Bearer ${ token } ` ,
59+ } ,
4860 } ,
61+ auditContext : "googlechat.api.ok" ,
4962 } ) ;
50- if ( ! res . ok ) {
51- const text = await res . text ( ) . catch ( ( ) => "" ) ;
52- throw new Error ( `Google Chat API ${ res . status } : ${ text || res . statusText } ` ) ;
63+ try {
64+ if ( ! res . ok ) {
65+ const text = await res . text ( ) . catch ( ( ) => "" ) ;
66+ throw new Error ( `Google Chat API ${ res . status } : ${ text || res . statusText } ` ) ;
67+ }
68+ } finally {
69+ await release ( ) ;
5370 }
5471}
5572
@@ -60,51 +77,59 @@ async function fetchBuffer(
6077 options ?: { maxBytes ?: number } ,
6178) : Promise < { buffer : Buffer ; contentType ?: string } > {
6279 const token = await getGoogleChatAccessToken ( account ) ;
63- const res = await fetch ( url , {
64- ...init ,
65- headers : {
66- ...headersToObject ( init ?. headers ) ,
67- Authorization : `Bearer ${ token } ` ,
80+ const { response : res , release } = await fetchWithSsrFGuard ( {
81+ url,
82+ init : {
83+ ...init ,
84+ headers : {
85+ ...headersToObject ( init ?. headers ) ,
86+ Authorization : `Bearer ${ token } ` ,
87+ } ,
6888 } ,
89+ auditContext : "googlechat.api.buffer" ,
6990 } ) ;
70- if ( ! res . ok ) {
71- const text = await res . text ( ) . catch ( ( ) => "" ) ;
72- throw new Error ( `Google Chat API ${ res . status } : ${ text || res . statusText } ` ) ;
73- }
74- const maxBytes = options ?. maxBytes ;
75- const lengthHeader = res . headers . get ( "content-length" ) ;
76- if ( maxBytes && lengthHeader ) {
77- const length = Number ( lengthHeader ) ;
78- if ( Number . isFinite ( length ) && length > maxBytes ) {
79- throw new Error ( `Google Chat media exceeds max bytes (${ maxBytes } )` ) ;
91+ try {
92+ if ( ! res . ok ) {
93+ const text = await res . text ( ) . catch ( ( ) => "" ) ;
94+ throw new Error ( `Google Chat API ${ res . status } : ${ text || res . statusText } ` ) ;
8095 }
81- }
82- if ( ! maxBytes || ! res . body ) {
83- const buffer = Buffer . from ( await res . arrayBuffer ( ) ) ;
84- const contentType = res . headers . get ( "content-type" ) ?? undefined ;
85- return { buffer, contentType } ;
86- }
87- const reader = res . body . getReader ( ) ;
88- const chunks : Buffer [ ] = [ ] ;
89- let total = 0 ;
90- while ( true ) {
91- const { done, value } = await reader . read ( ) ;
92- if ( done ) {
93- break ;
96+ const maxBytes = options ?. maxBytes ;
97+ const lengthHeader = res . headers . get ( "content-length" ) ;
98+ if ( maxBytes && lengthHeader ) {
99+ const length = Number ( lengthHeader ) ;
100+ if ( Number . isFinite ( length ) && length > maxBytes ) {
101+ throw new Error ( `Google Chat media exceeds max bytes (${ maxBytes } )` ) ;
102+ }
94103 }
95- if ( ! value ) {
96- continue ;
104+ if ( ! maxBytes || ! res . body ) {
105+ const buffer = Buffer . from ( await res . arrayBuffer ( ) ) ;
106+ const contentType = res . headers . get ( "content-type" ) ?? undefined ;
107+ return { buffer, contentType } ;
97108 }
98- total += value . length ;
99- if ( total > maxBytes ) {
100- await reader . cancel ( ) ;
101- throw new Error ( `Google Chat media exceeds max bytes (${ maxBytes } )` ) ;
109+ const reader = res . body . getReader ( ) ;
110+ const chunks : Buffer [ ] = [ ] ;
111+ let total = 0 ;
112+ while ( true ) {
113+ const { done, value } = await reader . read ( ) ;
114+ if ( done ) {
115+ break ;
116+ }
117+ if ( ! value ) {
118+ continue ;
119+ }
120+ total += value . length ;
121+ if ( total > maxBytes ) {
122+ await reader . cancel ( ) ;
123+ throw new Error ( `Google Chat media exceeds max bytes (${ maxBytes } )` ) ;
124+ }
125+ chunks . push ( Buffer . from ( value ) ) ;
102126 }
103- chunks . push ( Buffer . from ( value ) ) ;
127+ const buffer = Buffer . concat ( chunks , total ) ;
128+ const contentType = res . headers . get ( "content-type" ) ?? undefined ;
129+ return { buffer, contentType } ;
130+ } finally {
131+ await release ( ) ;
104132 }
105- const buffer = Buffer . concat ( chunks , total ) ;
106- const contentType = res . headers . get ( "content-type" ) ?? undefined ;
107- return { buffer, contentType } ;
108133}
109134
110135export async function sendGoogleChatMessage ( params : {
@@ -185,24 +210,32 @@ export async function uploadGoogleChatAttachment(params: {
185210
186211 const token = await getGoogleChatAccessToken ( account ) ;
187212 const url = `${ CHAT_UPLOAD_BASE } /${ space } /attachments:upload?uploadType=multipart` ;
188- const res = await fetch ( url , {
189- method : "POST" ,
190- headers : {
191- Authorization : `Bearer ${ token } ` ,
192- "Content-Type" : `multipart/related; boundary=${ boundary } ` ,
213+ const { response : res , release } = await fetchWithSsrFGuard ( {
214+ url,
215+ init : {
216+ method : "POST" ,
217+ headers : {
218+ Authorization : `Bearer ${ token } ` ,
219+ "Content-Type" : `multipart/related; boundary=${ boundary } ` ,
220+ } ,
221+ body,
193222 } ,
194- body ,
223+ auditContext : "googlechat.upload" ,
195224 } ) ;
196- if ( ! res . ok ) {
197- const text = await res . text ( ) . catch ( ( ) => "" ) ;
198- throw new Error ( `Google Chat upload ${ res . status } : ${ text || res . statusText } ` ) ;
225+ try {
226+ if ( ! res . ok ) {
227+ const text = await res . text ( ) . catch ( ( ) => "" ) ;
228+ throw new Error ( `Google Chat upload ${ res . status } : ${ text || res . statusText } ` ) ;
229+ }
230+ const payload = ( await res . json ( ) ) as {
231+ attachmentDataRef ?: { attachmentUploadToken ?: string } ;
232+ } ;
233+ return {
234+ attachmentUploadToken : payload . attachmentDataRef ?. attachmentUploadToken ,
235+ } ;
236+ } finally {
237+ await release ( ) ;
199238 }
200- const payload = ( await res . json ( ) ) as {
201- attachmentDataRef ?: { attachmentUploadToken ?: string } ;
202- } ;
203- return {
204- attachmentUploadToken : payload . attachmentDataRef ?. attachmentUploadToken ,
205- } ;
206239}
207240
208241export async function downloadGoogleChatMedia ( params : {
0 commit comments