Skip to content

Commit 6352b6d

Browse files
Merge pull request #287 from Power-Maverick/copilot/fix-connection-timeout-indicator
Fix: Add visual indicators and notifications for expired connection tokens
2 parents 17cadc8 + e76d7da commit 6352b6d

File tree

7 files changed

+236
-6
lines changed

7 files changed

+236
-6
lines changed
Lines changed: 58 additions & 0 deletions
Loading
Lines changed: 58 additions & 0 deletions
Loading

src/commands/registerCommands.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ export async function registerCommands(vscontext: vscode.ExtensionContext, tr: T
111111
}
112112
},
113113
},
114+
{
115+
command: "dvdt.explorer.connections.updateStatusBar",
116+
callback: (conn: IConnection | undefined) => {
117+
try {
118+
updateConnectionStatusBar(conn);
119+
} catch (error) {
120+
errorHandler.log(error, "updateStatusBar");
121+
}
122+
},
123+
},
114124
{
115125
command: "dvdt.explorer.entities.showEntityDetails",
116126
callback: async (enItem: EntitiesTreeItem) => {
@@ -313,7 +323,12 @@ export async function registerCommands(vscontext: vscode.ExtensionContext, tr: T
313323
*/
314324
export function updateConnectionStatusBar(conn: IConnection | undefined): void {
315325
if (conn) {
316-
dvStatusBarItem.text = conn.userName ? `Connected to: ${conn.environmentUrl} as ${conn.userName}` : `Connected to: ${conn.environmentUrl}`;
326+
const isExpired = conn.tokenExpiresAt ? Date.now() >= conn.tokenExpiresAt : false;
327+
const statusIcon = isExpired ? "$(warning)" : "$(plug)";
328+
const statusSuffix = isExpired ? " (Token Expired)" : "";
329+
dvStatusBarItem.text = conn.userName
330+
? `${statusIcon} Connected to: ${conn.environmentUrl} as ${conn.userName}${statusSuffix}`
331+
: `${statusIcon} Connected to: ${conn.environmentUrl}${statusSuffix}`;
317332
dvStatusBarItem.show();
318333
} else {
319334
dvStatusBarItem.hide();

src/extension.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const extensionVersion = extension.packageJSON.version;
1515
// telemetry reporter
1616
let reporter: TelemetryReporter;
1717

18+
// Token expiration check interval (in milliseconds) - check every 60 seconds
19+
const TOKEN_CHECK_INTERVAL = 60000;
20+
let tokenExpirationTimer: NodeJS.Timeout | undefined;
21+
1822
// this method is called when your extension is activated
1923
// your extension is activated the very first time the command is executed
2024
export function activate(context: vscode.ExtensionContext) {
@@ -29,6 +33,9 @@ export function activate(context: vscode.ExtensionContext) {
2933
registerTreeDataProviders(context, reporter);
3034
registerCommands(context, reporter);
3135

36+
// Start periodic token expiration check
37+
startTokenExpirationCheck(context);
38+
3239
let dataverseToolsPublicApi = {
3340
currentConnectionToken() {
3441
const dvHelper = new DataverseHelper(context);
@@ -41,5 +48,57 @@ export function activate(context: vscode.ExtensionContext) {
4148

4249
// this method is called when your extension is deactivated
4350
export function deactivate() {
51+
if (tokenExpirationTimer) {
52+
clearInterval(tokenExpirationTimer);
53+
}
4454
reporter.dispose();
4555
}
56+
57+
/**
58+
* Start periodic token expiration check
59+
*/
60+
function startTokenExpirationCheck(context: vscode.ExtensionContext) {
61+
let lastNotifiedExpiration = false;
62+
63+
tokenExpirationTimer = setInterval(() => {
64+
const dvHelper = new DataverseHelper(context);
65+
const isExpired = dvHelper.isCurrentConnectionTokenExpired();
66+
67+
if (isExpired && !lastNotifiedExpiration) {
68+
// Get current connection to create tree item for reconnect
69+
const conn = dvHelper.getCurrentWorkspaceConnection();
70+
71+
// Show notification to user
72+
vscode.window.showWarningMessage(
73+
"Your Dataverse connection token has expired. Please reconnect to continue working.",
74+
"Reconnect"
75+
).then(selection => {
76+
if (selection === "Reconnect" && conn) {
77+
// Create a tree item with the connection name to pass to the command
78+
const connItem = {
79+
label: conn.connectionName,
80+
desc: conn.userName,
81+
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
82+
level: 2,
83+
current: true,
84+
expired: true
85+
};
86+
vscode.commands.executeCommand("dvdt.explorer.connections.connectDataverse", connItem);
87+
}
88+
});
89+
90+
lastNotifiedExpiration = true;
91+
92+
// Refresh the connection tree to show expired icon
93+
vscode.commands.executeCommand("dvdt.explorer.connections.refreshConnection");
94+
95+
// Update status bar to show expired state
96+
if (conn) {
97+
vscode.commands.executeCommand("dvdt.explorer.connections.updateStatusBar", conn);
98+
}
99+
} else if (!isExpired) {
100+
// Reset notification flag when token is refreshed
101+
lastNotifiedExpiration = false;
102+
}
103+
}, TOKEN_CHECK_INTERVAL);
104+
}

src/helpers/dataverseHelper.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export class DataverseHelper {
6666
if (tokenResponse) {
6767
conn.currentAccessToken = tokenResponse.access_token!;
6868
conn.refreshToken = tokenResponse.refresh_token!;
69+
// Set token expiration timestamp (expires_in is in seconds, convert to milliseconds)
70+
conn.tokenExpiresAt = Date.now() + (tokenResponse.expires_in * 1000);
6971
this.vsstate.saveInWorkspace(connectionCurrentStoreKey, conn);
7072
}
7173
}
@@ -132,6 +134,8 @@ export class DataverseHelper {
132134
break;
133135
}
134136
conn.refreshToken = tokenResponse.refresh_token!;
137+
// Set token expiration timestamp (expires_in is in seconds, convert to milliseconds)
138+
conn.tokenExpiresAt = Date.now() + (tokenResponse.expires_in * 1000);
135139
} else {
136140
vscode.window.showErrorMessage("Unable to connect to Dataverse. Please try again.");
137141
return undefined;
@@ -180,6 +184,15 @@ export class DataverseHelper {
180184
return undefined;
181185
}
182186

187+
/**
188+
* Get the current workspace connection without reloading data.
189+
* @returns The connection object.
190+
*/
191+
public getCurrentWorkspaceConnection(): IConnection | undefined {
192+
const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey);
193+
return connFromWS;
194+
}
195+
183196
/**
184197
* Get the current access token from the current connection.
185198
* @returns The current access token.
@@ -189,6 +202,19 @@ export class DataverseHelper {
189202
return connFromWS.currentAccessToken;
190203
}
191204

205+
/**
206+
* Check if the current connection's token is expired.
207+
* @returns True if the token is expired, false otherwise.
208+
*/
209+
public isCurrentConnectionTokenExpired(): boolean {
210+
const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey);
211+
if (connFromWS && connFromWS.tokenExpiresAt) {
212+
return Date.now() >= connFromWS.tokenExpiresAt;
213+
}
214+
// If no expiration time is set, assume it's not expired for backward compatibility
215+
return false;
216+
}
217+
192218
/**
193219
* Get the entty definition from the current connection.
194220
*/
@@ -428,6 +454,8 @@ export class DataverseHelper {
428454
if (tokenResponse) {
429455
currentConnection.currentAccessToken = tokenResponse.access_token;
430456
currentConnection.refreshToken = tokenResponse.refresh_token;
457+
// Set token expiration timestamp (expires_in is in seconds, convert to milliseconds)
458+
currentConnection.tokenExpiresAt = Date.now() + (tokenResponse.expires_in * 1000);
431459
}
432460

433461
this.vsstate.saveInWorkspace(connectionCurrentStoreKey, currentConnection);

0 commit comments

Comments
 (0)