사용자는 Google Calendar 일정을 자유롭게 업데이트하거나 삭제할 수 있습니다. 사용자가 회의를 만든 후 이벤트를 업데이트하는 경우 부가기능에서 회의 데이터를 업데이트하여 변경사항에 응답해야 할 수 있습니다. 서드 파티 회의 시스템이 이벤트 데이터 추적에 의존하는 경우, 이벤트 변경으로 인해 회의를 업데이트하지 못하면 회의를 사용할 수 없게 되고 사용자 환경이 저하될 수 있습니다.
Google Calendar 일정 변경사항에 따라 컨퍼런스 데이터를 업데이트하는 프로세스를 동기화라고 합니다. 지정된 캘린더에서 일정이 변경될 때마다 실행되는 Apps Script 설치 가능한 트리거를 만들어 일정 변경사항을 동기화할 수 있습니다. 트리거는 어떤 이벤트가 변경되었는지 보고하지 않으며 개발자가 만든 회의가 있는 이벤트로만 제한할 수 없습니다. 대신 마지막 동기화 이후 캘린더에 적용된 모든 변경사항의 목록을 요청하고, 이벤트 목록을 필터링하고, 적절하게 업데이트해야 합니다.
일반적인 동기화 절차는 다음과 같습니다.
- 사용자가 회의를 처음 만들면 동기화 프로세스가 초기화됩니다.
- 사용자가 캘린더 일정 중 하나를 생성, 업데이트 또는 삭제할 때마다 트리거는 부가기능 프로젝트에서 트리거 함수를 실행합니다.
- 트리거 함수는 마지막 동기화 이후의 이벤트 변경사항 집합을 검사하고 연결된 서드 파티 회의를 업데이트해야 하는지 확인합니다.
- 필요한 업데이트는 서드 파티 API 요청을 통해 회의에 적용됩니다.
- 다음 트리거 실행 시 달력의 최근 변경사항만 검사하면 되도록 새 동기화 토큰이 저장됩니다.
동기화 초기화
부가기능이 서드 파티 시스템에 회의를 성공적으로 만든 후에는 이 캘린더의 이벤트 변경사항에 응답하는 설치 가능한 트리거를 만들어야 합니다(트리거가 아직 없는 경우).
트리거를 만든 후 초기 동기화 토큰을 만들어 초기화가 완료되어야 합니다. 트리거 함수를 직접 실행하면 됩니다.
Calendar 트리거 만들기
동기화하려면 부가기능에서 회의가 첨부된 Calendar 일정이 변경될 때 이를 감지해야 합니다. 이렇게 하려면 EventUpdated
설치 가능한 트리거를 만듭니다. 부가기능에는 캘린더별로 하나의 트리거만 필요하며 프로그래매틱 방식으로 만들 수 있습니다.
트리거를 만드는 좋은 시점은 사용자가 첫 번째 회의를 만들 때입니다. 이때 사용자가 부가기능을 사용하기 시작하기 때문입니다. 회의를 만들고 오류가 없는지 확인한 후에는 부가기능에서 이 사용자에 대한 트리거가 있는지 확인하고 없는 경우 트리거를 만들어야 합니다.
동기화 트리거 함수 구현
트리거 함수는 Apps Script에서 트리거를 실행하는 조건을 감지할 때 실행됩니다.
EventUpdated
캘린더 트리거는 사용자가 지정된 캘린더에서 일정을 생성, 수정 또는 삭제할 때 실행됩니다.
부가기능에서 사용하는 트리거 함수를 구현해야 합니다. 이 트리거 함수는 다음을 실행해야 합니다.
syncToken
를 사용하여 Calendar 고급 서비스Calendar.Events.list()
를 호출하여 마지막 동기화 이후 변경된 일정 목록을 가져옵니다. 동기화 토큰을 사용하면 부가기능에서 검사해야 하는 이벤트 수를 줄일 수 있습니다.유효한 동기화 토큰 없이 트리거 함수가 실행되면 전체 동기화로 백오프됩니다. 전체 동기화는 유효한 새 동기화 토큰을 생성하기 위해 지정된 시간 내에 모든 이벤트를 검색하려고 시도합니다.
- 수정된 각 이벤트를 검사하여 연결된 서드 파티 회의가 있는지 확인합니다.
- 일정에 회의가 있는 경우 변경된 내용을 확인하기 위해 검사됩니다. 변경사항에 따라 연결된 회의를 수정해야 할 수도 있습니다. 예를 들어 일정이 삭제되면 부가기능에서 회의도 삭제해야 합니다.
- 회의에 필요한 변경사항은 서드 파티 시스템에 API를 호출하여 적용됩니다.
- 필요한 모든 변경사항을 적용한 후
Calendar.Events.list()
메서드에서 반환된nextSyncToken
를 저장합니다. 이 동기화 토큰은Calendar.Events.list()
호출에서 반환된 결과의 마지막 페이지에 있습니다.
Google Calendar 일정 업데이트
동기화를 수행할 때 Google Calendar 일정을 업데이트해야 하는 경우가 있습니다. 이 방법을 선택하는 경우 적절한 Google Calendar 고급 서비스 요청으로 일정을 업데이트합니다. If-Match
헤더와 함께 조건부 업데이트를 사용해야 합니다. 이렇게 하면 변경사항이 다른 클라이언트에서 사용자가 실행한 동시 변경사항을 의도치 않게 덮어쓰지 않습니다.
예
다음 예는 캘린더 일정 및 관련 회의의 동기화를 설정하는 방법을 보여줍니다.
/** * Initializes syncing of conference data by creating a sync trigger and * sync token if either does not exist yet. * * @param {String} calendarId The ID of the Google Calendar. */ function initializeSyncing(calendarId) { // Create a syncing trigger if it doesn't exist yet. createSyncTrigger(calendarId); // Perform an event sync to create the initial sync token. syncEvents({'calendarId': calendarId}); } /** * Creates a sync trigger if it does not exist yet. * * @param {String} calendarId The ID of the Google Calendar. */ function createSyncTrigger(calendarId) { // Check to see if the trigger already exists; if does, return. var allTriggers = ScriptApp.getProjectTriggers(); for (var i = 0; i < allTriggers.length; i++) { var trigger = allTriggers[i]; if (trigger.getTriggerSourceId() == calendarId) { return; } } // Trigger does not exist, so create it. The trigger calls the // 'syncEvents()' trigger function when it fires. var trigger = ScriptApp.newTrigger('syncEvents') .forUserCalendar(calendarId) .onEventUpdated() .create(); } /** * Sync events for the given calendar; this is the syncing trigger * function. If a sync token already exists, this retrieves all events * that have been modified since the last sync, then checks each to see * if an associated conference needs to be updated and makes any required * changes. If the sync token does not exist or is invalid, this * retrieves future events modified in the last 24 hours instead. In * either case, a new sync token is created and stored. * * @param {Object} e If called by a event updated trigger, this object * contains the Google Calendar ID, authorization mode, and * calling trigger ID. Only the calendar ID is actually used here, * however. */ function syncEvents(e) { var calendarId = e.calendarId; var properties = PropertiesService.getUserProperties(); var syncToken = properties.getProperty('syncToken'); var options; if (syncToken) { // There's an existing sync token, so configure the following event // retrieval request to only get events that have been modified // since the last sync. options = { syncToken: syncToken }; } else { // No sync token, so configure to do a 'full' sync instead. In this // example only recently updated events are retrieved in a full sync. // A larger time window can be examined during a full sync, but this // slows down the script execution. Consider the trade-offs while // designing your add-on. var now = new Date(); var yesterday = new Date(); yesterday.setDate(now.getDate() - 1); options = { timeMin: now.toISOString(), // Events that start after now... updatedMin: yesterday.toISOString(), // ...and were modified recently maxResults: 50, // Max. number of results per page of responses orderBy: 'updated' } } // Examine the list of updated events since last sync (or all events // modified after yesterday if the sync token is missing or invalid), and // update any associated conferences as required. var events; var pageToken; do { try { options.pageToken = pageToken; events = Calendar.Events.list(calendarId, options); } catch (err) { // Check to see if the sync token was invalidated by the server; // if so, perform a full sync instead. if (err.message === "Sync token is no longer valid, a full sync is required.") { properties.deleteProperty('syncToken'); syncEvents(e); return; } else { throw new Error(err.message); } } // Read through the list of returned events looking for conferences // to update. if (events.items && events.items.length > 0) { for (var i = 0; i < events.items.length; i++) { var calEvent = events.items[i]; // Check to see if there is a record of this event has a // conference that needs updating. if (eventHasConference(calEvent)) { updateConference(calEvent, calEvent.conferenceData.conferenceId); } } } pageToken = events.nextPageToken; } while (pageToken); // Record the new sync token. if (events.nextSyncToken) { properties.setProperty('syncToken', events.nextSyncToken); } } /** * Returns true if the specified event has an associated conference * of the type managed by this add-on; retuns false otherwise. * * @param {Object} calEvent The Google Calendar event object, as defined by * the Calendar API. * @return {boolean} */ function eventHasConference(calEvent) { var name = calEvent.conferenceData.conferenceSolution.name || null; // This version checks if the conference data solution name matches the // one of the solution names used by the add-on. Alternatively you could // check the solution's entry point URIs or other solution-specific // information. if (name) { if (name === "My Web Conference" || name === "My Recorded Web Conference") { return true; } } return false; } /** * Update a conference based on new Google Calendar event information. * The exact implementation of this function is highly dependant on the * details of the third-party conferencing system, so only a rough outline * is shown here. * * @param {Object} calEvent The Google Calendar event object, as defined by * the Calendar API. * @param {String} conferenceId The ID used to identify the conference on * the third-party conferencing system. */ function updateConference(calEvent, conferenceId) { // Check edge case: the event was cancelled if (calEvent.status === 'cancelled' || eventHasConference(calEvent)) { // Use the third-party API to delete the conference too. } else { // Extract any necessary information from the event object, then // make the appropriate third-party API requests to update the // conference with that information. } }