appcenter-cli
Version:
Command line tool for Visual Studio App Center
177 lines (151 loc) • 4.93 kB
text/typescript
// Information storage and retrieval about the current user
//
// Right now we only support a single logged in user
import * as fs from "fs";
import * as path from "path";
import * as mkdirp from "mkdirp";
import { environments } from "./environments";
import { profileFile, getProfileDir, fileExistsSync } from "../misc";
import { TokenValueType, tokenStore } from "../token-store";
const debug = require("debug")("appcenter-cli:util:profile:profile");
export interface UpdatableProfile {
userId: string;
userName: string;
displayName: string;
email: string;
environment: string;
readonly accessTokenId: Promise<string>;
readonly endpoint: string;
defaultApp?: DefaultApp;
}
export interface Profile extends UpdatableProfile {
readonly accessToken: Promise<string>;
readonly tokenSuppliedByUser: boolean;
save(): Profile;
logout(): Promise<void>;
}
export interface DefaultApp {
ownerName: string;
appName: string;
identifier: string;
}
class ProfileImpl implements Profile {
userId: string;
userName: string;
displayName: string;
email: string;
environment: string;
defaultApp?: DefaultApp;
tokenSuppliedByUser: boolean;
get accessTokenId(): Promise<string> {
return tokenStore.get(this.userName)
.then((entry) => entry.accessToken.id)
.catch((err: Error) => {
debug(`Failed to get token id from profile, error: ${err.message}`);
throw err;
});
}
get accessToken(): Promise<string> {
const getter = tokenStore.get(this.userName)
.catch((err: Error) => tokenStore.get(this.userName, true));
return getter
.then((entry) => entry.accessToken.token)
.catch((err: Error) => {
debug(`Failed to get token from profile, error: ${err.message}`);
throw err;
});
}
get endpoint(): string {
return environments(this.environment).endpoint;
}
constructor(fileContents: any) {
// This is slightly convoluted since file and API use different field names
// TODO: Normalize to match them up?
this.userId = fileContents.userId || fileContents.id;
this.userName = fileContents.userName || fileContents.name;
this.displayName = fileContents.displayName;
this.email = fileContents.email;
this.environment = fileContents.environment;
this.defaultApp = fileContents.defaultApp;
this.tokenSuppliedByUser = fileContents.tokenSuppliedByUser || false;
}
save(): Profile {
const profile: any = {
userId: this.userId,
userName: this.userName,
displayName: this.displayName,
email: this.email,
environment: this.environment,
defaultApp: this.defaultApp,
tokenSuppliedByUser: this.tokenSuppliedByUser
};
mkdirp.sync(getProfileDir());
fs.writeFileSync(getProfileFilename(), JSON.stringify(profile), { encoding: "utf8" });
return this;
}
setAccessToken(token: TokenValueType): Promise<Profile> {
return tokenStore.set(this.userName, token).then(() => this);
}
async logout(): Promise<void> {
await tokenStore.remove(this.userName);
try {
fs.unlinkSync(getProfileFilename());
} catch (err) {
if (err.code !== "ENOENT") {
// File not found is fine, anything else pass on the error
throw err;
}
}
}
}
const validApp = /^([a-zA-Z0-9-_.]{1,100})\/([a-zA-Z0-9-_.]{1,100})$/;
export function toDefaultApp(app: string): DefaultApp {
const matches = app.match(validApp);
if (matches !== null) {
return {
ownerName: matches[1],
appName: matches[2],
identifier: `${matches[1]}/${matches[2]}`
};
}
return null;
}
let currentProfile: Profile | null = null;
function getProfileFilename(): string {
const profileDir = getProfileDir();
return path.join(profileDir, profileFile);
}
function loadProfile(): Profile | null {
const profilePath = getProfileFilename();
debug(`Loading profile from ${profilePath}`);
if (!fileExistsSync(profilePath)) {
debug("No profile file exists");
return null;
}
debug("Profile file loaded");
const profileContents = fs.readFileSync(profilePath, "utf8");
const profile: any = JSON.parse(profileContents);
return new ProfileImpl(profile);
}
export function getUser(): Profile | null {
debug("Getting current user from profile");
if (!currentProfile) {
debug("No current user, loading from file");
currentProfile = loadProfile();
}
return currentProfile;
}
export function saveUser(user: any, token: TokenValueType, environment: string, tokenSuppliedByUser: boolean): Promise<Profile> {
return tokenStore.set(user.name, token)
.then(() => {
const profile = new ProfileImpl(Object.assign({}, user, { environment, tokenSuppliedByUser }));
profile.save();
return profile;
});
}
export async function deleteUser(): Promise<void> {
const profile = getUser();
if (profile) {
return profile.logout();
}
}