Merge pull request #142 from teaxyz/refactor-tauri-api

#136 refactor api integration
This commit is contained in:
Neil 2023-01-12 16:56:00 +08:00 committed by GitHub
commit 3b80e8f431
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 111 additions and 110 deletions

View file

@ -43,12 +43,14 @@
"dependencies": {
"@tauri-apps/api": "^1.2.0",
"@types/bcryptjs": "^2.4.2",
"@types/url-join": "^4.0.1",
"bcryptjs": "^2.4.3",
"buffer": "^6.0.3",
"fuse.js": "^6.6.2",
"lodash": "^4.17.21",
"lorem-ipsum": "^2.0.8",
"svelte-watch-resize": "^1.0.3"
"svelte-watch-resize": "^1.0.3",
"url-join": "^5.0.0"
},
"pnpm": {
"onlyBuiltDependencies": [

View file

@ -2,13 +2,13 @@
import { open } from '@tauri-apps/api/shell';
import { authStore } from '$libs/stores';
import type { Developer } from '@tea/ui/types';
import { apiBaseUrl } from '@api';
import { baseUrl } from '$libs/v1Client';
let user: Developer | null = null;
const deviceId = authStore.deviceIdStore;
const openGithub = () => {
open(`${apiBaseUrl}/auth/user?device_id=${$deviceId}`);
open(`${baseUrl}/auth/user?device_id=${$deviceId}`);
try {
authStore.pollSession();
} catch (error) {

View file

@ -11,8 +11,6 @@ import { PackageStates } from '../types';
import { loremIpsum } from 'lorem-ipsum';
import _ from 'lodash';
export const apiBaseUrl = 'https://api.tea.xyz/v1';
const packages: Package[] = [
{
slug: 'mesonbuild_com',

View file

@ -11,73 +11,22 @@
* - connect to a local platform api and returns a data
*/
import { getClient } from '@tauri-apps/api/http';
// import { invoke } from '@tauri-apps/api';
import { Command } from '@tauri-apps/api/shell';
import { readDir, BaseDirectory } from '@tauri-apps/api/fs';
import urlJoin from 'url-join';
import type { Package, Review, AirtablePost, Developer, Bottle } from '@tea/ui/types';
import type { GUIPackage, Course, Category, AuthStatus } from '../types';
import type { GUIPackage, Course, Category, AuthStatus, DeviceAuth } from '../types';
import * as mock from './mock';
import { PackageStates } from '../types';
import { getSession } from '$libs/stores/auth';
import type { Session } from '$libs/stores/auth';
import { getInstalledPackages } from '$libs/teaDir';
import bcrypt from 'bcryptjs';
import { installPackageCommand } from '$libs/cli';
export const apiBaseUrl = 'https://api.tea.xyz/v1';
// export const apiBaseUrl = 'http://localhost:3000/v1';
async function getHeaders(path: string, session: Session) {
const unixMs = new Date().getTime();
const unixHexSecs = Math.round(unixMs / 1000).toString(16); // hex
const deviceId = session.device_id?.split('-')[0];
const preHash = [unixHexSecs, session.key, deviceId, path].join('');
const Authorization = bcrypt.hashSync(preHash, 10);
return {
Authorization,
['tea-ts']: unixMs.toString(),
['tea-uid']: session.user?.developer_id,
['tea-gui_id']: session.device_id
};
}
async function get<T>(path: string, query?: { [key: string]: string }) {
const [session, client] = await Promise.all([getSession(), getClient()]);
const uri = join(apiBaseUrl, path);
const headers =
session?.device_id && session?.user
? await getHeaders(`GET/${path}`, session)
: { Authorization: 'public ' };
const { data } = await client.get<T>(uri.toString(), {
headers,
query: query || {}
});
return data;
}
const join = function (...paths: string[]) {
return paths
.map(function (path) {
if (path[0] === '/') {
path = path.slice(1);
}
if (path[path.length - 1] === '/') {
path = path.slice(0, path.length - 1);
}
return path;
})
.join('/');
};
import { get as apiGet } from '$libs/v1Client';
export async function getPackages(): Promise<GUIPackage[]> {
const [packages, installedPackages] = await Promise.all([
get<Package[]>('packages'),
apiGet<Package[]>('packages'),
getInstalledPackages()
]);
@ -98,7 +47,7 @@ export async function getFeaturedPackages(): Promise<Package[]> {
export async function getPackageReviews(full_name: string): Promise<Review[]> {
console.log(`getting reviews for ${full_name}`);
const reviews: Review[] = await get<Review[]>(
const reviews: Review[] = await apiGet<Review[]>(
`packages/${full_name.replaceAll('/', ':')}/reviews`
);
@ -113,40 +62,8 @@ export async function installPackage(full_name: string) {
}
}
async function installPackageCommand(full_name: string) {
return new Promise((resolve, reject) => {
const teaInstallCommand = new Command('tea-install', [`+${full_name}`, 'true']);
teaInstallCommand.on('error', reject);
const handleLineOutput = async (line: string | { code: number }) => {
const c = await child;
if (
(typeof line === 'string' && line.includes('installed:')) ||
(typeof line !== 'string' && line?.code === 0)
) {
c.kill();
resolve(c.pid);
} else if (typeof line !== 'string' && line?.code === 1) {
reject();
}
};
teaInstallCommand.stdout.on('data', handleLineOutput);
teaInstallCommand.stderr.on('data', handleLineOutput);
teaInstallCommand.on('close', (line: string) => {
console.log('command closed!');
handleLineOutput(line || '');
});
teaInstallCommand.on('error', (line: string) => {
console.log('command error!', line);
handleLineOutput(line || '');
});
const child = teaInstallCommand.spawn();
});
}
export async function getFeaturedCourses(): Promise<Course[]> {
const posts = await get<AirtablePost[]>('posts', { tag: 'featured_course' });
const posts = await apiGet<AirtablePost[]>('posts', { tag: 'featured_course' });
return posts.map((post) => {
return {
title: post.title,
@ -164,36 +81,30 @@ export async function getTopPackages(): Promise<GUIPackage[]> {
export async function getAllPosts(tag?: string): Promise<AirtablePost[]> {
// add filter here someday: tag = news | course
const posts = await get<AirtablePost[]>('posts', tag ? { tag } : {});
const posts = await apiGet<AirtablePost[]>('posts', tag ? { tag } : {});
return posts;
}
export async function getCategorizedPackages(): Promise<Category[]> {
const categories = await get<Category[]>('/packages/categorized');
const categories = await apiGet<Category[]>('/packages/categorized');
return categories;
}
type DeviceAuth = {
status: AuthStatus;
user: Developer;
key: string;
};
export async function getDeviceAuth(deviceId: string): Promise<DeviceAuth> {
const data = await get<DeviceAuth>(`/auth/device/${deviceId}`);
const data = await apiGet<DeviceAuth>(`/auth/device/${deviceId}`);
return data;
}
export async function getPackageBottles(packageName: string): Promise<Bottle[]> {
console.log('getting bottles for ', packageName);
const client = await getClient();
const uri = join('https://app.tea.xyz/api/bottles/', packageName);
const uri = urlJoin('https://app.tea.xyz/api/bottles/', packageName);
const { data } = await client.get<Bottle[]>(uri.toString());
console.log('got bottles', data);
return data;
}
export async function registerDevice(): Promise<string> {
const { deviceId } = await get<{ deviceId: string }>('/auth/registerDevice');
const { deviceId } = await apiGet<{ deviceId: string }>('/auth/registerDevice');
return deviceId;
}

View file

@ -0,0 +1,33 @@
import { Command } from '@tauri-apps/api/shell';
export async function installPackageCommand(full_name: string) {
return new Promise((resolve, reject) => {
const teaInstallCommand = new Command('tea-install', [`+${full_name}`, 'true']);
teaInstallCommand.on('error', reject);
const handleLineOutput = async (line: string | { code: number }) => {
const c = await child;
if (
(typeof line === 'string' && line.includes('installed:')) ||
(typeof line !== 'string' && line?.code === 0)
) {
c.kill();
resolve(c.pid);
} else if (typeof line !== 'string' && line?.code === 1) {
reject();
}
};
teaInstallCommand.stdout.on('data', handleLineOutput);
teaInstallCommand.stderr.on('data', handleLineOutput);
teaInstallCommand.on('close', (line: string) => {
console.log('command closed!');
handleLineOutput(line || '');
});
teaInstallCommand.on('error', (line: string) => {
console.log('command error!', line);
handleLineOutput(line || '');
});
const child = teaInstallCommand.spawn();
});
}

View file

@ -8,8 +8,6 @@ import { getPackages, getFeaturedPackages, getPackageReviews, getAllPosts } from
import initAuthStore from './stores/auth';
import initNavStore from './stores/nav';
export const backLink = writable<string>('/');
export const featuredPackages = writable<Package[]>([]);
function initPackagesStore() {

View file

@ -3,7 +3,7 @@
// please use the package @tea/ui/src/types.ts
// things that go there are shared types/shapes like ie: Package
import type { Package } from '@tea/ui/types';
import type { Package, Developer } from '@tea/ui/types';
export enum PackageStates {
AVAILABLE,
@ -36,3 +36,9 @@ export enum AuthStatus {
SUCCESS = 'SUCCESS',
FAILED = 'FAILED'
}
export type DeviceAuth = {
status: AuthStatus;
user: Developer;
key: string;
};

View file

@ -0,0 +1,40 @@
import { getClient } from '@tauri-apps/api/http';
import type { Session } from '$libs/stores/auth';
import bcrypt from 'bcryptjs';
import { getSession } from '$libs/stores/auth';
import urlJoin from 'url-join';
export const baseUrl = 'https://api.tea.xyz/v1';
export async function get<T>(path: string, query?: { [key: string]: string }) {
const [session, client] = await Promise.all([getSession(), getClient()]);
const uri = urlJoin(baseUrl, path);
const headers =
session?.device_id && session?.user
? await getHeaders(`GET/${path}`, session)
: { Authorization: 'public ' };
const { data } = await client.get<T>(uri.toString(), {
headers,
query: query || {}
});
return data;
}
async function getHeaders(path: string, session: Session) {
const unixMs = new Date().getTime();
const unixHexSecs = Math.round(unixMs / 1000).toString(16); // hex
const deviceId = session.device_id?.split('-')[0];
const preHash = [unixHexSecs, session.key, deviceId, path].join('');
const Authorization = bcrypt.hashSync(preHash, 10);
return {
Authorization,
['tea-ts']: unixMs.toString(),
['tea-uid']: session.user?.developer_id,
['tea-gui_id']: session.device_id
};
}

View file

@ -15,6 +15,7 @@ importers:
'@tauri-apps/cli': ^1.2.2
'@tea/ui': workspace:*
'@types/bcryptjs': ^2.4.2
'@types/url-join': ^4.0.1
'@typescript-eslint/eslint-plugin': ^5.27.0
'@typescript-eslint/parser': ^5.27.0
autoprefixer: ^10.4.13
@ -38,16 +39,19 @@ importers:
tailwindcss: ^3.2.4
tslib: ^2.3.1
typescript: ^4.7.4
url-join: ^5.0.0
vite: ^4.0.0
dependencies:
'@tauri-apps/api': 1.2.0
'@types/bcryptjs': 2.4.2
'@types/url-join': 4.0.1
bcryptjs: 2.4.3
buffer: 6.0.3
fuse.js: 6.6.2
lodash: 4.17.21
lorem-ipsum: 2.0.8
svelte-watch-resize: 1.0.3
url-join: 5.0.0
devDependencies:
'@playwright/test': 1.25.0
'@sveltejs/adapter-auto': 1.0.0_@sveltejs+kit@1.0.1
@ -3896,6 +3900,10 @@ packages:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: true
/@types/url-join/4.0.1:
resolution: {integrity: sha512-wDXw9LEEUHyV+7UWy7U315nrJGJ7p1BzaCxDpEoLr789Dk1WDVMMlf3iBfbG2F8NdWnYyFbtTxUn2ZNbm1Q4LQ==}
dev: false
/@types/webpack-env/1.18.0:
resolution: {integrity: sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==}
dev: true
@ -10345,6 +10353,11 @@ packages:
deprecated: Please see https://github.com/lydell/urix#deprecated
dev: true
/url-join/5.0.0:
resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/use/3.1.1:
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
engines: {node: '>=0.10.0'}