digital-tools 2.0.2 → 2.1.1

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +3 -4
  3. package/src/define.js +267 -0
  4. package/src/entities/advertising.js +999 -0
  5. package/src/entities/ai.js +756 -0
  6. package/src/entities/analytics.js +1588 -0
  7. package/src/entities/automation.js +601 -0
  8. package/src/entities/communication.js +1150 -0
  9. package/src/entities/crm.js +1386 -0
  10. package/src/entities/design.js +546 -0
  11. package/src/entities/development.js +2212 -0
  12. package/src/entities/document.js +874 -0
  13. package/src/entities/ecommerce.js +1429 -0
  14. package/src/entities/experiment.js +1039 -0
  15. package/src/entities/finance.js +3478 -0
  16. package/src/entities/forms.js +1892 -0
  17. package/src/entities/hr.js +661 -0
  18. package/src/entities/identity.js +997 -0
  19. package/src/entities/index.js +282 -0
  20. package/src/entities/infrastructure.js +1153 -0
  21. package/src/entities/knowledge.js +1438 -0
  22. package/src/entities/marketing.js +1610 -0
  23. package/src/entities/media.js +1634 -0
  24. package/src/entities/notification.js +1199 -0
  25. package/src/entities/presentation.js +1274 -0
  26. package/src/entities/productivity.js +1317 -0
  27. package/src/entities/project-management.js +1136 -0
  28. package/src/entities/recruiting.js +736 -0
  29. package/src/entities/shipping.js +509 -0
  30. package/src/entities/signature.js +1102 -0
  31. package/src/entities/site.js +222 -0
  32. package/src/entities/spreadsheet.js +1341 -0
  33. package/src/entities/storage.js +1198 -0
  34. package/src/entities/support.js +1166 -0
  35. package/src/entities/video-conferencing.js +1750 -0
  36. package/src/entities/video.js +950 -0
  37. package/src/entities.js +1663 -0
  38. package/src/index.js +74 -0
  39. package/src/providers/analytics/index.js +17 -0
  40. package/src/providers/analytics/mixpanel.js +255 -0
  41. package/src/providers/calendar/cal-com.js +303 -0
  42. package/src/providers/calendar/google-calendar.js +335 -0
  43. package/src/providers/calendar/index.js +20 -0
  44. package/src/providers/crm/hubspot.js +566 -0
  45. package/src/providers/crm/index.js +17 -0
  46. package/src/providers/development/github.js +472 -0
  47. package/src/providers/development/index.js +17 -0
  48. package/src/providers/ecommerce/index.js +17 -0
  49. package/src/providers/ecommerce/shopify.js +378 -0
  50. package/src/providers/email/index.js +20 -0
  51. package/src/providers/email/resend.js +258 -0
  52. package/src/providers/email/sendgrid.js +161 -0
  53. package/src/providers/finance/index.js +17 -0
  54. package/src/providers/finance/stripe.js +549 -0
  55. package/src/providers/forms/index.js +17 -0
  56. package/src/providers/forms/typeform.js +500 -0
  57. package/src/providers/index.js +123 -0
  58. package/src/providers/knowledge/index.js +17 -0
  59. package/src/providers/knowledge/notion.js +389 -0
  60. package/src/providers/marketing/index.js +17 -0
  61. package/src/providers/marketing/mailchimp.js +443 -0
  62. package/src/providers/media/cloudinary.js +318 -0
  63. package/src/providers/media/index.js +17 -0
  64. package/src/providers/messaging/index.js +20 -0
  65. package/src/providers/messaging/slack.js +393 -0
  66. package/src/providers/messaging/twilio-sms.js +249 -0
  67. package/src/providers/project-management/index.js +17 -0
  68. package/src/providers/project-management/linear.js +575 -0
  69. package/src/providers/registry.js +86 -0
  70. package/src/providers/spreadsheet/google-sheets.js +375 -0
  71. package/src/providers/spreadsheet/index.js +20 -0
  72. package/src/providers/spreadsheet/xlsx.js +423 -0
  73. package/src/providers/storage/index.js +24 -0
  74. package/src/providers/storage/s3.js +419 -0
  75. package/src/providers/support/index.js +17 -0
  76. package/src/providers/support/zendesk.js +373 -0
  77. package/src/providers/tasks/index.js +17 -0
  78. package/src/providers/tasks/todoist.js +286 -0
  79. package/src/providers/types.js +9 -0
  80. package/src/providers/video-conferencing/google-meet.js +286 -0
  81. package/src/providers/video-conferencing/index.js +31 -0
  82. package/src/providers/video-conferencing/jitsi.js +254 -0
  83. package/src/providers/video-conferencing/teams.js +270 -0
  84. package/src/providers/video-conferencing/zoom.js +332 -0
  85. package/src/registry.js +128 -0
  86. package/src/tools/communication.js +184 -0
  87. package/src/tools/data.js +205 -0
  88. package/src/tools/index.js +11 -0
  89. package/src/tools/web.js +137 -0
  90. package/src/types.js +10 -0
  91. package/test/define.test.js +306 -0
  92. package/test/registry.test.js +357 -0
  93. package/test/tools.test.js +363 -0
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Google Meet Video Conferencing Provider
3
+ *
4
+ * Concrete implementation of VideoConferencingProvider using Google Calendar API v3
5
+ * to create and manage Google Meet video conferences.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ import { defineProvider } from '../registry.js';
10
+ const CALENDAR_API_URL = 'https://www.googleapis.com/calendar/v3';
11
+ /**
12
+ * Google Meet provider info
13
+ */
14
+ export const googleMeetInfo = {
15
+ id: 'meeting.google-meet',
16
+ name: 'Google Meet',
17
+ description: 'Google Meet video conferencing via Google Calendar API',
18
+ category: 'video-conferencing',
19
+ website: 'https://meet.google.com',
20
+ docsUrl: 'https://developers.google.com/calendar/api',
21
+ requiredConfig: ['accessToken'],
22
+ optionalConfig: ['clientId', 'clientSecret', 'refreshToken'],
23
+ };
24
+ /**
25
+ * Create Google Meet provider
26
+ */
27
+ export function createGoogleMeetProvider(config) {
28
+ let accessToken;
29
+ let clientId;
30
+ let clientSecret;
31
+ let refreshToken;
32
+ let tokenExpiresAt = 0;
33
+ let calendarId = 'primary';
34
+ /**
35
+ * Refresh OAuth access token
36
+ */
37
+ async function refreshAccessToken() {
38
+ if (!clientId || !clientSecret || !refreshToken) {
39
+ throw new Error('clientId, clientSecret, and refreshToken required for token refresh');
40
+ }
41
+ const response = await fetch('https://oauth2.googleapis.com/token', {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/x-www-form-urlencoded',
45
+ },
46
+ body: new URLSearchParams({
47
+ client_id: clientId,
48
+ client_secret: clientSecret,
49
+ refresh_token: refreshToken,
50
+ grant_type: 'refresh_token',
51
+ }),
52
+ });
53
+ if (!response.ok) {
54
+ throw new Error(`Failed to refresh access token: HTTP ${response.status}`);
55
+ }
56
+ const data = (await response.json());
57
+ accessToken = data.access_token;
58
+ tokenExpiresAt = Date.now() + data.expires_in * 1000;
59
+ return accessToken;
60
+ }
61
+ /**
62
+ * Get valid access token
63
+ */
64
+ async function getAccessToken() {
65
+ // Return cached token if still valid (with 5-minute buffer)
66
+ if (accessToken && Date.now() < tokenExpiresAt - 300000) {
67
+ return accessToken;
68
+ }
69
+ // Try to refresh if we have refresh token
70
+ if (refreshToken) {
71
+ return refreshAccessToken();
72
+ }
73
+ // Otherwise use the provided token
74
+ return accessToken;
75
+ }
76
+ /**
77
+ * Make authenticated API request
78
+ */
79
+ async function apiRequest(endpoint, options = {}) {
80
+ const token = await getAccessToken();
81
+ const url = `${CALENDAR_API_URL}${endpoint}`;
82
+ const response = await fetch(url, {
83
+ ...options,
84
+ headers: {
85
+ Authorization: `Bearer ${token}`,
86
+ 'Content-Type': 'application/json',
87
+ ...options.headers,
88
+ },
89
+ });
90
+ if (!response.ok) {
91
+ const errorData = await response.json().catch(() => ({}));
92
+ throw new Error(`Google Calendar API error: ${response.status} - ${errorData?.error?.message || response.statusText}`);
93
+ }
94
+ return response.json();
95
+ }
96
+ /**
97
+ * Convert Google Calendar event to MeetingData
98
+ */
99
+ function convertEvent(event) {
100
+ const startTime = new Date(event.start.dateTime);
101
+ const endTime = new Date(event.end.dateTime);
102
+ const duration = Math.round((endTime.getTime() - startTime.getTime()) / 60000); // minutes
103
+ return {
104
+ id: event.id,
105
+ topic: event.summary,
106
+ startTime,
107
+ duration,
108
+ timezone: event.start.timeZone,
109
+ agenda: event.description,
110
+ joinUrl: event.hangoutLink || event.conferenceData?.entryPoints?.[0]?.uri || '',
111
+ hostId: event.creator?.id || event.creator?.email || '',
112
+ status: event.status === 'confirmed' ? 'waiting' : 'finished',
113
+ createdAt: new Date(event.created),
114
+ };
115
+ }
116
+ return {
117
+ info: googleMeetInfo,
118
+ async initialize(cfg) {
119
+ accessToken = cfg.accessToken;
120
+ clientId = cfg.clientId;
121
+ clientSecret = cfg.clientSecret;
122
+ refreshToken = cfg.refreshToken;
123
+ if (!accessToken) {
124
+ throw new Error('Google Meet requires accessToken');
125
+ }
126
+ // Override calendar ID if provided
127
+ if (cfg.calendarId) {
128
+ calendarId = cfg.calendarId;
129
+ }
130
+ },
131
+ async healthCheck() {
132
+ const start = Date.now();
133
+ try {
134
+ // Get calendar to verify API access
135
+ await apiRequest(`/calendars/${calendarId}`);
136
+ return {
137
+ healthy: true,
138
+ latencyMs: Date.now() - start,
139
+ message: 'Connected',
140
+ checkedAt: new Date(),
141
+ };
142
+ }
143
+ catch (error) {
144
+ return {
145
+ healthy: false,
146
+ latencyMs: Date.now() - start,
147
+ message: error instanceof Error ? error.message : 'Unknown error',
148
+ checkedAt: new Date(),
149
+ };
150
+ }
151
+ },
152
+ async dispose() {
153
+ // Clear cached token
154
+ accessToken = '';
155
+ tokenExpiresAt = 0;
156
+ },
157
+ async createMeeting(meeting) {
158
+ const endTime = meeting.startTime
159
+ ? new Date(meeting.startTime.getTime() + (meeting.duration || 60) * 60000)
160
+ : new Date(Date.now() + (meeting.duration || 60) * 60000);
161
+ const body = {
162
+ summary: meeting.topic,
163
+ description: meeting.agenda,
164
+ start: {
165
+ dateTime: (meeting.startTime || new Date()).toISOString(),
166
+ timeZone: meeting.timezone || 'UTC',
167
+ },
168
+ end: {
169
+ dateTime: endTime.toISOString(),
170
+ timeZone: meeting.timezone || 'UTC',
171
+ },
172
+ conferenceData: {
173
+ createRequest: {
174
+ requestId: `meet-${Date.now()}`,
175
+ conferenceSolutionKey: {
176
+ type: 'hangoutsMeet',
177
+ },
178
+ },
179
+ },
180
+ };
181
+ const response = await apiRequest(`/calendars/${calendarId}/events?conferenceDataVersion=1`, {
182
+ method: 'POST',
183
+ body: JSON.stringify(body),
184
+ });
185
+ return convertEvent(response);
186
+ },
187
+ async getMeeting(meetingId) {
188
+ try {
189
+ const response = await apiRequest(`/calendars/${calendarId}/events/${meetingId}`);
190
+ return convertEvent(response);
191
+ }
192
+ catch (error) {
193
+ if (error instanceof Error && error.message.includes('404')) {
194
+ return null;
195
+ }
196
+ throw error;
197
+ }
198
+ },
199
+ async updateMeeting(meetingId, updates) {
200
+ // First get the current event
201
+ const current = await this.getMeeting(meetingId);
202
+ if (!current) {
203
+ throw new Error(`Meeting ${meetingId} not found`);
204
+ }
205
+ const body = {};
206
+ if (updates.topic)
207
+ body.summary = updates.topic;
208
+ if (updates.agenda !== undefined)
209
+ body.description = updates.agenda;
210
+ if (updates.startTime) {
211
+ const endTime = new Date(updates.startTime.getTime() + (updates.duration || current.duration || 60) * 60000);
212
+ body.start = {
213
+ dateTime: updates.startTime.toISOString(),
214
+ timeZone: updates.timezone || current.timezone || 'UTC',
215
+ };
216
+ body.end = {
217
+ dateTime: endTime.toISOString(),
218
+ timeZone: updates.timezone || current.timezone || 'UTC',
219
+ };
220
+ }
221
+ const response = await apiRequest(`/calendars/${calendarId}/events/${meetingId}`, {
222
+ method: 'PATCH',
223
+ body: JSON.stringify(body),
224
+ });
225
+ return convertEvent(response);
226
+ },
227
+ async deleteMeeting(meetingId) {
228
+ try {
229
+ await apiRequest(`/calendars/${calendarId}/events/${meetingId}`, {
230
+ method: 'DELETE',
231
+ });
232
+ return true;
233
+ }
234
+ catch (error) {
235
+ if (error instanceof Error && error.message.includes('404')) {
236
+ return false;
237
+ }
238
+ throw error;
239
+ }
240
+ },
241
+ async listMeetings(options = {}) {
242
+ const params = new URLSearchParams();
243
+ // Set time range based on type
244
+ const now = new Date();
245
+ if (options.type === 'upcoming' || options.type === 'scheduled') {
246
+ params.append('timeMin', now.toISOString());
247
+ }
248
+ else if (options.type === 'previous') {
249
+ params.append('timeMax', now.toISOString());
250
+ }
251
+ params.append('singleEvents', 'true');
252
+ params.append('orderBy', 'startTime');
253
+ if (options.limit)
254
+ params.append('maxResults', options.limit.toString());
255
+ if (options.cursor)
256
+ params.append('pageToken', options.cursor);
257
+ const response = await apiRequest(`/calendars/${calendarId}/events?${params.toString()}`);
258
+ // Filter to only events with Meet links
259
+ const meetEvents = response.items.filter((event) => event.hangoutLink || event.conferenceData);
260
+ return {
261
+ items: meetEvents.map(convertEvent),
262
+ hasMore: !!response.nextPageToken,
263
+ nextCursor: response.nextPageToken,
264
+ };
265
+ },
266
+ endMeeting: async function (meetingId) {
267
+ // Google Meet doesn't support programmatically ending meetings
268
+ // We can delete the calendar event instead
269
+ return await this.deleteMeeting(meetingId);
270
+ },
271
+ async getParticipants(meetingId) {
272
+ // Google Calendar API doesn't provide participant data for past meetings
273
+ // This would require Google Meet API access which is more restricted
274
+ return [];
275
+ },
276
+ async getRecordings(meetingId) {
277
+ // Google Meet recordings are stored in Google Drive
278
+ // This would require Google Drive API access
279
+ return [];
280
+ },
281
+ };
282
+ }
283
+ /**
284
+ * Google Meet provider definition
285
+ */
286
+ export const googleMeetProvider = defineProvider(googleMeetInfo, async (config) => createGoogleMeetProvider(config));
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Video Conferencing Providers
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ export { zoomInfo, zoomProvider, createZoomProvider } from './zoom.js';
7
+ export { googleMeetInfo, googleMeetProvider, createGoogleMeetProvider } from './google-meet.js';
8
+ export { teamsInfo, teamsProvider, createTeamsProvider } from './teams.js';
9
+ export { jitsiInfo, jitsiProvider, createJitsiProvider } from './jitsi.js';
10
+ import { zoomProvider } from './zoom.js';
11
+ import { googleMeetProvider } from './google-meet.js';
12
+ import { teamsProvider } from './teams.js';
13
+ import { jitsiProvider } from './jitsi.js';
14
+ /**
15
+ * Register all video conferencing providers
16
+ */
17
+ export function registerVideoConferencingProviders() {
18
+ zoomProvider.register();
19
+ googleMeetProvider.register();
20
+ teamsProvider.register();
21
+ jitsiProvider.register();
22
+ }
23
+ /**
24
+ * All video conferencing providers
25
+ */
26
+ export const videoConferencingProviders = [
27
+ zoomProvider,
28
+ googleMeetProvider,
29
+ teamsProvider,
30
+ jitsiProvider,
31
+ ];
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Jitsi Meet Video Conferencing Provider
3
+ *
4
+ * Concrete implementation of VideoConferencingProvider using Jitsi Meet.
5
+ * Self-hostable open source video conferencing solution.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ import { defineProvider } from '../registry.js';
10
+ const DEFAULT_JITSI_SERVER = 'https://meet.jit.si';
11
+ /**
12
+ * Jitsi Meet provider info
13
+ */
14
+ export const jitsiInfo = {
15
+ id: 'meeting.jitsi',
16
+ name: 'Jitsi Meet',
17
+ description: 'Open source self-hostable video conferencing solution',
18
+ category: 'video-conferencing',
19
+ website: 'https://jitsi.org',
20
+ docsUrl: 'https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker',
21
+ requiredConfig: [],
22
+ optionalConfig: ['serverUrl', 'jwtSecret'],
23
+ };
24
+ /**
25
+ * Create Jitsi Meet provider
26
+ */
27
+ export function createJitsiProvider(config) {
28
+ let serverUrl;
29
+ let jwtSecret;
30
+ let hostId;
31
+ // In-memory storage for meetings
32
+ const meetings = new Map();
33
+ /**
34
+ * Generate a unique room name
35
+ */
36
+ function generateRoomName(topic) {
37
+ // Create URL-safe room name from topic
38
+ const safeTopic = topic
39
+ .toLowerCase()
40
+ .replace(/[^a-z0-9]+/g, '-')
41
+ .replace(/^-+|-+$/g, '')
42
+ .substring(0, 50);
43
+ // Add random suffix to ensure uniqueness
44
+ const randomSuffix = Math.random().toString(36).substring(2, 8);
45
+ return `${safeTopic}-${randomSuffix}`;
46
+ }
47
+ /**
48
+ * Generate JWT token for secure Jitsi meeting (if JWT is enabled)
49
+ */
50
+ function generateJWT(roomName, moderator = true) {
51
+ if (!jwtSecret) {
52
+ return undefined;
53
+ }
54
+ // Note: In production, you would use a proper JWT library
55
+ // This is a simplified placeholder
56
+ // In real implementation, use jsonwebtoken package
57
+ const header = {
58
+ alg: 'HS256',
59
+ typ: 'JWT',
60
+ };
61
+ const payload = {
62
+ context: {
63
+ user: {
64
+ moderator,
65
+ id: hostId,
66
+ },
67
+ },
68
+ aud: 'jitsi',
69
+ iss: 'jitsi',
70
+ sub: serverUrl.replace(/^https?:\/\//, ''),
71
+ room: roomName,
72
+ exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour expiry
73
+ };
74
+ // This is a placeholder - in real implementation use proper JWT signing
75
+ return `${btoa(JSON.stringify(header))}.${btoa(JSON.stringify(payload))}.signature`;
76
+ }
77
+ /**
78
+ * Build join URL for a Jitsi room
79
+ */
80
+ function buildJoinUrl(roomName, jwt) {
81
+ let url = `${serverUrl}/${roomName}`;
82
+ if (jwt) {
83
+ url += `?jwt=${jwt}`;
84
+ }
85
+ return url;
86
+ }
87
+ /**
88
+ * Convert stored meeting to MeetingData
89
+ */
90
+ function convertMeeting(meeting) {
91
+ return {
92
+ id: meeting.id,
93
+ topic: meeting.topic,
94
+ startTime: meeting.startTime,
95
+ duration: meeting.duration,
96
+ timezone: meeting.timezone,
97
+ agenda: meeting.agenda,
98
+ joinUrl: meeting.joinUrl,
99
+ hostId: meeting.hostId,
100
+ status: meeting.status,
101
+ password: meeting.password,
102
+ createdAt: meeting.createdAt,
103
+ };
104
+ }
105
+ return {
106
+ info: jitsiInfo,
107
+ async initialize(cfg) {
108
+ serverUrl = cfg.serverUrl || DEFAULT_JITSI_SERVER;
109
+ jwtSecret = cfg.jwtSecret;
110
+ hostId = cfg.hostId || 'default-host';
111
+ // Remove trailing slash from server URL
112
+ serverUrl = serverUrl.replace(/\/$/, '');
113
+ },
114
+ async healthCheck() {
115
+ const start = Date.now();
116
+ try {
117
+ // Check if Jitsi server is accessible
118
+ const response = await fetch(serverUrl, {
119
+ method: 'HEAD',
120
+ });
121
+ if (!response.ok && response.status !== 404) {
122
+ throw new Error(`Server returned status ${response.status}`);
123
+ }
124
+ return {
125
+ healthy: true,
126
+ latencyMs: Date.now() - start,
127
+ message: 'Connected',
128
+ checkedAt: new Date(),
129
+ };
130
+ }
131
+ catch (error) {
132
+ return {
133
+ healthy: false,
134
+ latencyMs: Date.now() - start,
135
+ message: error instanceof Error ? error.message : 'Unknown error',
136
+ checkedAt: new Date(),
137
+ };
138
+ }
139
+ },
140
+ async dispose() {
141
+ // Clear all stored meetings
142
+ meetings.clear();
143
+ },
144
+ async createMeeting(meeting) {
145
+ const id = `jitsi-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
146
+ const roomName = generateRoomName(meeting.topic);
147
+ const jwt = generateJWT(roomName, true);
148
+ const joinUrl = buildJoinUrl(roomName, jwt);
149
+ const storedMeeting = {
150
+ id,
151
+ topic: meeting.topic,
152
+ startTime: meeting.startTime,
153
+ duration: meeting.duration,
154
+ timezone: meeting.timezone,
155
+ agenda: meeting.agenda,
156
+ joinUrl,
157
+ hostId,
158
+ status: 'waiting',
159
+ password: meeting.password,
160
+ createdAt: new Date(),
161
+ roomName,
162
+ };
163
+ meetings.set(id, storedMeeting);
164
+ return convertMeeting(storedMeeting);
165
+ },
166
+ async getMeeting(meetingId) {
167
+ const meeting = meetings.get(meetingId);
168
+ return meeting ? convertMeeting(meeting) : null;
169
+ },
170
+ async updateMeeting(meetingId, updates) {
171
+ const meeting = meetings.get(meetingId);
172
+ if (!meeting) {
173
+ throw new Error(`Meeting ${meetingId} not found`);
174
+ }
175
+ // Update meeting properties
176
+ if (updates.topic)
177
+ meeting.topic = updates.topic;
178
+ if (updates.startTime !== undefined)
179
+ meeting.startTime = updates.startTime;
180
+ if (updates.duration !== undefined)
181
+ meeting.duration = updates.duration;
182
+ if (updates.timezone !== undefined)
183
+ meeting.timezone = updates.timezone;
184
+ if (updates.agenda !== undefined)
185
+ meeting.agenda = updates.agenda;
186
+ if (updates.password !== undefined)
187
+ meeting.password = updates.password;
188
+ // If topic changed, regenerate room name and URL
189
+ if (updates.topic) {
190
+ meeting.roomName = generateRoomName(updates.topic);
191
+ const jwt = generateJWT(meeting.roomName, true);
192
+ meeting.joinUrl = buildJoinUrl(meeting.roomName, jwt);
193
+ }
194
+ meetings.set(meetingId, meeting);
195
+ return convertMeeting(meeting);
196
+ },
197
+ async deleteMeeting(meetingId) {
198
+ return meetings.delete(meetingId);
199
+ },
200
+ async listMeetings(options = {}) {
201
+ let items = Array.from(meetings.values());
202
+ // Filter by type
203
+ const now = new Date();
204
+ if (options.type === 'upcoming' || options.type === 'scheduled') {
205
+ items = items.filter((m) => m.startTime && m.startTime > now);
206
+ }
207
+ else if (options.type === 'previous') {
208
+ items = items.filter((m) => m.startTime && m.startTime < now);
209
+ }
210
+ else if (options.type === 'live') {
211
+ items = items.filter((m) => m.status === 'started');
212
+ }
213
+ // Sort by start time
214
+ items.sort((a, b) => {
215
+ const aTime = a.startTime?.getTime() || 0;
216
+ const bTime = b.startTime?.getTime() || 0;
217
+ return aTime - bTime;
218
+ });
219
+ // Apply pagination
220
+ const offset = parseInt(options.cursor || '0', 10);
221
+ const limit = options.limit || 50;
222
+ const paginatedItems = items.slice(offset, offset + limit);
223
+ return {
224
+ items: paginatedItems.map(convertMeeting),
225
+ hasMore: offset + limit < items.length,
226
+ nextCursor: offset + limit < items.length ? (offset + limit).toString() : undefined,
227
+ };
228
+ },
229
+ async endMeeting(meetingId) {
230
+ const meeting = meetings.get(meetingId);
231
+ if (!meeting) {
232
+ return false;
233
+ }
234
+ meeting.status = 'finished';
235
+ meetings.set(meetingId, meeting);
236
+ return true;
237
+ },
238
+ async getParticipants(meetingId) {
239
+ // Jitsi doesn't provide a built-in API for participant data
240
+ // This would require setting up Jitsi Meet API with prosody modules
241
+ // or integrating with jitsi-videobridge stats
242
+ return [];
243
+ },
244
+ async getRecordings(meetingId) {
245
+ // Jitsi recordings would need to be configured with Jibri
246
+ // and would require integration with your recording storage
247
+ return [];
248
+ },
249
+ };
250
+ }
251
+ /**
252
+ * Jitsi Meet provider definition
253
+ */
254
+ export const jitsiProvider = defineProvider(jitsiInfo, async (config) => createJitsiProvider(config));