feat: agent auth support to refresh token. (#262)

sweep/improve-logging-information
Zhiming Ma 2023-06-23 14:43:55 +08:00 committed by GitHub
parent b6845ddac0
commit 1752f1555b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 388 additions and 126 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -543,6 +543,18 @@ var ApiService = class {
query
});
}
/**
* @param token
* @returns DeviceTokenRefreshResponse Success
* @throws ApiError
*/
deviceTokenRefresh(token) {
return this.httpRequest.request({
method: "POST",
url: "/device-token/refresh",
headers: { Authorization: `Bearer ${token}` }
});
}
/**
* @param body object for anonymous usage tracking
*/
@ -559,7 +571,7 @@ var ApiService = class {
var CloudApi = class {
constructor(config, HttpRequest = AxiosHttpRequest) {
this.request = new HttpRequest({
BASE: config?.BASE ?? "https://tabbyml.app.tabbyml.com/tabby",
BASE: config?.BASE,
VERSION: config?.VERSION ?? "0.0.0",
WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
CREDENTIALS: config?.CREDENTIALS ?? "include",
@ -613,10 +625,10 @@ rootLogger.onChild = (child) => {
var _Auth = class extends import_events.EventEmitter {
constructor(options) {
super();
// 3 days
this.logger = rootLogger.child({ component: "Auth" });
this.dataStore = null;
this.pollingTokenTimer = null;
this.stopPollingTokenTimer = null;
this.refreshTokenTimer = null;
this.authApi = null;
this.jwt = null;
@ -644,10 +656,16 @@ var _Auth = class extends import_events.EventEmitter {
const storedJwt = this.dataStore.data["auth"]?.[this.endpoint]?.jwt;
if (typeof storedJwt === "string" && this.jwt?.token !== storedJwt) {
this.logger.debug({ storedJwt }, "Load jwt from data store.");
this.jwt = {
const jwt = {
token: storedJwt,
payload: (0, import_jwt_decode.default)(storedJwt)
};
if (jwt.payload.exp * 1e3 - Date.now() < _Auth.tokenStrategy.refresh.beforeExpire) {
this.jwt = await this.refreshToken(jwt);
await this.save();
} else {
this.jwt = jwt;
}
this.scheduleRefreshToken();
}
} catch (error) {
@ -686,10 +704,15 @@ var _Auth = class extends import_events.EventEmitter {
clearInterval(this.pollingTokenTimer);
this.pollingTokenTimer = null;
}
if (this.stopPollingTokenTimer) {
clearTimeout(this.stopPollingTokenTimer);
this.stopPollingTokenTimer = null;
}
}
async requestToken() {
try {
await this.reset();
this.logger.debug("Start to request device token");
const deviceToken = await this.authApi.api.deviceToken({ auth_url: this.endpoint });
this.logger.debug({ deviceToken }, "Request device token response");
const authUrl = new URL(_Auth.authPageUrl);
@ -701,6 +724,28 @@ var _Auth = class extends import_events.EventEmitter {
throw error;
}
}
async refreshToken(jwt, retry = 0) {
try {
this.logger.debug({ retry }, "Start to refresh token");
const refreshedJwt = await this.authApi.api.deviceTokenRefresh(jwt.token);
this.logger.debug({ refreshedJwt }, "Refresh token response");
return {
token: refreshedJwt.data.jwt,
payload: (0, import_jwt_decode.default)(refreshedJwt.data.jwt)
};
} catch (error) {
if (error instanceof ApiError && [401, 403, 405].indexOf(error.status) !== -1) {
this.logger.debug({ error }, "Error when refreshing jwt");
} else {
this.logger.error({ error }, "Unknown error when refreshing jwt");
if (retry < _Auth.tokenStrategy.refresh.maxTry) {
this.logger.debug("Retry refreshing jwt");
return this.refreshToken(jwt, retry + 1);
}
}
throw { ...error, retry };
}
}
async schedulePollingToken(code) {
this.pollingTokenTimer = setInterval(async () => {
try {
@ -722,7 +767,13 @@ var _Auth = class extends import_events.EventEmitter {
this.logger.error({ error }, "Error when polling jwt");
}
}
}, _Auth.pollTokenInterval);
}, _Auth.tokenStrategy.polling.interval);
this.stopPollingTokenTimer = setTimeout(() => {
if (this.pollingTokenTimer) {
clearInterval(this.pollingTokenTimer);
this.pollingTokenTimer = null;
}
}, _Auth.tokenStrategy.polling.timeout);
}
scheduleRefreshToken() {
if (this.refreshTokenTimer) {
@ -732,17 +783,39 @@ var _Auth = class extends import_events.EventEmitter {
if (!this.jwt) {
return null;
}
const refreshDelay = Math.max(0, this.jwt.payload.exp * 1e3 - Date.now() - _Auth.refreshTokenInterval);
const refreshDelay = Math.max(
0,
this.jwt.payload.exp * 1e3 - _Auth.tokenStrategy.refresh.beforeExpire - Date.now()
);
this.logger.debug({ refreshDelay }, "Schedule refresh token");
this.refreshTokenTimer = setTimeout(async () => {
this.logger.debug({ expireAt: this.jwt.payload.exp }, "Refresh token");
this.jwt = await this.refreshToken(this.jwt);
await this.save();
this.scheduleRefreshToken();
super.emit("updated", this.jwt);
}, refreshDelay);
}
};
var Auth = _Auth;
Auth.authPageUrl = "https://app.tabbyml.com/account/device-token";
Auth.pollTokenInterval = 5e3;
// 5 seconds
Auth.refreshTokenInterval = 1e3 * 60 * 60 * 24 * 3;
Auth.tokenStrategy = {
polling: {
// polling token after auth url generated
interval: 5e3,
// polling token every 5 seconds
timeout: 5 * 60 * 1e3
// stop polling after trying for 5 min
},
refresh: {
// refresh token 30 min before token expires
// assume a new token expires in 1 day, much longer than 30 min
beforeExpire: 30 * 60 * 1e3,
maxTry: 5,
// try to refresh token 5 times
retryDelay: 2e3
// retry after 2 seconds
}
};
// src/AgentConfig.ts
var defaultAgentConfig = {
@ -1047,7 +1120,6 @@ var _TabbyAgent = class extends import_events2.EventEmitter {
const agent = new _TabbyAgent();
agent.dataStore = options?.dataStore;
agent.anonymousUsageLogger = await AnonymousUsageLogger.create({ dataStore: options?.dataStore });
await agent.applyConfig();
return agent;
}
async applyConfig() {
@ -1119,9 +1191,7 @@ var _TabbyAgent = class extends import_events2.EventEmitter {
if (options.client) {
allLoggers.forEach((logger2) => logger2.setBindings && logger2.setBindings({ client: options.client }));
}
if (options.config) {
await this.updateConfig(options.config);
}
await this.updateConfig(options.config || {});
await this.anonymousUsageLogger.event("AgentInitialized", {
client: options.client
});
@ -1138,7 +1208,7 @@ var _TabbyAgent = class extends import_events2.EventEmitter {
super.emit("configUpdated", event);
}
await this.healthCheck();
return this.status !== "notInitialized";
return true;
}
getConfig() {
return this.config;
@ -1147,6 +1217,9 @@ var _TabbyAgent = class extends import_events2.EventEmitter {
return this.status;
}
startAuth() {
if (this.status === "notInitialized") {
throw new Error("Agent is not initialized");
}
return cancelable(
this.healthCheck().then(() => {
if (this.status === "unauthorized") {

File diff suppressed because one or more lines are too long

View File

@ -13261,6 +13261,18 @@ var ApiService = class {
query
});
}
/**
* @param token
* @returns DeviceTokenRefreshResponse Success
* @throws ApiError
*/
deviceTokenRefresh(token) {
return this.httpRequest.request({
method: "POST",
url: "/device-token/refresh",
headers: { Authorization: `Bearer ${token}` }
});
}
/**
* @param body object for anonymous usage tracking
*/
@ -13277,7 +13289,7 @@ var ApiService = class {
var CloudApi = class {
constructor(config2, HttpRequest = AxiosHttpRequest) {
this.request = new HttpRequest({
BASE: config2?.BASE ?? "https://tabbyml.app.tabbyml.com/tabby",
BASE: config2?.BASE,
VERSION: config2?.VERSION ?? "0.0.0",
WITH_CREDENTIALS: config2?.WITH_CREDENTIALS ?? false,
CREDENTIALS: config2?.CREDENTIALS ?? "include",
@ -13339,10 +13351,10 @@ rootLogger.onChild = (child) => {
var _Auth = class extends EventEmitter {
constructor(options) {
super();
// 3 days
this.logger = rootLogger.child({ component: "Auth" });
this.dataStore = null;
this.pollingTokenTimer = null;
this.stopPollingTokenTimer = null;
this.refreshTokenTimer = null;
this.authApi = null;
this.jwt = null;
@ -13370,10 +13382,16 @@ var _Auth = class extends EventEmitter {
const storedJwt = this.dataStore.data["auth"]?.[this.endpoint]?.jwt;
if (typeof storedJwt === "string" && this.jwt?.token !== storedJwt) {
this.logger.debug({ storedJwt }, "Load jwt from data store.");
this.jwt = {
const jwt = {
token: storedJwt,
payload: jwt_decode_esm_default(storedJwt)
};
if (jwt.payload.exp * 1e3 - Date.now() < _Auth.tokenStrategy.refresh.beforeExpire) {
this.jwt = await this.refreshToken(jwt);
await this.save();
} else {
this.jwt = jwt;
}
this.scheduleRefreshToken();
}
} catch (error) {
@ -13412,10 +13430,15 @@ var _Auth = class extends EventEmitter {
clearInterval(this.pollingTokenTimer);
this.pollingTokenTimer = null;
}
if (this.stopPollingTokenTimer) {
clearTimeout(this.stopPollingTokenTimer);
this.stopPollingTokenTimer = null;
}
}
async requestToken() {
try {
await this.reset();
this.logger.debug("Start to request device token");
const deviceToken = await this.authApi.api.deviceToken({ auth_url: this.endpoint });
this.logger.debug({ deviceToken }, "Request device token response");
const authUrl = new URL(_Auth.authPageUrl);
@ -13427,6 +13450,28 @@ var _Auth = class extends EventEmitter {
throw error;
}
}
async refreshToken(jwt, retry = 0) {
try {
this.logger.debug({ retry }, "Start to refresh token");
const refreshedJwt = await this.authApi.api.deviceTokenRefresh(jwt.token);
this.logger.debug({ refreshedJwt }, "Refresh token response");
return {
token: refreshedJwt.data.jwt,
payload: jwt_decode_esm_default(refreshedJwt.data.jwt)
};
} catch (error) {
if (error instanceof ApiError && [401, 403, 405].indexOf(error.status) !== -1) {
this.logger.debug({ error }, "Error when refreshing jwt");
} else {
this.logger.error({ error }, "Unknown error when refreshing jwt");
if (retry < _Auth.tokenStrategy.refresh.maxTry) {
this.logger.debug("Retry refreshing jwt");
return this.refreshToken(jwt, retry + 1);
}
}
throw { ...error, retry };
}
}
async schedulePollingToken(code) {
this.pollingTokenTimer = setInterval(async () => {
try {
@ -13448,7 +13493,13 @@ var _Auth = class extends EventEmitter {
this.logger.error({ error }, "Error when polling jwt");
}
}
}, _Auth.pollTokenInterval);
}, _Auth.tokenStrategy.polling.interval);
this.stopPollingTokenTimer = setTimeout(() => {
if (this.pollingTokenTimer) {
clearInterval(this.pollingTokenTimer);
this.pollingTokenTimer = null;
}
}, _Auth.tokenStrategy.polling.timeout);
}
scheduleRefreshToken() {
if (this.refreshTokenTimer) {
@ -13458,17 +13509,39 @@ var _Auth = class extends EventEmitter {
if (!this.jwt) {
return null;
}
const refreshDelay = Math.max(0, this.jwt.payload.exp * 1e3 - Date.now() - _Auth.refreshTokenInterval);
const refreshDelay = Math.max(
0,
this.jwt.payload.exp * 1e3 - _Auth.tokenStrategy.refresh.beforeExpire - Date.now()
);
this.logger.debug({ refreshDelay }, "Schedule refresh token");
this.refreshTokenTimer = setTimeout(async () => {
this.logger.debug({ expireAt: this.jwt.payload.exp }, "Refresh token");
this.jwt = await this.refreshToken(this.jwt);
await this.save();
this.scheduleRefreshToken();
super.emit("updated", this.jwt);
}, refreshDelay);
}
};
var Auth = _Auth;
Auth.authPageUrl = "https://app.tabbyml.com/account/device-token";
Auth.pollTokenInterval = 5e3;
// 5 seconds
Auth.refreshTokenInterval = 1e3 * 60 * 60 * 24 * 3;
Auth.tokenStrategy = {
polling: {
// polling token after auth url generated
interval: 5e3,
// polling token every 5 seconds
timeout: 5 * 60 * 1e3
// stop polling after trying for 5 min
},
refresh: {
// refresh token 30 min before token expires
// assume a new token expires in 1 day, much longer than 30 min
beforeExpire: 30 * 60 * 1e3,
maxTry: 5,
// try to refresh token 5 times
retryDelay: 2e3
// retry after 2 seconds
}
};
// src/AgentConfig.ts
init_global();
@ -15127,7 +15200,6 @@ var _TabbyAgent = class extends EventEmitter {
const agent = new _TabbyAgent();
agent.dataStore = options?.dataStore;
agent.anonymousUsageLogger = await AnonymousUsageLogger.create({ dataStore: options?.dataStore });
await agent.applyConfig();
return agent;
}
async applyConfig() {
@ -15199,9 +15271,7 @@ var _TabbyAgent = class extends EventEmitter {
if (options.client) {
allLoggers.forEach((logger2) => logger2.setBindings && logger2.setBindings({ client: options.client }));
}
if (options.config) {
await this.updateConfig(options.config);
}
await this.updateConfig(options.config || {});
await this.anonymousUsageLogger.event("AgentInitialized", {
client: options.client
});
@ -15218,7 +15288,7 @@ var _TabbyAgent = class extends EventEmitter {
super.emit("configUpdated", event);
}
await this.healthCheck();
return this.status !== "notInitialized";
return true;
}
getConfig() {
return this.config;
@ -15227,6 +15297,9 @@ var _TabbyAgent = class extends EventEmitter {
return this.status;
}
startAuth() {
if (this.status === "notInitialized") {
throw new Error("Agent is not initialized");
}
return cancelable(
this.healthCheck().then(() => {
if (this.status === "unauthorized") {

File diff suppressed because one or more lines are too long

View File

@ -9,18 +9,33 @@ export type StorageData = {
auth: { [endpoint: string]: { jwt: string } };
};
type JWT = { token: string; payload: { email: string; exp: number } };
export class Auth extends EventEmitter {
static readonly authPageUrl = "https://app.tabbyml.com/account/device-token";
static readonly pollTokenInterval = 5000; // 5 seconds
static readonly refreshTokenInterval = 1000 * 60 * 60 * 24 * 3; // 3 days
static readonly tokenStrategy = {
polling: {
// polling token after auth url generated
interval: 5000, // polling token every 5 seconds
timeout: 5 * 60 * 1000, // stop polling after trying for 5 min
},
refresh: {
// refresh token 30 min before token expires
// assume a new token expires in 1 day, much longer than 30 min
beforeExpire: 30 * 60 * 1000,
maxTry: 5, // try to refresh token 5 times
retryDelay: 2000, // retry after 2 seconds
},
};
private readonly logger = rootLogger.child({ component: "Auth" });
readonly endpoint: string;
readonly dataStore: DataStore | null = null;
private pollingTokenTimer: ReturnType<typeof setInterval> | null = null;
private stopPollingTokenTimer: ReturnType<typeof setTimeout> | null = null;
private refreshTokenTimer: ReturnType<typeof setTimeout> | null = null;
private authApi: CloudApi | null = null;
private jwt: { token: string; payload: { email: string; exp: number } } | null = null;
private jwt: JWT | null = null;
static async create(options: { endpoint: string; dataStore?: DataStore }): Promise<Auth> {
const auth = new Auth(options);
@ -54,10 +69,17 @@ export class Auth extends EventEmitter {
const storedJwt = this.dataStore.data["auth"]?.[this.endpoint]?.jwt;
if (typeof storedJwt === "string" && this.jwt?.token !== storedJwt) {
this.logger.debug({ storedJwt }, "Load jwt from data store.");
this.jwt = {
const jwt: JWT = {
token: storedJwt,
payload: decodeJwt(storedJwt),
};
// refresh token if it is about to expire or has expired
if (jwt.payload.exp * 1000 - Date.now() < Auth.tokenStrategy.refresh.beforeExpire) {
this.jwt = await this.refreshToken(jwt);
await this.save();
} else {
this.jwt = jwt;
}
this.scheduleRefreshToken();
}
} catch (error: any) {
@ -95,11 +117,16 @@ export class Auth extends EventEmitter {
clearInterval(this.pollingTokenTimer);
this.pollingTokenTimer = null;
}
if (this.stopPollingTokenTimer) {
clearTimeout(this.stopPollingTokenTimer);
this.stopPollingTokenTimer = null;
}
}
async requestToken(): Promise<string> {
try {
await this.reset();
this.logger.debug("Start to request device token");
const deviceToken = await this.authApi.api.deviceToken({ auth_url: this.endpoint });
this.logger.debug({ deviceToken }, "Request device token response");
const authUrl = new URL(Auth.authPageUrl);
@ -112,7 +139,31 @@ export class Auth extends EventEmitter {
}
}
async schedulePollingToken(code: string) {
private async refreshToken(jwt: JWT, retry = 0): Promise<JWT> {
try {
this.logger.debug({ retry }, "Start to refresh token");
const refreshedJwt = await this.authApi.api.deviceTokenRefresh(jwt.token);
this.logger.debug({ refreshedJwt }, "Refresh token response");
return {
token: refreshedJwt.data.jwt,
payload: decodeJwt(refreshedJwt.data.jwt),
};
} catch (error) {
if (error instanceof ApiError && [401, 403, 405].indexOf(error.status) !== -1) {
this.logger.debug({ error }, "Error when refreshing jwt");
} else {
// unknown error, retry a few times
this.logger.error({ error }, "Unknown error when refreshing jwt");
if (retry < Auth.tokenStrategy.refresh.maxTry) {
this.logger.debug("Retry refreshing jwt");
return this.refreshToken(jwt, retry + 1);
}
}
throw { ...error, retry };
}
}
private async schedulePollingToken(code: string) {
this.pollingTokenTimer = setInterval(async () => {
try {
const response = await this.authApi.api.deviceTokenAccept({ code });
@ -134,7 +185,13 @@ export class Auth extends EventEmitter {
this.logger.error({ error }, "Error when polling jwt");
}
}
}, Auth.pollTokenInterval);
}, Auth.tokenStrategy.polling.interval);
this.stopPollingTokenTimer = setTimeout(() => {
if (this.pollingTokenTimer) {
clearInterval(this.pollingTokenTimer);
this.pollingTokenTimer = null;
}
}, Auth.tokenStrategy.polling.timeout);
}
private scheduleRefreshToken() {
@ -146,10 +203,16 @@ export class Auth extends EventEmitter {
return null;
}
const refreshDelay = Math.max(0, this.jwt.payload.exp * 1000 - Date.now() - Auth.refreshTokenInterval);
const refreshDelay = Math.max(
0,
this.jwt.payload.exp * 1000 - Auth.tokenStrategy.refresh.beforeExpire - Date.now()
);
this.logger.debug({ refreshDelay }, "Schedule refresh token");
this.refreshTokenTimer = setTimeout(async () => {
this.logger.debug({ expireAt: this.jwt.payload.exp }, "Refresh token");
// FIXME: not implemented
this.jwt = await this.refreshToken(this.jwt);
await this.save();
this.scheduleRefreshToken();
super.emit("updated", this.jwt);
}, refreshDelay);
}
}

View File

@ -56,7 +56,6 @@ export class TabbyAgent extends EventEmitter implements Agent {
const agent = new TabbyAgent();
agent.dataStore = options?.dataStore;
agent.anonymousUsageLogger = await AnonymousUsageLogger.create({ dataStore: options?.dataStore });
await agent.applyConfig();
return agent;
}
@ -142,9 +141,7 @@ export class TabbyAgent extends EventEmitter implements Agent {
// `pino.Logger.setBindings` is not present in the browser
allLoggers.forEach((logger) => logger.setBindings && logger.setBindings({ client: options.client }));
}
if (options.config) {
await this.updateConfig(options.config);
}
await this.updateConfig(options.config || {});
await this.anonymousUsageLogger.event("AgentInitialized", {
client: options.client,
});
@ -162,7 +159,7 @@ export class TabbyAgent extends EventEmitter implements Agent {
super.emit("configUpdated", event);
}
await this.healthCheck();
return this.status !== "notInitialized";
return true;
}
public getConfig(): AgentConfig {
@ -174,6 +171,9 @@ export class TabbyAgent extends EventEmitter implements Agent {
}
public startAuth(): CancelablePromise<string | null> {
if (this.status === "notInitialized") {
throw new Error("Agent is not initialized");
}
return cancelable(
this.healthCheck().then(() => {
if (this.status === "unauthorized") {

View File

@ -11,7 +11,7 @@ export class CloudApi {
constructor(config?: Partial<OpenAPIConfig>, HttpRequest: HttpRequestConstructor = AxiosHttpRequest) {
this.request = new HttpRequest({
BASE: config?.BASE ?? 'https://tabbyml.app.tabbyml.com/tabby',
BASE: config?.BASE,
VERSION: config?.VERSION ?? "0.0.0",
WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
CREDENTIALS: config?.CREDENTIALS ?? "include",

View File

@ -0,0 +1,5 @@
export type DeviceTokenRefreshResponse = {
data: {
jwt: string;
};
};

View File

@ -1,3 +1,3 @@
export type DeviceTokenResponse = {
export type DeviceTokenRequest = {
auth_url: string;
};

View File

@ -1,9 +1,10 @@
import type { CancelablePromise } from "../../generated/core/CancelablePromise";
import type { BaseHttpRequest } from "../../generated/core/BaseHttpRequest";
import { DeviceTokenRequest } from "../models/DeviceTokenRequest";
import { DeviceTokenResponse } from "../models/DeviceTokenResponse";
import { DeviceTokenAcceptResponse } from "../models/DeviceTokenAcceptResponse";
import type { DeviceTokenRequest } from "../models/DeviceTokenRequest";
import type { DeviceTokenResponse } from "../models/DeviceTokenResponse";
import type { DeviceTokenAcceptResponse } from "../models/DeviceTokenAcceptResponse";
import type { DeviceTokenRefreshResponse } from "../models/DeviceTokenRefreshResponse";
export class ApiService {
constructor(public readonly httpRequest: BaseHttpRequest) {}
@ -33,6 +34,19 @@ export class ApiService {
});
}
/**
* @param token
* @returns DeviceTokenRefreshResponse Success
* @throws ApiError
*/
public deviceTokenRefresh(token: string): CancelablePromise<DeviceTokenRefreshResponse> {
return this.httpRequest.request({
method: "POST",
url: "/device-token/refresh",
headers: { Authorization: `Bearer ${token}` },
});
}
/**
* @param body object for anonymous usage tracking
*/

View File

@ -140,16 +140,22 @@ const emitEvent: Command = {
const openAuthPage: Command = {
command: "tabby.openAuthPage",
callback: () => {
callback: (callbacks?: { onOpenAuthPage?: () => void }) => {
agent()
.startAuth()
.then((authUrl) => {
if (authUrl) {
callbacks?.onOpenAuthPage?.();
env.openExternal(Uri.parse(authUrl));
} else if (agent().getStatus() === "ready") {
notifications.showInformationWhenStartAuthButAlreadyAuthorized();
} else {
notifications.showInformationWhenStartAuthFailed();
}
})
.catch((error) => {
console.debug("Error to start auth", { error })
console.debug("Error to start auth", { error });
notifications.showInformationWhenStartAuthFailed();
});
},
};

View File

@ -52,17 +52,17 @@ function showInformationWhenDisconnected() {
});
}
function showInformationStartAuth() {
function showInformationStartAuth(callbacks?: { onOpenAuthPage?: () => void }) {
window
.showInformationMessage(
"Tabby Server requires authentication. Continue to open authentication page in your browser.",
"Tabby Server requires authorization. Continue to open authorization page in your browser.",
"Continue",
"Settings"
)
.then((selection) => {
switch (selection) {
case "Continue":
commands.executeCommand("tabby.openAuthPage");
commands.executeCommand("tabby.openAuthPage", callbacks);
break;
case "Settings":
commands.executeCommand("tabby.openSettings");
@ -74,6 +74,20 @@ function showInformationAuthSuccess() {
window.showInformationMessage("Congrats, you're authorized, start to use Tabby now.");
}
function showInformationWhenStartAuthButAlreadyAuthorized() {
window.showInformationMessage("You are already authorized now.");
}
function showInformationWhenStartAuthFailed() {
window.showInformationMessage("Cannot connect to server. Please check settings.", "Settings").then((selection) => {
switch (selection) {
case "Settings":
commands.executeCommand("tabby.openSettings");
break;
}
});
}
export const notifications = {
showInformationWhenLoading,
showInformationWhenDisabled,
@ -81,4 +95,6 @@ export const notifications = {
showInformationWhenDisconnected,
showInformationStartAuth,
showInformationAuthSuccess,
showInformationWhenStartAuthButAlreadyAuthorized,
showInformationWhenStartAuthFailed,
};

View File

@ -32,11 +32,23 @@ const fsm = createMachine({
entry: () => toDisconnected(),
},
unauthorized: {
on: { ready: "ready", disconnected: "disconnected", disabled: "disabled" },
on: {
ready: "ready",
disconnected: "disconnected",
disabled: "disabled",
openAuthPage: "unauthorizedAndAuthPageOpen",
},
entry: () => {
toUnauthorized();
notifications.showInformationStartAuth();
notifications.showInformationStartAuth({
onOpenAuthPage: () => {
fsmService.send("openAuthPage");
},
});
},
},
unauthorizedAndAuthPageOpen: {
on: { ready: "ready", disconnected: "disconnected", disabled: "disabled" },
exit: (_, event) => {
if (event.type === "ready") {
notifications.showInformationAuthSuccess();
@ -79,7 +91,7 @@ function toUnauthorized() {
item.color = colorWarning;
item.backgroundColor = backgroundColorWarning;
item.text = `${iconUnauthorized} ${label}`;
item.tooltip = "Tabby Server requires authentication. Click to continue.";
item.tooltip = "Tabby Server requires authorization. Click to continue.";
item.command = { title: "", command: "tabby.statusBarItemClicked", arguments: ["unauthorized"] };
}