diff --git a/docs/api-reference.md b/docs/api-reference.md index c087f61..1c5f8f6 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -510,6 +510,13 @@ interface Event { availability: string; /** External identifier for the event, useful for external sync services */ externalIdentifier: string | null; + /** Organizer info when available from EventKit */ + organizer: { + name: string | null; + email: string | null; + } | null; + /** JSON-encoded attendee list with name/email/status/role/type/isCurrentUser */ + attendeesJson: string; } ``` @@ -674,4 +681,4 @@ if (success) { } else { console.log('Reminder not found or could not be removed'); } -``` \ No newline at end of file +``` diff --git a/index.d.ts b/index.d.ts index 23c09d2..0f58ec9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -270,6 +270,13 @@ export interface Event { availability: 'free' | 'busy' | 'tentative' | 'unavailable' | 'unknown'; /** External identifier for the event */ externalIdentifier: string | null; + /** Organizer info when available from EventKit */ + organizer: { + name: string | null; + email: string | null; + } | null; + /** JSON-encoded attendee list with name/email/status/role/type/isCurrentUser */ + attendeesJson: string; } /** @@ -502,4 +509,4 @@ export function getCalendarItemsWithExternalIdentifier(externalIdentifier: strin * @returns The current authorization status * @see https://developer.apple.com/documentation/eventkit/ekauthorizationstatus */ -export function getAuthorizationStatus(entityType: EntityType): AuthorizationStatus; \ No newline at end of file +export function getAuthorizationStatus(entityType: EntityType): AuthorizationStatus; diff --git a/index.ts b/index.ts index bbd86ed..5acba9c 100644 --- a/index.ts +++ b/index.ts @@ -120,6 +120,13 @@ export interface Event { availability: 'free' | 'busy' | 'tentative' | 'unavailable' | 'unknown'; /** External identifier for the event, useful for external sync services */ externalIdentifier: string | null; + /** Organizer info when available from EventKit */ + organizer: { + name: string | null; + email: string | null; + } | null; + /** JSON-encoded attendee list with name/email/status/role/type/isCurrentUser */ + attendeesJson: string; } /** @@ -713,4 +720,4 @@ export function saveReminder(reminderData: ReminderData, commit: boolean = true) */ export function getAuthorizationStatus(entityType: EntityType): AuthorizationStatus { return nativeModule.getAuthorizationStatus(entityType); -} \ No newline at end of file +} diff --git a/node-addon/EventKitBridge.swift b/node-addon/EventKitBridge.swift index 4231cbb..e7023ff 100644 --- a/node-addon/EventKitBridge.swift +++ b/node-addon/EventKitBridge.swift @@ -383,6 +383,9 @@ import Foundation @objc public let hasAlarms: Bool @objc public let availability: String @objc public let externalIdentifier: String? + @objc public let organizerName: String? + @objc public let organizerEmail: String? + @objc public let attendeesJson: String init(from ekEvent: EKEvent) { self.id = ekEvent.eventIdentifier @@ -397,6 +400,71 @@ import Foundation self.url = ekEvent.url?.absoluteString self.hasAlarms = ekEvent.hasAlarms self.externalIdentifier = ekEvent.calendarItemExternalIdentifier + if let organizer = ekEvent.organizer { + self.organizerName = organizer.name + let raw = organizer.url.absoluteString + if raw.lowercased().hasPrefix("mailto:") { + self.organizerEmail = String(raw.dropFirst("mailto:".count)) + } else { + self.organizerEmail = nil + } + } else { + self.organizerName = nil + self.organizerEmail = nil + } + + let attendeesArray: [[String: Any]] = (ekEvent.attendees ?? []).map { attendee in + let raw = attendee.url.absoluteString + let email: String? = raw.lowercased().hasPrefix("mailto:") + ? String(raw.dropFirst("mailto:".count)) + : nil + + let status: String + switch attendee.participantStatus { + case .accepted: status = "accepted" + case .declined: status = "declined" + case .tentative: status = "tentative" + case .pending: status = "pending" + case .delegated: status = "delegated" + case .completed: status = "completed" + case .inProcess: status = "inProcess" + default: status = "unknown" + } + + let role: String + switch attendee.participantRole { + case .required: role = "required" + case .optional: role = "optional" + case .chair: role = "chair" + case .nonParticipant: role = "nonParticipant" + default: role = "unknown" + } + + let type: String + switch attendee.participantType { + case .person: type = "person" + case .room: type = "room" + case .resource: type = "resource" + case .group: type = "group" + default: type = "unknown" + } + + return [ + "name": attendee.name ?? "", + "email": email ?? "", + "url": raw, + "status": status, + "role": role, + "type": type, + "isCurrentUser": attendee.isCurrentUser + ] + } + if let data = try? JSONSerialization.data(withJSONObject: attendeesArray, options: []), + let json = String(data: data, encoding: .utf8) { + self.attendeesJson = json + } else { + self.attendeesJson = "[]" + } // Convert availability to string switch ekEvent.availability { @@ -850,4 +918,4 @@ import Foundation return ["success": false, "error": error.localizedDescription] } } -} \ No newline at end of file +} diff --git a/node-addon/eventkit_binding.mm b/node-addon/eventkit_binding.mm index 11500f3..e080ed8 100644 --- a/node-addon/eventkit_binding.mm +++ b/node-addon/eventkit_binding.mm @@ -709,7 +709,6 @@ void SetResult(bool success, const std::string& calendarIdOrError) { return promise; } -// Helper to convert Event object to JS object Napi::Object EventToJSObject(const Napi::CallbackInfo& info, Event *event) { Napi::Env env = info.Env(); Napi::Object jsObject = Napi::Object::New(env); @@ -753,6 +752,23 @@ void SetResult(bool success, const std::string& calendarIdOrError) { } else { jsObject.Set("externalIdentifier", env.Null()); } + + if (event.organizerName || event.organizerEmail) { + Napi::Object organizer = Napi::Object::New(env); + if (event.organizerName) organizer.Set("name", Napi::String::New(env, [event.organizerName UTF8String])); + else organizer.Set("name", env.Null()); + if (event.organizerEmail) organizer.Set("email", Napi::String::New(env, [event.organizerEmail UTF8String])); + else organizer.Set("email", env.Null()); + jsObject.Set("organizer", organizer); + } else { + jsObject.Set("organizer", env.Null()); + } + + if (event.attendeesJson) { + jsObject.Set("attendeesJson", Napi::String::New(env, [event.attendeesJson UTF8String])); + } else { + jsObject.Set("attendeesJson", Napi::String::New(env, "[]")); + } return jsObject; }