@corsair-dev/googlecalendar 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @corsair-dev/googlecalendar
2
+
3
+ Google calendar plugin for Corsair.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @corsair-dev/googlecalendar
9
+ ```
10
+
11
+ ## Endpoints
12
+
13
+ | Operation | Operation ID | Risk | Description |
14
+ |-----------|--------------|------|-------------|
15
+ | `calendar.getAvailability` | `googlecalendar.api.calendar.getAvailability` | `read` | Get free/busy availability for a calendar |
16
+ | `events.create` | `googlecalendar.api.events.create` | `write` | Create a new calendar event |
17
+ | `events.delete` | `googlecalendar.api.events.delete` | `destructive` | Delete a calendar event [DESTRUCTIVE] |
18
+ | `events.get` | `googlecalendar.api.events.get` | `read` | Get a specific calendar event |
19
+ | `events.getMany` | `googlecalendar.api.events.getMany` | `read` | List calendar events |
20
+ | `events.update` | `googlecalendar.api.events.update` | `write` | Update an existing calendar event |
21
+
22
+ ## Auth
23
+
24
+ Auth: OAuth 2.0. Corsair prompts your tenant for credentials on first use.
25
+
26
+ ## Webhooks
27
+
28
+ Handles 1 webhook event. See the reference for payloads and `webhookHooks`.
29
+
30
+ ## Reference
31
+
32
+ Full docs, types, and examples: https://docs.corsair.dev/plugins/googlecalendar
33
+
34
+ ## License
35
+
36
+ Apache-2.0
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{AuthMissingError as Ne,getOAuthAccessToken as We}from"corsair/core";import{logEventFromContext as te}from"corsair/core";import{request as Q}from"corsair/http";var X="https://www.googleapis.com/calendar/v3";async function h(t,o,i={}){let{method:a="GET",body:s,query:d}=i;return await Q({BASE:X,VERSION:"1.0.0",WITH_CREDENTIALS:!1,CREDENTIALS:"omit",TOKEN:o,HEADERS:{"Content-Type":"application/json"}},{method:a,url:t,body:a==="POST"||a==="PUT"||a==="PATCH"?s:void 0,mediaType:"application/json",query:d})}function ee(t){return t instanceof Error&&"status"in t&&t.status===401}async function c(t,o,i={}){try{return await h(t,o.key,i)}catch(a){if(ee(a)&&o._refreshAuth){let s=await o._refreshAuth();return await h(t,s,i)}throw a}}var T=async(t,o)=>{let i=await c("/freeBusy",t,{method:"POST",body:{timeMin:o.timeMin,timeMax:o.timeMax,timeZone:o.timeZone,groupExpansionMax:o.groupExpansionMax,calendarExpansionMax:o.calendarExpansionMax,items:o.items}});return await te(t,"googlecalendar.calendar.getAvailability",{...o},"completed"),i};import{logEventFromContext as f}from"corsair/core";var I=async(t,o)=>{let i=o.calendarId||"primary",a=await c(`/calendars/${i}/events`,t,{method:"POST",body:o.event});if(a.id&&t.db.events)try{await t.db.events.upsertByEntityId(a.id,{...a,id:a.id,calendarId:i,createdAt:new Date})}catch(s){console.warn("Failed to save event to database:",s)}return await f(t,"googlecalendar.events.create",{...o},"completed"),a},x=async(t,o)=>{let i=o.calendarId||"primary",a=await c(`/calendars/${i}/events/${o.id}`,t,{method:"GET",query:{timeZone:o.timeZone,maxAttendees:o.maxAttendees}});if(a.id&&t.db.events)try{await t.db.events.upsertByEntityId(a.id,{...a,id:a.id,calendarId:i,createdAt:new Date})}catch(s){console.warn("Failed to save event to database:",s)}return await f(t,"googlecalendar.events.get",{...o},"completed"),a},P=async(t,o)=>{let i=o.calendarId||"primary",a=await c(`/calendars/${i}/events`,t,{method:"GET",query:o});if(a.items&&t.db.events)try{for(let s of a.items)s.id&&await t.db.events.upsertByEntityId(s.id,{...s,id:s.id,calendarId:i,createdAt:new Date})}catch(s){console.warn("Failed to save events to database:",s)}return await f(t,"googlecalendar.events.getMany",{...o},"completed"),a},A=async(t,o)=>{let i=o.calendarId||"primary",a=await c(`/calendars/${i}/events/${o.id}`,t,{method:"PUT",body:o.event,query:{sendUpdates:o.sendUpdates,sendNotifications:o.sendNotifications,conferenceDataVersion:o.conferenceDataVersion,maxAttendees:o.maxAttendees,supportsAttachments:o.supportsAttachments}});if(a.id&&t.db.events)try{await t.db.events.upsertByEntityId(a.id,{...a,id:a.id,calendarId:i,createdAt:new Date})}catch(s){console.warn("Failed to save event to database:",s)}return await f(t,"googlecalendar.events.update",{...o},"completed"),a},D=async(t,o)=>{let i=o.calendarId||"primary";if(await c(`/calendars/${i}/events/${o.id}`,t,{method:"DELETE",query:{sendUpdates:o.sendUpdates,sendNotifications:o.sendNotifications}}),t.db.events)try{await t.db.events.deleteByEntityId(o.id)}catch(a){console.warn("Failed to delete event from database:",a)}await f(t,"googlecalendar.events.delete",{...o},"completed")};import{z as e}from"zod";var v=e.object({date:e.string().optional().describe('All-day event date in "YYYY-MM-DD" format'),dateTime:e.string().optional().describe('RFC 3339 timestamp, e.g. "2024-12-25T10:00:00-07:00". Required for timed events.'),timeZone:e.string().optional().describe('IANA time zone name, e.g. "America/New_York"')}),M=e.object({id:e.string().optional(),email:e.string().optional().describe("Attendee's email address. Required when adding an attendee."),displayName:e.string().optional(),organizer:e.boolean().optional(),self:e.boolean().optional(),resource:e.boolean().optional(),optional:e.boolean().optional(),responseStatus:e.enum(["needsAction","declined","tentative","accepted"]).optional(),comment:e.string().optional(),additionalGuests:e.number().optional()}),O=e.object({id:e.string().optional(),email:e.string().optional(),displayName:e.string().optional(),self:e.boolean().optional()}),R=e.object({method:e.enum(["email","popup"]).optional(),minutes:e.number().optional()}),U=e.object({useDefault:e.boolean().optional().describe("Use the calendar default reminders"),overrides:e.array(R).optional().describe("Custom reminders. Only used when useDefault is false.")}),ae=e.object({fileUrl:e.string().optional(),title:e.string().optional(),mimeType:e.string().optional(),iconLink:e.string().optional(),fileId:e.string().optional()}),ie=e.object({createRequest:e.object({requestId:e.string().optional(),conferenceSolutionKey:e.object({type:e.string().optional()}).optional(),status:e.object({statusCode:e.string().optional()}).optional()}).optional(),entryPoints:e.array(e.object({entryPointType:e.string().optional(),uri:e.string().optional(),label:e.string().optional(),pin:e.string().optional(),accessCode:e.string().optional(),meetingCode:e.string().optional(),passcode:e.string().optional(),password:e.string().optional()})).optional(),conferenceSolution:e.object({key:e.object({type:e.string().optional()}).optional(),name:e.string().optional(),iconUri:e.string().optional()}).optional(),conferenceId:e.string().optional(),signature:e.string().optional(),notes:e.string().optional()}),N=e.object({summary:e.string().optional().describe("Title of the event"),description:e.string().optional().describe("Description of the event. HTML is supported."),location:e.string().optional().describe("Geographic location of the event as free-form text"),start:v.optional().describe('Start time. Use "date" for all-day events, "dateTime" for timed events.'),end:v.optional().describe('End time. Use "date" for all-day events, "dateTime" for timed events.'),attendees:e.array(M).optional().describe("List of attendees. Each attendee must include an email."),recurrence:e.array(e.string()).optional().describe('RRULE / EXDATE lines per RFC 5545, e.g. ["RRULE:FREQ=WEEKLY;COUNT=5"]'),colorId:e.string().optional().describe("Color ID (1\u201311). Use the colors endpoint to see options."),transparency:e.enum(["opaque","transparent"]).optional().describe('"opaque" blocks time on the calendar (default); "transparent" does not'),visibility:e.enum(["default","public","private","confidential"]).optional(),eventType:e.enum(["default","outOfOffice","focusTime","workingLocation","birthday","fromGmail"]).optional(),status:e.enum(["confirmed","tentative","cancelled"]).optional(),reminders:U.optional(),guestsCanModify:e.boolean().optional(),guestsCanInviteOthers:e.boolean().optional(),guestsCanSeeOtherGuests:e.boolean().optional(),anyoneCanAddSelf:e.boolean().optional(),sequence:e.number().optional().describe("Sequence number for recurring event updates"),originalStartTime:v.optional().describe("Original start time for a recurring event instance"),recurringEventId:e.string().optional().describe("ID of the recurring event this instance belongs to")}),re=e.object({calendarId:e.string().optional().describe('Calendar ID. Defaults to "primary".'),event:N.describe('Event body. Provide at minimum "summary", "start", and "end".'),sendUpdates:e.enum(["all","externalOnly","none"]).optional(),sendNotifications:e.boolean().optional(),conferenceDataVersion:e.number().optional(),maxAttendees:e.number().optional(),supportsAttachments:e.boolean().optional()}),se=e.object({calendarId:e.string().optional(),id:e.string(),timeZone:e.string().optional(),maxAttendees:e.number().optional()}),le=e.object({calendarId:e.string().optional(),timeMin:e.string().optional(),timeMax:e.string().optional(),timeZone:e.string().optional(),updatedMin:e.string().optional(),singleEvents:e.boolean().optional(),maxResults:e.number().optional(),pageToken:e.string().optional(),q:e.string().optional(),orderBy:e.enum(["startTime","updated"]).optional(),iCalUID:e.string().optional(),showDeleted:e.boolean().optional(),showHiddenInvitations:e.boolean().optional()}),de=e.object({calendarId:e.string().optional().describe('Calendar ID. Defaults to "primary".'),id:e.string().describe("Event ID to update"),event:N.describe("Updated event fields"),sendUpdates:e.enum(["all","externalOnly","none"]).optional(),sendNotifications:e.boolean().optional(),conferenceDataVersion:e.number().optional(),maxAttendees:e.number().optional(),supportsAttachments:e.boolean().optional()}),pe=e.object({calendarId:e.string().optional(),id:e.string(),sendUpdates:e.enum(["all","externalOnly","none"]).optional(),sendNotifications:e.boolean().optional()}),ce=e.object({timeMin:e.string(),timeMax:e.string(),timeZone:e.string().optional(),groupExpansionMax:e.number().optional(),calendarExpansionMax:e.number().optional(),items:e.array(e.object({id:e.string()})).optional()}),u={eventsCreate:re,eventsGet:se,eventsGetMany:le,eventsUpdate:de,eventsDelete:pe,calendarGetAvailability:ce},C=e.object({id:e.string().optional(),status:e.enum(["confirmed","tentative","cancelled"]).optional(),htmlLink:e.string().optional(),created:e.string().optional(),updated:e.string().optional(),summary:e.string().optional(),description:e.string().optional(),location:e.string().optional(),colorId:e.string().optional(),creator:O.optional(),organizer:O.optional(),start:v.optional(),end:v.optional(),endTimeUnspecified:e.boolean().optional(),recurrence:e.array(e.string()).optional(),recurringEventId:e.string().optional(),originalStartTime:v.optional(),transparency:e.enum(["opaque","transparent"]).optional(),visibility:e.enum(["default","public","private","confidential"]).optional(),iCalUID:e.string().optional(),sequence:e.number().optional(),attendees:e.array(M).optional(),attendeesOmitted:e.boolean().optional(),hangoutLink:e.string().optional(),reminders:U.optional(),anyoneCanAddSelf:e.boolean().optional(),guestsCanInviteOthers:e.boolean().optional(),guestsCanModify:e.boolean().optional(),guestsCanSeeOtherGuests:e.boolean().optional(),privateCopy:e.boolean().optional(),locked:e.boolean().optional(),conferenceData:ie.optional(),attachments:e.array(ae).optional(),source:e.object({url:e.string().optional(),title:e.string().optional()}).optional(),gadget:e.object({type:e.string().optional(),title:e.string().optional(),link:e.string().optional(),iconLink:e.string().optional(),width:e.number().optional(),height:e.number().optional(),display:e.string().optional(),preferences:e.record(e.string(),e.string()).optional()}).optional(),eventType:e.enum(["default","outOfOffice","focusTime","workingLocation","birthday","fromGmail"]).optional()}),ge=e.object({kind:e.string().optional(),etag:e.string().optional(),summary:e.string().optional(),description:e.string().optional(),updated:e.string().optional(),timeZone:e.string().optional(),accessRole:e.string().optional(),defaultReminders:e.array(R).optional(),nextPageToken:e.string().optional(),nextSyncToken:e.string().optional(),items:e.array(C).optional()}),ue=e.object({busy:e.array(e.object({start:e.string().optional(),end:e.string().optional()})).optional(),errors:e.array(e.object({domain:e.string().optional(),reason:e.string().optional()})).optional()}),me=e.object({kind:e.string().optional(),calendars:e.record(e.string(),ue).optional(),groups:e.record(e.string(),e.object({calendars:e.array(e.string()).optional(),errors:e.array(e.object({domain:e.string().optional(),reason:e.string().optional()})).optional()})).optional(),timeMin:e.string().optional(),timeMax:e.string().optional()}),m={eventsCreate:C,eventsGet:C,eventsGetMany:ge,eventsUpdate:C,eventsDelete:e.void(),calendarGetAvailability:me};var b={create:I,get:x,getMany:P,update:A,delete:D},W={getAvailability:T};import{z as n}from"zod";var G=n.object({date:n.string().optional(),dateTime:n.string().optional(),timeZone:n.string().optional()}),ve=n.object({id:n.string().optional(),email:n.string().optional(),displayName:n.string().optional(),organizer:n.boolean().optional(),self:n.boolean().optional(),resource:n.boolean().optional(),optional:n.boolean().optional(),responseStatus:n.enum(["needsAction","declined","tentative","accepted"]).optional(),comment:n.string().optional(),additionalGuests:n.number().optional()}),j=n.object({id:n.string().optional(),email:n.string().optional(),displayName:n.string().optional(),self:n.boolean().optional()}),be=n.object({method:n.enum(["email","popup"]).optional(),minutes:n.number().optional()}),ye=n.object({useDefault:n.boolean().optional(),overrides:n.array(be).optional()}),q=n.object({id:n.string(),status:n.enum(["confirmed","tentative","cancelled"]).optional(),htmlLink:n.string().optional(),created:n.string().optional(),updated:n.string().optional(),summary:n.string().optional(),description:n.string().optional(),location:n.string().optional(),colorId:n.string().optional(),creator:j.optional(),organizer:j.optional(),start:G.optional(),end:G.optional(),endTimeUnspecified:n.boolean().optional(),recurrence:n.array(n.string()).optional(),recurringEventId:n.string().optional(),originalStartTime:G.optional(),transparency:n.enum(["opaque","transparent"]).optional(),visibility:n.enum(["default","public","private","confidential"]).optional(),iCalUID:n.string().optional(),sequence:n.number().optional(),attendees:n.array(ve).optional(),attendeesOmitted:n.boolean().optional(),hangoutLink:n.string().optional(),reminders:ye.optional(),anyoneCanAddSelf:n.boolean().optional(),guestsCanInviteOthers:n.boolean().optional(),guestsCanModify:n.boolean().optional(),guestsCanSeeOtherGuests:n.boolean().optional(),privateCopy:n.boolean().optional(),locked:n.boolean().optional(),eventType:n.enum(["default","outOfOffice","focusTime","workingLocation","birthday","fromGmail"]).optional(),calendarId:n.string().optional(),createdAt:n.coerce.date().optional()}),B=n.object({id:n.string(),summary:n.string().optional(),description:n.string().optional(),location:n.string().optional(),timeZone:n.string().optional(),createdAt:n.coerce.date().optional()});var L={version:"1.0.0",entities:{events:q,calendars:B}};import{googleChannelSubscribe as he}from"corsair/core";var _=(t,o)=>he(t,{webhookUrl:o.webhookUrl,watchUrl:"https://www.googleapis.com/calendar/v3/calendars/primary/events/watch"});import{logEventFromContext as S}from"corsair/core";import{z as r}from"zod";var fe=r.object({data:r.string().optional(),attributes:r.record(r.string(),r.string()).optional(),messageId:r.string().optional(),publishTime:r.string().optional()}),z=r.object({message:fe.optional(),subscription:r.string().optional(),event:r.unknown().optional()}),lt=r.object({resourceId:r.string().optional(),resourceState:r.string().optional(),resourceUri:r.string().optional(),channelId:r.string().optional(),channelExpiration:r.string().optional(),channelToken:r.string().optional(),changed:r.string().optional()}),Ee=r.object({type:r.literal("eventCreated"),calendarId:r.string(),event:r.custom(),timestamp:r.string()}),Ce=r.object({type:r.literal("eventUpdated"),calendarId:r.string(),event:r.custom(),timestamp:r.string()}),Ge=r.object({type:r.literal("eventDeleted"),calendarId:r.string(),eventId:r.string(),timestamp:r.string()}),F=r.union([Ee,Ce,Ge]);function y(t){let o=Buffer.from(t,"base64").toString("utf-8");return JSON.parse(o)}function k(t){return o=>{let i=o.body;if(!i.message?.data)return!1;try{let a=y(i.message.data);return!!a.channelId&&!!a.resourceId&&!!a.resourceUri&&a.resourceUri.includes("/calendar/")}catch{return!1}}}var ke=/\/calendars\/([^\/\?]+)/,Se=/\/calendars\/[^\/]+\/events\/([^\/\?]+)/,we=120*1e3,Te=5e3;function Ie(t){return{calendarId:t.match(ke)?.[1],eventId:t.match(Se)?.[1]}}function xe(t){return!t.created||!t.updated?!1:Math.abs(new Date(t.updated).getTime()-new Date(t.created).getTime())<Te}function Pe(t,o,i){return h(`/calendars/${o}/events/${i}`,t,{method:"GET"})}async function H(t,o){let i=new Date(Date.now()-we).toISOString();return(await h(`/calendars/${o}/events`,t,{method:"GET",query:{maxResults:10,orderBy:"updated",updatedMin:i,showDeleted:!0}})).items??[]}async function Ae(t,o,i){return i?Pe(t,o,i):(await H(t,o)).at(-1)??null}async function De(t,o,i,a){return i||((await H(t,o)).find(p=>p.status==="cancelled")?.id??a.resourceId??"")}async function K(t,o,i){i&&t.db.events&&await t.db.events.deleteByEntityId(i);let a={type:"eventDeleted",calendarId:o,eventId:i,timestamp:new Date().toISOString()};return await S(t,"googlecalendar.webhook.eventDeleted",{...a},"completed"),{eventData:a}}var $={match:k("eventChanged"),handler:async(t,o)=>{let i=o.payload;if(!i.message?.data)return{success:!1,error:"No message data in notification"};let a=y(i.message.data);if(!a.resourceUri)return{success:!1,error:"Invalid push notification format"};let{calendarId:s,eventId:d}=Ie(a.resourceUri);if(!s)return{success:!1,error:"Could not parse calendar ID from resource URI"};let p=t.key;try{if(a.resourceState==="not_exists"){let g=await De(p,s,d,a),{eventData:J}=await K(t,s,g);return{success:!0,data:J}}let l=await Ae(p,s,d);if(!l)return{success:!1,error:"No recently updated events found in calendar"};if(l.status==="cancelled"){let{eventData:g}=await K(t,s,l.id??"");return{success:!0,data:g}}let E="";if(l.id&&t.db.events&&(E=(await t.db.events.upsertByEntityId(l.id,{...l,id:l.id,calendarId:s,createdAt:new Date}))?.id??""),xe(l)){let g={type:"eventCreated",calendarId:s,event:l,timestamp:new Date().toISOString()};return await S(t,"googlecalendar.webhook.eventCreated",{...g},"completed"),{success:!0,corsairEntityId:E,data:g}}let w={type:"eventUpdated",calendarId:s,event:l,timestamp:new Date().toISOString()};return await S(t,"googlecalendar.webhook.eventUpdated",{...w},"completed"),{success:!0,corsairEntityId:E,data:w}}catch(l){return console.error("Failed to process webhook:",l),{success:!1,error:`Failed to process event: ${l instanceof Error?l.message:"Unknown error"}`}}}};var Z={onEventChanged:$};import{asRecord as Oe,decodePubSubData as Me,firstString as V,getHeader as Re,readBodyRecord as Ue}from"corsair/core";function Y(t){let o=V([Re(t.headers,"x-goog-channel-id")]);if(o)return{linkType:"channel_id",externalId:o};let i=Ue(t);if(!i)return null;let a=Oe(Me(i)),s=V([a?.channelId]);return s?{linkType:"channel_id",externalId:s}:null}var je={events:{create:b.create,get:b.get,getMany:b.getMany,update:b.update,delete:b.delete},calendar:{getAvailability:W.getAvailability}},qe={"events.create":{input:u.eventsCreate,output:m.eventsCreate},"events.get":{input:u.eventsGet,output:m.eventsGet},"events.getMany":{input:u.eventsGetMany,output:m.eventsGetMany},"events.update":{input:u.eventsUpdate,output:m.eventsUpdate},"events.delete":{input:u.eventsDelete,output:m.eventsDelete},"calendar.getAvailability":{input:u.calendarGetAvailability,output:m.calendarGetAvailability}},Be={onEventChanged:Z.onEventChanged},Le={onEventChanged:{description:"A Google Calendar event was created, updated, or deleted",payload:z,response:F}},_e="oauth_2",ze={"events.create":{riskLevel:"write",description:"Create a new calendar event"},"events.get":{riskLevel:"read",description:"Get a specific calendar event"},"events.getMany":{riskLevel:"read",description:"List calendar events"},"events.update":{riskLevel:"write",description:"Update an existing calendar event"},"events.delete":{riskLevel:"destructive",description:"Delete a calendar event [DESTRUCTIVE]"},"calendar.getAvailability":{riskLevel:"read",description:"Get free/busy availability for a calendar"}},Fe={oauth_2:{account:["channel_id"]}};function It(t={}){let o={...t,authType:t.authType??_e};return{id:"googlecalendar",authConfig:Fe,schema:L,options:o,oauthConfig:{providerName:"Google",authUrl:"https://accounts.google.com/o/oauth2/v2/auth",tokenUrl:"https://oauth2.googleapis.com/token",scopes:["https://www.googleapis.com/auth/calendar"],authParams:{access_type:"offline",prompt:"consent"}},hooks:o.hooks,webhookHooks:o.webhookHooks,endpoints:je,webhooks:Be,endpointMeta:ze,endpointSchemas:qe,webhookSchemas:Le,keyBuilder:async i=>{let a=i.authType;if(o.key)return o.key;if(i.authType==="oauth_2")return We(i,{plugin:"googlecalendar",tokenUrl:"https://oauth2.googleapis.com/token"});throw new Ne("googlecalendar","oauth_2")},pluginWebhookMatcher:i=>{let a=i.headers;if(!(a.from==="noreply@google.com"||typeof a["user-agent"]=="string"&&a["user-agent"].includes("APIs-Google")))return!1;let d=i.body;if(!d?.message?.data)return!1;try{let p=y(d.message.data);return!!p.resourceUri&&p.resourceUri.includes("calendar")}catch{return!1}},pluginTenantWebhookMatcher:Y,subscribe:_}}export{k as createGoogleCalendarWebhookMatcher,y as decodePubSubMessage,Fe as googleCalendarAuthConfig,It as googlecalendar,qe as googlecalendarEndpointSchemas};
1
+ import{AuthMissingError as Ne,getOAuthAccessToken as We}from"corsair/core";import{logEventFromContext as te}from"corsair/core";import{request as Q}from"corsair/http";var X="https://www.googleapis.com/calendar/v3";async function h(t,o,i={}){let{method:a="GET",body:s,query:d}=i;return await Q({BASE:X,VERSION:"1.0.0",WITH_CREDENTIALS:!1,CREDENTIALS:"omit",TOKEN:o,HEADERS:{"Content-Type":"application/json"}},{method:a,url:t,body:a==="POST"||a==="PUT"||a==="PATCH"?s:void 0,mediaType:"application/json",query:d})}function ee(t){return t instanceof Error&&"status"in t&&t.status===401}async function c(t,o,i={}){try{return await h(t,o.key,i)}catch(a){if(ee(a)&&o._refreshAuth){let s=await o._refreshAuth();return await h(t,s,i)}throw a}}var T=async(t,o)=>{let i=await c("/freeBusy",t,{method:"POST",body:{timeMin:o.timeMin,timeMax:o.timeMax,timeZone:o.timeZone,groupExpansionMax:o.groupExpansionMax,calendarExpansionMax:o.calendarExpansionMax,items:o.items}});return await te(t,"googlecalendar.calendar.getAvailability",{...o},"completed"),i};import{logEventFromContext as f}from"corsair/core";var I=async(t,o)=>{let i=o.calendarId||"primary",a=await c(`/calendars/${i}/events`,t,{method:"POST",body:o.event});if(a.id&&t.db.events)try{await t.db.events.upsertByEntityId(a.id,{...a,id:a.id,calendarId:i,createdAt:new Date})}catch(s){console.warn("Failed to save event to database:",s)}return await f(t,"googlecalendar.events.create",{...o},"completed"),a},x=async(t,o)=>{let i=o.calendarId||"primary",a=await c(`/calendars/${i}/events/${o.id}`,t,{method:"GET",query:{timeZone:o.timeZone,maxAttendees:o.maxAttendees}});if(a.id&&t.db.events)try{await t.db.events.upsertByEntityId(a.id,{...a,id:a.id,calendarId:i,createdAt:new Date})}catch(s){console.warn("Failed to save event to database:",s)}return await f(t,"googlecalendar.events.get",{...o},"completed"),a},P=async(t,o)=>{let i=o.calendarId||"primary",a=await c(`/calendars/${i}/events`,t,{method:"GET",query:o});if(a.items&&t.db.events)try{for(let s of a.items)s.id&&await t.db.events.upsertByEntityId(s.id,{...s,id:s.id,calendarId:i,createdAt:new Date})}catch(s){console.warn("Failed to save events to database:",s)}return await f(t,"googlecalendar.events.getMany",{...o},"completed"),a},A=async(t,o)=>{let i=o.calendarId||"primary",a=await c(`/calendars/${i}/events/${o.id}`,t,{method:"PUT",body:o.event,query:{sendUpdates:o.sendUpdates,sendNotifications:o.sendNotifications,conferenceDataVersion:o.conferenceDataVersion,maxAttendees:o.maxAttendees,supportsAttachments:o.supportsAttachments}});if(a.id&&t.db.events)try{await t.db.events.upsertByEntityId(a.id,{...a,id:a.id,calendarId:i,createdAt:new Date})}catch(s){console.warn("Failed to save event to database:",s)}return await f(t,"googlecalendar.events.update",{...o},"completed"),a},D=async(t,o)=>{let i=o.calendarId||"primary";if(await c(`/calendars/${i}/events/${o.id}`,t,{method:"DELETE",query:{sendUpdates:o.sendUpdates,sendNotifications:o.sendNotifications}}),t.db.events)try{await t.db.events.deleteByEntityId(o.id)}catch(a){console.warn("Failed to delete event from database:",a)}await f(t,"googlecalendar.events.delete",{...o},"completed")};import{z as e}from"zod";var v=e.object({date:e.string().optional().describe('All-day event date in "YYYY-MM-DD" format'),dateTime:e.string().optional().describe('RFC 3339 timestamp, e.g. "2024-12-25T10:00:00-07:00". Required for timed events.'),timeZone:e.string().optional().describe('IANA time zone name, e.g. "America/New_York"')}),M=e.object({id:e.string().optional(),email:e.string().optional().describe("Attendee's email address. Required when adding an attendee."),displayName:e.string().optional(),organizer:e.boolean().optional(),self:e.boolean().optional(),resource:e.boolean().optional(),optional:e.boolean().optional(),responseStatus:e.enum(["needsAction","declined","tentative","accepted"]).optional(),comment:e.string().optional(),additionalGuests:e.number().optional()}),O=e.object({id:e.string().optional(),email:e.string().optional(),displayName:e.string().optional(),self:e.boolean().optional()}),R=e.object({method:e.enum(["email","popup"]).optional(),minutes:e.number().optional()}),U=e.object({useDefault:e.boolean().optional().describe("Use the calendar default reminders"),overrides:e.array(R).optional().describe("Custom reminders. Only used when useDefault is false.")}),ae=e.object({fileUrl:e.string().optional(),title:e.string().optional(),mimeType:e.string().optional(),iconLink:e.string().optional(),fileId:e.string().optional()}),ie=e.object({createRequest:e.object({requestId:e.string().optional(),conferenceSolutionKey:e.object({type:e.string().optional()}).optional(),status:e.object({statusCode:e.string().optional()}).optional()}).optional(),entryPoints:e.array(e.object({entryPointType:e.string().optional(),uri:e.string().optional(),label:e.string().optional(),pin:e.string().optional(),accessCode:e.string().optional(),meetingCode:e.string().optional(),passcode:e.string().optional(),password:e.string().optional()})).optional(),conferenceSolution:e.object({key:e.object({type:e.string().optional()}).optional(),name:e.string().optional(),iconUri:e.string().optional()}).optional(),conferenceId:e.string().optional(),signature:e.string().optional(),notes:e.string().optional()}),N=e.object({summary:e.string().optional().describe("Title of the event"),description:e.string().optional().describe("Description of the event. HTML is supported."),location:e.string().optional().describe("Geographic location of the event as free-form text"),start:v.optional().describe('Start time. Use "date" for all-day events, "dateTime" for timed events.'),end:v.optional().describe('End time. Use "date" for all-day events, "dateTime" for timed events.'),attendees:e.array(M).optional().describe("List of attendees. Each attendee must include an email."),recurrence:e.array(e.string()).optional().describe('RRULE / EXDATE lines per RFC 5545, e.g. ["RRULE:FREQ=WEEKLY;COUNT=5"]'),colorId:e.string().optional().describe("Color ID (1\u201311). Use the colors endpoint to see options."),transparency:e.enum(["opaque","transparent"]).optional().describe('"opaque" blocks time on the calendar (default); "transparent" does not'),visibility:e.enum(["default","public","private","confidential"]).optional(),eventType:e.enum(["default","outOfOffice","focusTime","workingLocation","birthday","fromGmail"]).optional(),status:e.enum(["confirmed","tentative","cancelled"]).optional(),reminders:U.optional(),guestsCanModify:e.boolean().optional(),guestsCanInviteOthers:e.boolean().optional(),guestsCanSeeOtherGuests:e.boolean().optional(),anyoneCanAddSelf:e.boolean().optional(),sequence:e.number().optional().describe("Sequence number for recurring event updates"),originalStartTime:v.optional().describe("Original start time for a recurring event instance"),recurringEventId:e.string().optional().describe("ID of the recurring event this instance belongs to")}),re=e.object({calendarId:e.string().optional().describe('Calendar ID. Defaults to "primary".'),event:N.describe('Event body. Provide at minimum "summary", "start", and "end".'),sendUpdates:e.enum(["all","externalOnly","none"]).optional(),sendNotifications:e.boolean().optional(),conferenceDataVersion:e.number().optional(),maxAttendees:e.number().optional(),supportsAttachments:e.boolean().optional()}),se=e.object({calendarId:e.string().optional(),id:e.string(),timeZone:e.string().optional(),maxAttendees:e.number().optional()}),le=e.object({calendarId:e.string().optional(),timeMin:e.string().optional(),timeMax:e.string().optional(),timeZone:e.string().optional(),updatedMin:e.string().optional(),singleEvents:e.boolean().optional(),maxResults:e.number().optional(),pageToken:e.string().optional(),q:e.string().optional(),orderBy:e.enum(["startTime","updated"]).optional(),iCalUID:e.string().optional(),showDeleted:e.boolean().optional(),showHiddenInvitations:e.boolean().optional()}),de=e.object({calendarId:e.string().optional().describe('Calendar ID. Defaults to "primary".'),id:e.string().describe("Event ID to update"),event:N.describe("Updated event fields"),sendUpdates:e.enum(["all","externalOnly","none"]).optional(),sendNotifications:e.boolean().optional(),conferenceDataVersion:e.number().optional(),maxAttendees:e.number().optional(),supportsAttachments:e.boolean().optional()}),pe=e.object({calendarId:e.string().optional(),id:e.string(),sendUpdates:e.enum(["all","externalOnly","none"]).optional(),sendNotifications:e.boolean().optional()}),ce=e.object({timeMin:e.string(),timeMax:e.string(),timeZone:e.string().optional(),groupExpansionMax:e.number().optional(),calendarExpansionMax:e.number().optional(),items:e.array(e.object({id:e.string()})).optional()}),u={eventsCreate:re,eventsGet:se,eventsGetMany:le,eventsUpdate:de,eventsDelete:pe,calendarGetAvailability:ce},C=e.object({id:e.string().optional(),status:e.enum(["confirmed","tentative","cancelled"]).optional(),htmlLink:e.string().optional(),created:e.string().optional(),updated:e.string().optional(),summary:e.string().optional(),description:e.string().optional(),location:e.string().optional(),colorId:e.string().optional(),creator:O.optional(),organizer:O.optional(),start:v.optional(),end:v.optional(),endTimeUnspecified:e.boolean().optional(),recurrence:e.array(e.string()).optional(),recurringEventId:e.string().optional(),originalStartTime:v.optional(),transparency:e.enum(["opaque","transparent"]).optional(),visibility:e.enum(["default","public","private","confidential"]).optional(),iCalUID:e.string().optional(),sequence:e.number().optional(),attendees:e.array(M).optional(),attendeesOmitted:e.boolean().optional(),hangoutLink:e.string().optional(),reminders:U.optional(),anyoneCanAddSelf:e.boolean().optional(),guestsCanInviteOthers:e.boolean().optional(),guestsCanModify:e.boolean().optional(),guestsCanSeeOtherGuests:e.boolean().optional(),privateCopy:e.boolean().optional(),locked:e.boolean().optional(),conferenceData:ie.optional(),attachments:e.array(ae).optional(),source:e.object({url:e.string().optional(),title:e.string().optional()}).optional(),gadget:e.object({type:e.string().optional(),title:e.string().optional(),link:e.string().optional(),iconLink:e.string().optional(),width:e.number().optional(),height:e.number().optional(),display:e.string().optional(),preferences:e.record(e.string(),e.string()).optional()}).optional(),eventType:e.enum(["default","outOfOffice","focusTime","workingLocation","birthday","fromGmail"]).optional()}),ge=e.object({kind:e.string().optional(),etag:e.string().optional(),summary:e.string().optional(),description:e.string().optional(),updated:e.string().optional(),timeZone:e.string().optional(),accessRole:e.string().optional(),defaultReminders:e.array(R).optional(),nextPageToken:e.string().optional(),nextSyncToken:e.string().optional(),items:e.array(C).optional()}),ue=e.object({busy:e.array(e.object({start:e.string().optional(),end:e.string().optional()})).optional(),errors:e.array(e.object({domain:e.string().optional(),reason:e.string().optional()})).optional()}),me=e.object({kind:e.string().optional(),calendars:e.record(e.string(),ue).optional(),groups:e.record(e.string(),e.object({calendars:e.array(e.string()).optional(),errors:e.array(e.object({domain:e.string().optional(),reason:e.string().optional()})).optional()})).optional(),timeMin:e.string().optional(),timeMax:e.string().optional()}),m={eventsCreate:C,eventsGet:C,eventsGetMany:ge,eventsUpdate:C,eventsDelete:e.void(),calendarGetAvailability:me};var b={create:I,get:x,getMany:P,update:A,delete:D},W={getAvailability:T};import{z as n}from"zod";var G=n.object({date:n.string().optional(),dateTime:n.string().optional(),timeZone:n.string().optional()}),ve=n.object({id:n.string().optional(),email:n.string().optional(),displayName:n.string().optional(),organizer:n.boolean().optional(),self:n.boolean().optional(),resource:n.boolean().optional(),optional:n.boolean().optional(),responseStatus:n.enum(["needsAction","declined","tentative","accepted"]).optional(),comment:n.string().optional(),additionalGuests:n.number().optional()}),j=n.object({id:n.string().optional(),email:n.string().optional(),displayName:n.string().optional(),self:n.boolean().optional()}),be=n.object({method:n.enum(["email","popup"]).optional(),minutes:n.number().optional()}),ye=n.object({useDefault:n.boolean().optional(),overrides:n.array(be).optional()}),q=n.object({id:n.string(),status:n.enum(["confirmed","tentative","cancelled"]).optional(),htmlLink:n.string().optional(),created:n.string().optional(),updated:n.string().optional(),summary:n.string().optional(),description:n.string().optional(),location:n.string().optional(),colorId:n.string().optional(),creator:j.optional(),organizer:j.optional(),start:G.optional(),end:G.optional(),endTimeUnspecified:n.boolean().optional(),recurrence:n.array(n.string()).optional(),recurringEventId:n.string().optional(),originalStartTime:G.optional(),transparency:n.enum(["opaque","transparent"]).optional(),visibility:n.enum(["default","public","private","confidential"]).optional(),iCalUID:n.string().optional(),sequence:n.number().optional(),attendees:n.array(ve).optional(),attendeesOmitted:n.boolean().optional(),hangoutLink:n.string().optional(),reminders:ye.optional(),anyoneCanAddSelf:n.boolean().optional(),guestsCanInviteOthers:n.boolean().optional(),guestsCanModify:n.boolean().optional(),guestsCanSeeOtherGuests:n.boolean().optional(),privateCopy:n.boolean().optional(),locked:n.boolean().optional(),eventType:n.enum(["default","outOfOffice","focusTime","workingLocation","birthday","fromGmail"]).optional(),calendarId:n.string().optional(),createdAt:n.coerce.date().optional()}),B=n.object({id:n.string(),summary:n.string().optional(),description:n.string().optional(),location:n.string().optional(),timeZone:n.string().optional(),createdAt:n.coerce.date().optional()});var L={version:"1.0.0",entities:{events:q,calendars:B}};import{googleChannelSubscribe as he}from"corsair/core";var _=(t,o)=>he(t,{webhookUrl:o.webhookUrl,watchUrl:"https://www.googleapis.com/calendar/v3/calendars/primary/events/watch"});import{logEventFromContext as S}from"corsair/core";import{z as r}from"zod";var fe=r.object({data:r.string().optional(),attributes:r.record(r.string(),r.string()).optional(),messageId:r.string().optional(),publishTime:r.string().optional()}),z=r.object({message:fe.optional(),subscription:r.string().optional(),event:r.unknown().optional()}),lt=r.object({resourceId:r.string().optional(),resourceState:r.string().optional(),resourceUri:r.string().optional(),channelId:r.string().optional(),channelExpiration:r.string().optional(),channelToken:r.string().optional(),changed:r.string().optional()}),Ee=r.object({type:r.literal("eventCreated"),calendarId:r.string(),event:r.custom(),timestamp:r.string()}),Ce=r.object({type:r.literal("eventUpdated"),calendarId:r.string(),event:r.custom(),timestamp:r.string()}),Ge=r.object({type:r.literal("eventDeleted"),calendarId:r.string(),eventId:r.string(),timestamp:r.string()}),F=r.union([Ee,Ce,Ge]);function y(t){let o=Buffer.from(t,"base64").toString("utf-8");return JSON.parse(o)}function k(t){return o=>{let i=o.body;if(!i.message?.data)return!1;try{let a=y(i.message.data);return!!a.channelId&&!!a.resourceId&&!!a.resourceUri&&a.resourceUri.includes("/calendar/")}catch{return!1}}}var ke=/\/calendars\/([^\/\?]+)/,Se=/\/calendars\/[^\/]+\/events\/([^\/\?]+)/,we=120*1e3,Te=5e3;function Ie(t){return{calendarId:t.match(ke)?.[1],eventId:t.match(Se)?.[1]}}function xe(t){return!t.created||!t.updated?!1:Math.abs(new Date(t.updated).getTime()-new Date(t.created).getTime())<Te}function Pe(t,o,i){return h(`/calendars/${o}/events/${i}`,t,{method:"GET"})}async function H(t,o){let i=new Date(Date.now()-we).toISOString();return(await h(`/calendars/${o}/events`,t,{method:"GET",query:{maxResults:10,orderBy:"updated",updatedMin:i,showDeleted:!0}})).items??[]}async function Ae(t,o,i){return i?Pe(t,o,i):(await H(t,o)).at(-1)??null}async function De(t,o,i,a){return i||((await H(t,o)).find(p=>p.status==="cancelled")?.id??a.resourceId??"")}async function K(t,o,i){i&&t.db.events&&await t.db.events.deleteByEntityId(i);let a={type:"eventDeleted",calendarId:o,eventId:i,timestamp:new Date().toISOString()};return await S(t,"googlecalendar.webhook.eventDeleted",{...a},"completed"),{eventData:a}}var $={match:k("eventChanged"),handler:async(t,o)=>{let i=o.payload;if(!i.message?.data)return{success:!1,error:"No message data in notification"};let a=y(i.message.data);if(!a.resourceUri)return{success:!1,error:"Invalid push notification format"};let{calendarId:s,eventId:d}=Ie(a.resourceUri);if(!s)return{success:!1,error:"Could not parse calendar ID from resource URI"};let p=t.key;try{if(a.resourceState==="not_exists"){let g=await De(p,s,d,a),{eventData:J}=await K(t,s,g);return{success:!0,data:J}}let l=await Ae(p,s,d);if(!l)return{success:!1,error:"No recently updated events found in calendar"};if(l.status==="cancelled"){let{eventData:g}=await K(t,s,l.id??"");return{success:!0,data:g}}let E="";if(l.id&&t.db.events&&(E=(await t.db.events.upsertByEntityId(l.id,{...l,id:l.id,calendarId:s,createdAt:new Date}))?.id??""),xe(l)){let g={type:"eventCreated",calendarId:s,event:l,timestamp:new Date().toISOString()};return await S(t,"googlecalendar.webhook.eventCreated",{...g},"completed"),{success:!0,corsairEntityId:E,data:g}}let w={type:"eventUpdated",calendarId:s,event:l,timestamp:new Date().toISOString()};return await S(t,"googlecalendar.webhook.eventUpdated",{...w},"completed"),{success:!0,corsairEntityId:E,data:w}}catch(l){return console.error("Failed to process webhook:",l),{success:!1,error:`Failed to process event: ${l instanceof Error?l.message:"Unknown error"}`}}}};var Z={onEventChanged:$};import{asRecord as Oe,decodePubSubData as Me,firstString as V,getHeader as Re,readBodyRecord as Ue}from"corsair/core";function Y(t){let o=V([Re(t.headers,"x-goog-channel-id")]);if(o)return{linkType:"channel_id",externalId:o};let i=Ue(t);if(!i)return null;let a=Oe(Me(i)),s=V([a?.channelId]);return s?{linkType:"channel_id",externalId:s}:null}var je={events:{create:b.create,get:b.get,getMany:b.getMany,update:b.update,delete:b.delete},calendar:{getAvailability:W.getAvailability}},qe={"events.create":{input:u.eventsCreate,output:m.eventsCreate},"events.get":{input:u.eventsGet,output:m.eventsGet},"events.getMany":{input:u.eventsGetMany,output:m.eventsGetMany},"events.update":{input:u.eventsUpdate,output:m.eventsUpdate},"events.delete":{input:u.eventsDelete,output:m.eventsDelete},"calendar.getAvailability":{input:u.calendarGetAvailability,output:m.calendarGetAvailability}},Be={onEventChanged:Z.onEventChanged},Le={onEventChanged:{description:"A Google Calendar event was created, updated, or deleted",payload:z,response:F}},_e="oauth_2",ze={"events.create":{riskLevel:"write",description:"Create a new calendar event"},"events.get":{riskLevel:"read",description:"Get a specific calendar event"},"events.getMany":{riskLevel:"read",description:"List calendar events"},"events.update":{riskLevel:"write",description:"Update an existing calendar event"},"events.delete":{riskLevel:"destructive",description:"Delete a calendar event"},"calendar.getAvailability":{riskLevel:"read",description:"Get free/busy availability for a calendar"}},Fe={oauth_2:{account:["channel_id"]}};function It(t={}){let o={...t,authType:t.authType??_e};return{id:"googlecalendar",authConfig:Fe,schema:L,options:o,oauthConfig:{providerName:"Google",authUrl:"https://accounts.google.com/o/oauth2/v2/auth",tokenUrl:"https://oauth2.googleapis.com/token",scopes:["https://www.googleapis.com/auth/calendar"],authParams:{access_type:"offline",prompt:"consent"}},hooks:o.hooks,webhookHooks:o.webhookHooks,endpoints:je,webhooks:Be,endpointMeta:ze,endpointSchemas:qe,webhookSchemas:Le,keyBuilder:async i=>{let a=i.authType;if(o.key)return o.key;if(i.authType==="oauth_2")return We(i,{plugin:"googlecalendar",tokenUrl:"https://oauth2.googleapis.com/token"});throw new Ne("googlecalendar","oauth_2")},pluginWebhookMatcher:i=>{let a=i.headers;if(!(a.from==="noreply@google.com"||typeof a["user-agent"]=="string"&&a["user-agent"].includes("APIs-Google")))return!1;let d=i.body;if(!d?.message?.data)return!1;try{let p=y(d.message.data);return!!p.resourceUri&&p.resourceUri.includes("calendar")}catch{return!1}},pluginTenantWebhookMatcher:Y,subscribe:_}}export{k as createGoogleCalendarWebhookMatcher,y as decodePubSubMessage,Fe as googleCalendarAuthConfig,It as googlecalendar,qe as googlecalendarEndpointSchemas};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corsair-dev/googlecalendar",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Google calendar plugin for Corsair",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,18 +28,24 @@
28
28
  "typescript": "^5.9.3",
29
29
  "zod": "^4.1.13",
30
30
  "jest": "^29.7.0",
31
- "corsair": "0.1.120"
31
+ "corsair": "0.1.129"
32
32
  },
33
33
  "keywords": [
34
34
  "corsair",
35
35
  "googlecalendar",
36
36
  "plugin"
37
37
  ],
38
- "author": "",
38
+ "author": "Corsair (https://corsair.dev)",
39
39
  "license": "Apache-2.0",
40
40
  "files": [
41
41
  "dist"
42
42
  ],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/corsairdev/corsair.git",
46
+ "directory": "packages/googlecalendar"
47
+ },
48
+ "homepage": "https://docs.corsair.dev/plugins/googlecalendar",
43
49
  "scripts": {
44
50
  "build": "rm -rf dist && tsc --build --force && tsup",
45
51
  "typecheck": "tsc --noEmit",