diff --git a/src/frontend/default.conf b/src/frontend/default.conf index 6dd98b53..51889be8 100644 --- a/src/frontend/default.conf +++ b/src/frontend/default.conf @@ -11,6 +11,12 @@ server { add_header Content-Disposition "attachment; filename=windows-app-web-link"; } + location ~ ^/outlook-addin(/.*)?$ { + alias /usr/share/nginx/html/outlook-addin$1; + add_header Access-Control-Allow-Origin "*"; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } + # Serve static files with caching location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 30d; diff --git a/src/frontend/public/outlook-addin/assets/icon-128.png b/src/frontend/public/outlook-addin/assets/icon-128.png new file mode 100644 index 00000000..37dfcd77 Binary files /dev/null and b/src/frontend/public/outlook-addin/assets/icon-128.png differ diff --git a/src/frontend/public/outlook-addin/assets/icon-16.png b/src/frontend/public/outlook-addin/assets/icon-16.png new file mode 100644 index 00000000..b6509798 Binary files /dev/null and b/src/frontend/public/outlook-addin/assets/icon-16.png differ diff --git a/src/frontend/public/outlook-addin/assets/icon-32.png b/src/frontend/public/outlook-addin/assets/icon-32.png new file mode 100644 index 00000000..dcf56db7 Binary files /dev/null and b/src/frontend/public/outlook-addin/assets/icon-32.png differ diff --git a/src/frontend/public/outlook-addin/assets/icon-64.png b/src/frontend/public/outlook-addin/assets/icon-64.png new file mode 100644 index 00000000..41051fce Binary files /dev/null and b/src/frontend/public/outlook-addin/assets/icon-64.png differ diff --git a/src/frontend/public/outlook-addin/assets/icon-80.png b/src/frontend/public/outlook-addin/assets/icon-80.png new file mode 100644 index 00000000..5e63769d Binary files /dev/null and b/src/frontend/public/outlook-addin/assets/icon-80.png differ diff --git a/src/frontend/public/outlook-addin/assets/logo-filled.png b/src/frontend/public/outlook-addin/assets/logo-filled.png new file mode 100644 index 00000000..5bf09cc2 Binary files /dev/null and b/src/frontend/public/outlook-addin/assets/logo-filled.png differ diff --git a/src/frontend/public/outlook-addin/commands.html b/src/frontend/public/outlook-addin/commands.html new file mode 100644 index 00000000..8d5110bf --- /dev/null +++ b/src/frontend/public/outlook-addin/commands.html @@ -0,0 +1,11 @@ + + +
+ + + + + + + + diff --git a/src/frontend/public/outlook-addin/commands.js b/src/frontend/public/outlook-addin/commands.js new file mode 100644 index 00000000..8ddf9c7a --- /dev/null +++ b/src/frontend/public/outlook-addin/commands.js @@ -0,0 +1,79 @@ +/* global Office */ + +Office.onReady(() => {}); + +function generateMeetingLinkFromCalendar(event) { + const session = loadSession(); + + if (!session?.access_token) { + Office.context.mailbox.item.notificationMessages.replaceAsync("meetNotif", { + type: Office.MailboxEnums.ItemNotificationMessageType.ErrorMessage, + message: "Vous n'êtes pas connecté. Ouvrez les paramètres pour vous connecter.", + }); + event.completed(); + return; + } + + fetch(`${BASE_URL}/external-api/v1.0/rooms/`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + session.access_token, + }, + }) + .then((res) => res.json()) + .then((data) => { + console.log("Room created:", data); + + const { url, message } = buildMeetingMessage(data); + const item = Office.context.mailbox.item; + + item.body.getAsync(Office.CoercionType.Html, (getResult) => { + if (getResult.status !== Office.AsyncResultStatus.Succeeded) { + item.notificationMessages.replaceAsync("meetNotif", { + type: Office.MailboxEnums.ItemNotificationMessageType.ErrorMessage, + message: `Erreur de lecture: ${getResult.error.message}`, + }); + event.completed(); + return; + } + + item.body.setAsync(getResult.value + message, { coercionType: Office.CoercionType.Html }, (setResult) => { + if (setResult.status !== Office.AsyncResultStatus.Succeeded) { + item.notificationMessages.replaceAsync("meetNotif", { + type: Office.MailboxEnums.ItemNotificationMessageType.ErrorMessage, + message: `Erreur d'insertion: ${setResult.error.message}`, + }); + event.completed(); + return; + } + + item.location.setAsync(url, (locationResult) => { + if (locationResult.status === Office.AsyncResultStatus.Succeeded) { + item.notificationMessages.replaceAsync("meetNotif", { + type: Office.MailboxEnums.ItemNotificationMessageType.InformationalMessage, + message: "Lien de réunion inséré !", + icon: "Icon.80x80", + persistent: false, + }); + } else { + item.notificationMessages.replaceAsync("meetNotif", { + type: Office.MailboxEnums.ItemNotificationMessageType.ErrorMessage, + message: `Erreur de localisation: ${locationResult.error.message}`, + }); + } + event.completed(); + }); + }); + }); + }) + .catch((err) => { + Office.context.mailbox.item.notificationMessages.replaceAsync("meetNotif", { + type: Office.MailboxEnums.ItemNotificationMessageType.ErrorMessage, + message: `Erreur: ${err.message}`, + }); + event.completed(); + }); +} + +Office.actions.associate("generateMeetingLinkFromCalendar", generateMeetingLinkFromCalendar); diff --git a/src/frontend/public/outlook-addin/common.js b/src/frontend/public/outlook-addin/common.js new file mode 100644 index 00000000..c6bd43c9 --- /dev/null +++ b/src/frontend/public/outlook-addin/common.js @@ -0,0 +1,97 @@ +/* global Office */ + +const BASE_URL = "https://meet.127.0.0.1.nip.io"; + +// ─── Session Storage ────────────────────────────────────────────────────── + +function saveSession(data) { + const expiresAt = data.expires_in + ? new Date(Date.now() + data.expires_in * 1000).toISOString() + : null; + + const payload = JSON.stringify({ + ...data, + expiresAt, + savedAt: new Date().toISOString(), + }); + + localStorage.setItem("meetSession", payload); + + const rs = Office.context.roamingSettings; + rs.set("meetSession", payload); + rs.saveAsync((result) => { + if (result.status !== Office.AsyncResultStatus.Succeeded) { + console.error("RoamingSettings save failed:", result.error.message); + } + }); +} + +function loadSession() { + let session = null; + + try { + const stored = Office.context.roamingSettings.get("meetSession"); + if (stored) session = JSON.parse(stored); + } catch (e) { + console.warn("RoamingSettings read failed:", e); + } + + if (!session) { + try { + const stored = localStorage.getItem("meetSession"); + if (stored) session = JSON.parse(stored); + } catch (e) { + console.warn("localStorage read failed:", e); + } + } + + if (!session) return null; + + if (session.expiresAt && new Date() > new Date(session.expiresAt)) { + console.warn("Token expired, clearing session."); + clearSession(); + return null; + } + + return session; +} + +function clearSession() { + localStorage.removeItem("meetSession"); + try { + const rs = Office.context.roamingSettings; + rs.remove("meetSession"); + rs.saveAsync(() => console.log("RoamingSettings cleared.")); + } catch (e) { + console.warn("Could not clear RoamingSettings:", e); + } +} + +// ─── Meeting Message Builder ─────────────────────────────────────────────── + +function buildMeetingMessage(data) { + const url = data.url; + const phone = data.telephony?.phone_number; + const pin = data.telephony?.pin_code; + + const formattedPin = pin + ? pin.replace(/(\d{3})(\d{3})(\d{4})/, "$1 $2 $3") + "#" + : ""; + + const formattedPhone = phone + ? phone.replace(/^\+33(\d)(\d{2})(\d{2})(\d{2})(\d{2})$/, "+33 $1 $2 $3 $4 $5") + : phone; + + const message = `
+────────────────────────────────────────
+Rejoindre la réunion LaSuite Meet
+
+${url}
+
+Ou appelez (audio uniquement)
+(FR) ${formattedPhone}
+Code : ${formattedPin}
+────────────────────────────────────────`;
+
+ return { url, message };
+}
diff --git a/src/frontend/public/outlook-addin/manifest.xml b/src/frontend/public/outlook-addin/manifest.xml
new file mode 100644
index 00000000..56dd9345
--- /dev/null
+++ b/src/frontend/public/outlook-addin/manifest.xml
@@ -0,0 +1,172 @@
+
+Chargement...
+