Skip to content

Commit

Permalink
chore: port to node.js
Browse files Browse the repository at this point in the history
  • Loading branch information
ssut committed Aug 16, 2019
1 parent 46437f3 commit 6c58fb3
Show file tree
Hide file tree
Showing 17 changed files with 6,221 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

# Use 4 spaces for the Python files
[*.py]
indent_size = 4
max_line_length = 80

# The JSON files contain newlines inconsistently
[*.json]
insert_final_newline = ignore

# Minified JavaScript files shouldn't be changed
[**.min.js]
indent_style = ignore
insert_final_newline = ignore

# Makefiles always use tabs for indentation
[Makefile]
indent_style = tab

# Batch files use tabs for indentation
[*.bat]
indent_style = tab

[*.md]
trim_trailing_whitespace = false

110 changes: 110 additions & 0 deletions lib/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { NotLoggedInError } from './core/errors';
import { Client } from './client';
import { Auth } from './core/auth';
import { Gateway } from './core/gateway';
import commander from 'commander';
import packageJson from '../package.json';
import * as constants from './core/constants';

import * as fs from 'fs';
import * as readline from 'readline';
import * as path from 'path';

const input = (question: string) => new Promise<string>((resolve) => {
const rl = readline.createInterface(process.stdin, process.stdout);
rl.question(question, (answer) => resolve(answer));
});

const options = {
country: constants.DEFAULT_COUNTRY,
language: constants.DEFAULT_LANGUAGE,
statePath: 'wideq-state.json',
};

const program = new commander.Command('WideQJS');
program
.version(packageJson.version)
.option('-C, --country <type>', 'Country code for account', constants.DEFAULT_COUNTRY)
.on('option:country', (value) => options.country = value)
.option('-l, --language <type>', 'Language code for account', constants.DEFAULT_LANGUAGE)
.on('option:language', (value) => options.language = value)
.option('-S, --state-path <type>', 'State file path', 'wideq-state.json')
.on('option:statePath', (value) => options.statePath = value);

program
.command('auth')
.description('Authenticate')
.action(async () => {
const { country, language, statePath } = options;

const client = await init(country, language, statePath);

saveState(statePath, client);
});

program
.command('ls', { isDefault: true })
.description('List devices')
.action(async function (...args) {
const { country, language, statePath } = options;
const client = await init(country, language, statePath);

for (const device of client.devices) {
console.info(String(device));
}

saveState(statePath, client);
});

async function authenticate(gateway: Gateway) {
const loginUrl = gateway.oauthUrl;

console.info('Log in here:', loginUrl);
const callbackUrl = await input('Then paste the URL where the browser is redirected: ');

return Auth.fromUrl(gateway, callbackUrl);
}

async function init(country: string, language: string, stateFilePath?: string) {
let state: any = {};

if (stateFilePath) {
if (fs.existsSync(stateFilePath)) {
try {
state = JSON.parse(fs.readFileSync(stateFilePath).toString());
} catch { }
}
}

const client = Client.loadFromState({
country,
language,

...state,
});

if (!client.gateway) {
client.gateway = await Gateway.discover(country, language);
}

if (!client.auth) {
client.auth = await authenticate(client.gateway);
}

if (!client.session && client.auth) {
({
session: client.session,
items: client.devices,
} = await client.auth.startSession());
}

await client.updateDevices();

return client;
}

function saveState(stateFilePath: string, client: Client) {
fs.writeFileSync(stateFilePath, JSON.stringify(client.toStateObject()));
}

program.parse(process.argv);
152 changes: 152 additions & 0 deletions lib/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Auth } from './core/auth';
import { Gateway } from './core/gateway';
import { Session } from './core/session';
import * as constants from './core/constants';
import { Device } from './core/device';
import { DeviceInfo } from './core/device-info';
import { ModelInfo } from './core/model-info';

export class Client {
public devices: any[] = [];
public modelInfo: { [key: string]: any } = {};

public constructor(
public gateway: Gateway,
public auth: Auth,
public session: Session | null,
public country = constants.DEFAULT_COUNTRY,
public language = constants.DEFAULT_LANGUAGE,
) {
}

public static async loadFromToken(refreshToken: string, country: string = constants.DEFAULT_COUNTRY, language: string = constants.DEFAULT_LANGUAGE) {
const gateway = await Gateway.discover(country, language);
const auth = new Auth(gateway, null, refreshToken);

const client = new Client(gateway, auth, null, country, language);
await client.refresh();

return client;
}

public static loadFromState(state: {
[key in 'gateway' | 'auth' | 'session' | 'modelInfo' | 'country' | 'language']: any;
}) {
let gateway: Gateway;
let auth: Auth;
let session: Session;
let modelInfo: Client['modelInfo'] = {};
let country: string = constants.DEFAULT_COUNTRY;
let language: string = constants.DEFAULT_LANGUAGE;

for (const key of Object.keys(state)) {
switch (key) {
case 'gateway': {
const data = state.gateway;
gateway = new Gateway(
data.authBase,
data.apiRoot,
data.oauthRoot,
data.country || constants.DEFAULT_COUNTRY,
data.language || constants.DEFAULT_LANGUAGE,
);
} break;

case 'auth': {
const data = state.auth;
auth = new Auth(
gateway!,
data.accessToken,
data.refreshToken,
);
} break;

case 'session':
session = new Session(auth!, state.session);
break;

case 'modelInfo':
modelInfo = state.modelInfo;
break;

case 'country':
country = state.country;
break;

case 'language':
language = state.language;
break;
}
}

const client = new Client(
gateway!,
auth!,
session!,
country,
language,
);
client.modelInfo = modelInfo;

return client;
}

public toStateObject() {
return {
modelInfo: this.modelInfo,

gateway: !this.gateway
? undefined
: {
authBase: this.gateway.authBase,
apiRoot: this.gateway.apiRoot,
oauthRoot: this.gateway.oauthRoot,
country: this.gateway.country,
language: this.gateway.language,
},

auth: !this.auth
? undefined
: {
accessToken: this.auth.accessToken,
refreshToken: this.auth.refreshToken,
},

session: !this.session
? undefined
: this.session.sessionId,

country: this.country,
language: this.language,
};
}

public async updateDevices() {
const devices: any[] = await this.session!.getDevices();
const deviceInfos = devices.map(device => new DeviceInfo(device));

this.devices = deviceInfos;
}

public async getDevice(deviceId: string) {
return this.devices.find(({ id }) => id === deviceId);
}

public async refresh() {
this.auth = await this.auth.refresh();

({
session: this.session,
items: this.devices,
} = await this.auth.startSession());
}

public async getModelInfo(device: DeviceInfo) {
const url = device.modelInfoUrl;
if (!(url in this.modelInfo)) {
this.modelInfo[url] = await device.loadModelInfo();
}

return new ModelInfo(this.modelInfo[url]);
}
}
Loading

0 comments on commit 6c58fb3

Please sign in to comment.