tide-commander 1.86.0 → 1.87.0
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/dist/assets/{BossLogsModal-BB1vCM5J.js → BossLogsModal-S3Rke-8g.js} +1 -1
- package/dist/assets/{BossSpawnModal-wMLySpgn.js → BossSpawnModal-BjWGNCnz.js} +1 -1
- package/dist/assets/{ControlsModal-BklEN7j8.js → ControlsModal-6yfU0XjZ.js} +1 -1
- package/dist/assets/{DockerLogsModal-B49O9xcw.js → DockerLogsModal-CYq0hNz6.js} +1 -1
- package/dist/assets/{EmbeddedEditor-BX9aBsiv.js → EmbeddedEditor-ZBdqRDqm.js} +1 -1
- package/dist/assets/GmailOAuthSetup-BcV5jAse.js +388 -0
- package/dist/assets/{GoogleOAuthSetup-4nw4iS8V.js → GoogleOAuthSetup-DyUW_STE.js} +2 -2
- package/dist/assets/{IframeModal-DGXje5GK.js → IframeModal-D9A3dUUc.js} +1 -1
- package/dist/assets/{IntegrationsPanel-CjtCtu7-.js → IntegrationsPanel-CHaNJBJW.js} +2 -2
- package/dist/assets/{LogViewerModal-CnOV3Ai5.js → LogViewerModal-BWkbY7wa.js} +1 -1
- package/dist/assets/{MonitoringModal-VwORpB9L.js → MonitoringModal-AZzokZAZ.js} +1 -1
- package/dist/assets/{PM2LogsModal-8bnD0XcQ.js → PM2LogsModal-q98eiBfq.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-BhwpPzFY.js → RestoreArchivedAreaModal-CTxRP2qE.js} +1 -1
- package/dist/assets/{Scene2DCanvas-DaRWkux-.js → Scene2DCanvas-C11dztp1.js} +1 -1
- package/dist/assets/{SceneManager-CKz-v1rU.js → SceneManager-CsW9MYrD.js} +1 -1
- package/dist/assets/{SkillsPanel-l0DKbOtc.js → SkillsPanel-BeZr9w6E.js} +1 -1
- package/dist/assets/{SpawnModal-DEAh9hdW.js → SpawnModal-DY_KM6lX.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-CczaDW9_.js → SubordinateAssignmentModal-D6RvjGX9.js} +1 -1
- package/dist/assets/{TriggerManagerPanel-aczhr8bG.js → TriggerManagerPanel-BmqjXv9T.js} +1 -1
- package/dist/assets/{WorkflowEditorPanel-Pp473-yC.js → WorkflowEditorPanel-Rd5ZjJmt.js} +1 -1
- package/dist/assets/{index-BrntsiFl.js → index-BOr_tbLK.js} +1 -1
- package/dist/assets/{index-Da106QNu.js → index-BYVHgVEo.js} +1 -1
- package/dist/assets/{index-VOQ8B_0s.js → index-BoORE9Q1.js} +1 -1
- package/dist/assets/{index-CsZl2cpZ.js → index-BtJyOo4p.js} +1 -1
- package/dist/assets/{index-DAsa2Q29.js → index-BxaEkSIx.js} +3 -3
- package/dist/assets/{index-CfEa34G0.js → index-Co7njQ0Q.js} +1 -1
- package/dist/assets/{index-DEEA8DzD.js → index-DHHRkTG1.js} +1 -1
- package/dist/assets/{index-bumMaJlF.js → index-DRGyDtmm.js} +1 -1
- package/dist/assets/{index-Bsu-4DMd.js → index-DSvJOrb7.js} +2 -2
- package/dist/assets/{main-D2EQVTAu.js → main-BrZe9Zbd.js} +4 -4
- package/dist/assets/{web-DlbC6v-L.js → web-D3zCwsS9.js} +1 -1
- package/dist/assets/{web-CZQVhlQA.js → web-DEq3Te_H.js} +1 -1
- package/dist/assets/{web-Bdm2kdQ5.js → web-DS0FHmg8.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/src/packages/server/integrations/gmail/gmail-client.js +15 -2
- package/dist/src/packages/server/integrations/gmail/gmail-config.js +8 -0
- package/dist/src/packages/server/integrations/gmail/index.js +12 -2
- package/dist/src/packages/server/integrations/google-calendar/calendar-client.js +10 -2
- package/dist/src/packages/server/integrations/google-calendar/calendar-config.js +13 -0
- package/dist/src/packages/server/integrations/google-calendar/index.js +7 -0
- package/dist/src/packages/server/integrations/google-drive/drive-client.js +10 -2
- package/dist/src/packages/server/integrations/google-drive/drive-config.js +13 -0
- package/dist/src/packages/server/integrations/google-drive/index.js +7 -0
- package/package.json +1 -1
- package/dist/assets/GmailOAuthSetup-BzB4QGx5.js +0 -388
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ck as s}from"./main-
|
|
1
|
+
import{ck as s}from"./main-BrZe9Zbd.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ck as t}from"./main-
|
|
1
|
+
import{ck as t}from"./main-BrZe9Zbd.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class o extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{o as AppWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ck as a}from"./main-
|
|
1
|
+
import{ck as a}from"./main-BrZe9Zbd.js";import{ImpactStyle as i,NotificationType as r}from"./index-DSvJOrb7.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
|
package/dist/index.html
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
|
|
23
23
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
|
|
24
24
|
<title>Tide Commander</title>
|
|
25
|
-
<script type="module" crossorigin src="/assets/main-
|
|
25
|
+
<script type="module" crossorigin src="/assets/main-BrZe9Zbd.js"></script>
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
|
|
28
28
|
<link rel="stylesheet" crossorigin href="/assets/main-kpU9m5LW.css">
|
|
@@ -71,7 +71,7 @@ export async function init(context) {
|
|
|
71
71
|
ctx.log.info('Gmail not configured (missing OAuth credentials)');
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
|
-
oauth2Client = new google.auth.OAuth2(config.clientId, config.clientSecret,
|
|
74
|
+
oauth2Client = new google.auth.OAuth2(config.clientId, config.clientSecret, getRedirectUri());
|
|
75
75
|
// Load refresh token from secrets
|
|
76
76
|
const refreshToken = ctx.secrets.get('GOOGLE_REFRESH_TOKEN') || config.refreshToken;
|
|
77
77
|
if (refreshToken) {
|
|
@@ -100,15 +100,23 @@ function loadConfig() {
|
|
|
100
100
|
const refreshToken = ctx.secrets.get('GOOGLE_REFRESH_TOKEN');
|
|
101
101
|
const serviceAccountJson = ctx.secrets.get('GOOGLE_SERVICE_ACCOUNT_JSON') || undefined;
|
|
102
102
|
const impersonateEmail = ctx.secrets.get('GOOGLE_IMPERSONATE_EMAIL') || undefined;
|
|
103
|
+
const redirectBaseUrl = ctx.secrets.get('GOOGLE_REDIRECT_BASE_URL') || undefined;
|
|
103
104
|
config = {
|
|
104
105
|
...config,
|
|
105
106
|
clientId,
|
|
106
107
|
clientSecret,
|
|
108
|
+
redirectBaseUrl,
|
|
107
109
|
refreshToken: refreshToken || undefined,
|
|
108
110
|
serviceAccountJson,
|
|
109
111
|
impersonateEmail,
|
|
110
112
|
};
|
|
111
113
|
}
|
|
114
|
+
function getRedirectUri() {
|
|
115
|
+
if (!ctx)
|
|
116
|
+
throw new Error('Gmail not initialized');
|
|
117
|
+
const base = (config.redirectBaseUrl?.trim() || ctx.serverConfig.baseUrl).replace(/\/$/, '');
|
|
118
|
+
return `${base}${REDIRECT_PATH}`;
|
|
119
|
+
}
|
|
112
120
|
export function updateConfig(updates) {
|
|
113
121
|
if (updates.authMethod !== undefined) {
|
|
114
122
|
config.authMethod = updates.authMethod;
|
|
@@ -121,6 +129,11 @@ export function updateConfig(updates) {
|
|
|
121
129
|
config.clientSecret = updates.clientSecret;
|
|
122
130
|
ctx?.secrets.set('GOOGLE_CLIENT_SECRET', updates.clientSecret);
|
|
123
131
|
}
|
|
132
|
+
if (updates.redirectBaseUrl !== undefined) {
|
|
133
|
+
const trimmed = updates.redirectBaseUrl.trim();
|
|
134
|
+
config.redirectBaseUrl = trimmed || undefined;
|
|
135
|
+
ctx?.secrets.set('GOOGLE_REDIRECT_BASE_URL', trimmed);
|
|
136
|
+
}
|
|
124
137
|
if (updates.serviceAccountJson !== undefined) {
|
|
125
138
|
config.serviceAccountJson = updates.serviceAccountJson;
|
|
126
139
|
ctx?.secrets.set('GOOGLE_SERVICE_ACCOUNT_JSON', updates.serviceAccountJson);
|
|
@@ -168,7 +181,7 @@ export function getAuthUrl() {
|
|
|
168
181
|
if (!config.clientId || !config.clientSecret || !ctx) {
|
|
169
182
|
throw new Error('Gmail OAuth not configured');
|
|
170
183
|
}
|
|
171
|
-
oauth2Client = new google.auth.OAuth2(config.clientId, config.clientSecret,
|
|
184
|
+
oauth2Client = new google.auth.OAuth2(config.clientId, config.clientSecret, getRedirectUri());
|
|
172
185
|
}
|
|
173
186
|
return oauth2Client.generateAuthUrl({
|
|
174
187
|
access_type: 'offline',
|
|
@@ -34,6 +34,14 @@ export const gmailConfigSchema = [
|
|
|
34
34
|
secret: true,
|
|
35
35
|
group: 'Authentication',
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
key: 'redirectBaseUrl',
|
|
39
|
+
label: 'OAuth Redirect Base URL',
|
|
40
|
+
type: 'text',
|
|
41
|
+
description: 'Override the base URL used for the OAuth redirect (e.g. http://commander.local:10003). Leave empty to use http://localhost:<port>. Google does not accept raw IPs — use a domain (you can map one in /etc/hosts).',
|
|
42
|
+
required: false,
|
|
43
|
+
group: 'Authentication',
|
|
44
|
+
},
|
|
37
45
|
{
|
|
38
46
|
key: 'serviceAccountJson',
|
|
39
47
|
label: 'Service Account JSON',
|
|
@@ -42,6 +42,7 @@ export const gmailPlugin = {
|
|
|
42
42
|
authMethod: 'oauth2',
|
|
43
43
|
clientId: '',
|
|
44
44
|
clientSecret: '',
|
|
45
|
+
redirectBaseUrl: '',
|
|
45
46
|
serviceAccountJson: '',
|
|
46
47
|
impersonateEmail: '',
|
|
47
48
|
pollingIntervalMs: 30000,
|
|
@@ -54,6 +55,7 @@ export const gmailPlugin = {
|
|
|
54
55
|
// Calendar and Drive integrations, which share these same secrets).
|
|
55
56
|
clientId: integrationCtx.secrets.get('GOOGLE_CLIENT_ID') ? '********' : '',
|
|
56
57
|
clientSecret: integrationCtx.secrets.get('GOOGLE_CLIENT_SECRET') ? '********' : '',
|
|
58
|
+
redirectBaseUrl: integrationCtx.secrets.get('GOOGLE_REDIRECT_BASE_URL') || '',
|
|
57
59
|
serviceAccountJson: integrationCtx.secrets.get('GOOGLE_SERVICE_ACCOUNT_JSON') ? '********' : '',
|
|
58
60
|
impersonateEmail: integrationCtx.secrets.get('GOOGLE_IMPERSONATE_EMAIL') || '',
|
|
59
61
|
pollingIntervalMs: gmailClient.getConfig().pollingIntervalMs,
|
|
@@ -75,6 +77,11 @@ export const gmailPlugin = {
|
|
|
75
77
|
updates.clientSecret = config.clientSecret;
|
|
76
78
|
integrationCtx.secrets.set('GOOGLE_CLIENT_SECRET', config.clientSecret);
|
|
77
79
|
}
|
|
80
|
+
if (config.redirectBaseUrl !== undefined) {
|
|
81
|
+
const value = String(config.redirectBaseUrl).trim();
|
|
82
|
+
updates.redirectBaseUrl = value;
|
|
83
|
+
integrationCtx.secrets.set('GOOGLE_REDIRECT_BASE_URL', value);
|
|
84
|
+
}
|
|
78
85
|
if (config.serviceAccountJson && config.serviceAccountJson !== '********') {
|
|
79
86
|
updates.serviceAccountJson = config.serviceAccountJson;
|
|
80
87
|
integrationCtx.secrets.set('GOOGLE_SERVICE_ACCOUNT_JSON', config.serviceAccountJson);
|
|
@@ -94,8 +101,11 @@ export const gmailPlugin = {
|
|
|
94
101
|
updates.defaultApprovalKeywords = keywords;
|
|
95
102
|
}
|
|
96
103
|
gmailClient.updateConfig(updates);
|
|
97
|
-
// Re-initialize authentication when auth method or
|
|
98
|
-
if (config.authMethod !== undefined
|
|
104
|
+
// Re-initialize authentication when auth method, credentials, or redirect URL change
|
|
105
|
+
if (config.authMethod !== undefined
|
|
106
|
+
|| config.serviceAccountJson
|
|
107
|
+
|| config.clientId
|
|
108
|
+
|| config.redirectBaseUrl !== undefined) {
|
|
99
109
|
gmailClient.shutdown();
|
|
100
110
|
await gmailClient.init(integrationCtx);
|
|
101
111
|
}
|
|
@@ -24,13 +24,14 @@ export async function init(integrationCtx) {
|
|
|
24
24
|
ctx.log.info('Google Calendar missing OAuth credentials, skipping init');
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
oauth2Client = new google.auth.OAuth2(clientId, clientSecret,
|
|
27
|
+
oauth2Client = new google.auth.OAuth2(clientId, clientSecret, getRedirectUri());
|
|
28
28
|
oauth2Client.setCredentials({ refresh_token: refreshToken });
|
|
29
29
|
calendarApi = google.calendar({ version: 'v3', auth: oauth2Client });
|
|
30
30
|
ctx.log.info('Google Calendar initialized');
|
|
31
31
|
}
|
|
32
32
|
export async function shutdown() {
|
|
33
33
|
calendarApi = null;
|
|
34
|
+
oauth2Client = null;
|
|
34
35
|
}
|
|
35
36
|
// ─── Status ───
|
|
36
37
|
export function getStatus() {
|
|
@@ -208,6 +209,13 @@ const SCOPES = [
|
|
|
208
209
|
];
|
|
209
210
|
const REDIRECT_PATH = '/api/calendar/auth/callback'; // Calendar's own callback
|
|
210
211
|
let oauth2Client = null;
|
|
212
|
+
function getRedirectUri() {
|
|
213
|
+
if (!ctx)
|
|
214
|
+
throw new Error('Google Calendar not initialized');
|
|
215
|
+
const override = ctx.secrets.get('GOOGLE_REDIRECT_BASE_URL')?.trim();
|
|
216
|
+
const base = (override || ctx.serverConfig.baseUrl).replace(/\/$/, '');
|
|
217
|
+
return `${base}${REDIRECT_PATH}`;
|
|
218
|
+
}
|
|
211
219
|
export function getAuthUrl() {
|
|
212
220
|
if (!oauth2Client) {
|
|
213
221
|
const clientId = ctx?.secrets.get('GOOGLE_CLIENT_ID');
|
|
@@ -215,7 +223,7 @@ export function getAuthUrl() {
|
|
|
215
223
|
if (!clientId || !clientSecret || !ctx) {
|
|
216
224
|
throw new Error('Google Calendar OAuth not configured');
|
|
217
225
|
}
|
|
218
|
-
oauth2Client = new google.auth.OAuth2(clientId, clientSecret,
|
|
226
|
+
oauth2Client = new google.auth.OAuth2(clientId, clientSecret, getRedirectUri());
|
|
219
227
|
}
|
|
220
228
|
return oauth2Client.generateAuthUrl({
|
|
221
229
|
access_type: 'offline',
|
|
@@ -91,6 +91,14 @@ export const calendarConfigSchema = [
|
|
|
91
91
|
secret: true,
|
|
92
92
|
group: 'Authentication',
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
key: 'GOOGLE_REDIRECT_BASE_URL',
|
|
96
|
+
label: 'OAuth Redirect Base URL',
|
|
97
|
+
type: 'text',
|
|
98
|
+
description: 'Override the base URL used for the OAuth redirect (e.g. http://commander.local:10003). Leave empty to use http://localhost:<port>. Shared with Gmail/Drive. Google does not accept raw IPs — use a domain (you can map one in /etc/hosts).',
|
|
99
|
+
required: false,
|
|
100
|
+
group: 'Authentication',
|
|
101
|
+
},
|
|
94
102
|
{
|
|
95
103
|
key: 'calendarId',
|
|
96
104
|
label: 'Calendar ID',
|
|
@@ -128,6 +136,7 @@ export function getConfigValues(secrets) {
|
|
|
128
136
|
GOOGLE_CLIENT_ID: secrets.get('GOOGLE_CLIENT_ID') ? '********' : '',
|
|
129
137
|
GOOGLE_CLIENT_SECRET: secrets.get('GOOGLE_CLIENT_SECRET') ? '********' : '',
|
|
130
138
|
GOOGLE_REFRESH_TOKEN: secrets.get('GOOGLE_REFRESH_TOKEN') ? '********' : '',
|
|
139
|
+
GOOGLE_REDIRECT_BASE_URL: secrets.get('GOOGLE_REDIRECT_BASE_URL') || '',
|
|
131
140
|
};
|
|
132
141
|
}
|
|
133
142
|
export async function setConfigValues(values, secrets) {
|
|
@@ -138,6 +147,10 @@ export async function setConfigValues(values, secrets) {
|
|
|
138
147
|
secrets.set(key, val);
|
|
139
148
|
}
|
|
140
149
|
}
|
|
150
|
+
// Redirect base URL is shared but not masked — write it whenever it's provided (empty string clears it).
|
|
151
|
+
if (typeof values.GOOGLE_REDIRECT_BASE_URL === 'string') {
|
|
152
|
+
secrets.set('GOOGLE_REDIRECT_BASE_URL', values.GOOGLE_REDIRECT_BASE_URL.trim());
|
|
153
|
+
}
|
|
141
154
|
// Handle non-secret config
|
|
142
155
|
const updates = {};
|
|
143
156
|
if (typeof values.enabled === 'boolean')
|
|
@@ -55,6 +55,13 @@ export const googleCalendarPlugin = {
|
|
|
55
55
|
if (!integrationCtx)
|
|
56
56
|
throw new Error('Google Calendar not initialized');
|
|
57
57
|
await setConfigValues(config, integrationCtx.secrets);
|
|
58
|
+
// Re-initialize so any cached oauth2Client picks up new credentials or redirect URL.
|
|
59
|
+
if (config.GOOGLE_CLIENT_ID !== undefined
|
|
60
|
+
|| config.GOOGLE_CLIENT_SECRET !== undefined
|
|
61
|
+
|| config.GOOGLE_REDIRECT_BASE_URL !== undefined) {
|
|
62
|
+
await calendarClient.shutdown();
|
|
63
|
+
await calendarClient.init(integrationCtx);
|
|
64
|
+
}
|
|
58
65
|
},
|
|
59
66
|
getCustomSettingsComponent() {
|
|
60
67
|
return 'google-oauth';
|
|
@@ -22,6 +22,13 @@ const SCOPES = [
|
|
|
22
22
|
];
|
|
23
23
|
const REDIRECT_PATH = '/api/drive/auth/callback';
|
|
24
24
|
let oauth2Client = null;
|
|
25
|
+
function getRedirectUri() {
|
|
26
|
+
if (!ctx)
|
|
27
|
+
throw new Error('Google Drive not initialized');
|
|
28
|
+
const override = ctx.secrets.get('GOOGLE_REDIRECT_BASE_URL')?.trim();
|
|
29
|
+
const base = (override || ctx.serverConfig.baseUrl).replace(/\/$/, '');
|
|
30
|
+
return `${base}${REDIRECT_PATH}`;
|
|
31
|
+
}
|
|
25
32
|
// ─── Init / Shutdown ───
|
|
26
33
|
export async function init(integrationCtx) {
|
|
27
34
|
ctx = integrationCtx;
|
|
@@ -37,7 +44,7 @@ export async function init(integrationCtx) {
|
|
|
37
44
|
ctx.log.info('Google Drive missing OAuth credentials, skipping init');
|
|
38
45
|
return;
|
|
39
46
|
}
|
|
40
|
-
oauth2Client = new google.auth.OAuth2(clientId, clientSecret,
|
|
47
|
+
oauth2Client = new google.auth.OAuth2(clientId, clientSecret, getRedirectUri());
|
|
41
48
|
oauth2Client.setCredentials({ refresh_token: refreshToken });
|
|
42
49
|
driveApi = google.drive({ version: 'v3', auth: oauth2Client });
|
|
43
50
|
docsApi = google.docs({ version: 'v1', auth: oauth2Client });
|
|
@@ -46,6 +53,7 @@ export async function init(integrationCtx) {
|
|
|
46
53
|
export async function shutdown() {
|
|
47
54
|
driveApi = null;
|
|
48
55
|
docsApi = null;
|
|
56
|
+
oauth2Client = null;
|
|
49
57
|
}
|
|
50
58
|
// ─── Status ───
|
|
51
59
|
export function getStatus() {
|
|
@@ -71,7 +79,7 @@ export function getAuthUrl() {
|
|
|
71
79
|
if (!clientId || !clientSecret || !ctx) {
|
|
72
80
|
throw new Error('Google Drive OAuth not configured');
|
|
73
81
|
}
|
|
74
|
-
oauth2Client = new google.auth.OAuth2(clientId, clientSecret,
|
|
82
|
+
oauth2Client = new google.auth.OAuth2(clientId, clientSecret, getRedirectUri());
|
|
75
83
|
}
|
|
76
84
|
return oauth2Client.generateAuthUrl({
|
|
77
85
|
access_type: 'offline',
|
|
@@ -89,6 +89,14 @@ export const driveConfigSchema = [
|
|
|
89
89
|
secret: true,
|
|
90
90
|
group: 'Authentication',
|
|
91
91
|
},
|
|
92
|
+
{
|
|
93
|
+
key: 'GOOGLE_REDIRECT_BASE_URL',
|
|
94
|
+
label: 'OAuth Redirect Base URL',
|
|
95
|
+
type: 'text',
|
|
96
|
+
description: 'Override the base URL used for the OAuth redirect (e.g. http://commander.local:10003). Leave empty to use http://localhost:<port>. Shared with Gmail/Calendar. Google does not accept raw IPs — use a domain (you can map one in /etc/hosts).',
|
|
97
|
+
required: false,
|
|
98
|
+
group: 'Authentication',
|
|
99
|
+
},
|
|
92
100
|
{
|
|
93
101
|
key: 'defaultFolderId',
|
|
94
102
|
label: 'Default Folder ID',
|
|
@@ -107,6 +115,7 @@ export function getConfigValues(secrets) {
|
|
|
107
115
|
GOOGLE_CLIENT_ID: secrets.get('GOOGLE_CLIENT_ID') ? '********' : '',
|
|
108
116
|
GOOGLE_CLIENT_SECRET: secrets.get('GOOGLE_CLIENT_SECRET') ? '********' : '',
|
|
109
117
|
GOOGLE_REFRESH_TOKEN: secrets.get('GOOGLE_REFRESH_TOKEN') ? '********' : '',
|
|
118
|
+
GOOGLE_REDIRECT_BASE_URL: secrets.get('GOOGLE_REDIRECT_BASE_URL') || '',
|
|
110
119
|
};
|
|
111
120
|
}
|
|
112
121
|
export async function setConfigValues(values, secrets) {
|
|
@@ -117,6 +126,10 @@ export async function setConfigValues(values, secrets) {
|
|
|
117
126
|
secrets.set(key, val);
|
|
118
127
|
}
|
|
119
128
|
}
|
|
129
|
+
// Redirect base URL is shared but not masked — write it whenever it's provided (empty string clears it).
|
|
130
|
+
if (typeof values.GOOGLE_REDIRECT_BASE_URL === 'string') {
|
|
131
|
+
secrets.set('GOOGLE_REDIRECT_BASE_URL', values.GOOGLE_REDIRECT_BASE_URL.trim());
|
|
132
|
+
}
|
|
120
133
|
// Handle non-secret config
|
|
121
134
|
const updates = {};
|
|
122
135
|
if (typeof values.enabled === 'boolean')
|
|
@@ -53,6 +53,13 @@ export const googleDrivePlugin = {
|
|
|
53
53
|
if (!integrationCtx)
|
|
54
54
|
throw new Error('Google Drive not initialized');
|
|
55
55
|
await setConfigValues(config, integrationCtx.secrets);
|
|
56
|
+
// Re-initialize so any cached oauth2Client picks up new credentials or redirect URL.
|
|
57
|
+
if (config.GOOGLE_CLIENT_ID !== undefined
|
|
58
|
+
|| config.GOOGLE_CLIENT_SECRET !== undefined
|
|
59
|
+
|| config.GOOGLE_REDIRECT_BASE_URL !== undefined) {
|
|
60
|
+
await driveClient.shutdown();
|
|
61
|
+
await driveClient.init(integrationCtx);
|
|
62
|
+
}
|
|
56
63
|
},
|
|
57
64
|
getCustomSettingsComponent() {
|
|
58
65
|
return 'google-oauth';
|