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": { "dependencies": {
"@tauri-apps/api": "^1.2.0", "@tauri-apps/api": "^1.2.0",
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/url-join": "^4.0.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lorem-ipsum": "^2.0.8", "lorem-ipsum": "^2.0.8",
"svelte-watch-resize": "^1.0.3" "svelte-watch-resize": "^1.0.3",
"url-join": "^5.0.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [

View file

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

View file

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

View file

@ -11,73 +11,22 @@
* - connect to a local platform api and returns a data * - connect to a local platform api and returns a data
*/ */
import { getClient } from '@tauri-apps/api/http'; import { getClient } from '@tauri-apps/api/http';
// import { invoke } from '@tauri-apps/api';
import { Command } from '@tauri-apps/api/shell'; import urlJoin from 'url-join';
import { readDir, BaseDirectory } from '@tauri-apps/api/fs';
import type { Package, Review, AirtablePost, Developer, Bottle } from '@tea/ui/types'; 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 * as mock from './mock';
import { PackageStates } from '../types'; import { PackageStates } from '../types';
import { getSession } from '$libs/stores/auth';
import type { Session } from '$libs/stores/auth';
import { getInstalledPackages } from '$libs/teaDir'; import { getInstalledPackages } from '$libs/teaDir';
import bcrypt from 'bcryptjs'; import { installPackageCommand } from '$libs/cli';
export const apiBaseUrl = 'https://api.tea.xyz/v1'; import { get as apiGet } from '$libs/v1Client';
// 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('/');
};
export async function getPackages(): Promise<GUIPackage[]> { export async function getPackages(): Promise<GUIPackage[]> {
const [packages, installedPackages] = await Promise.all([ const [packages, installedPackages] = await Promise.all([
get<Package[]>('packages'), apiGet<Package[]>('packages'),
getInstalledPackages() getInstalledPackages()
]); ]);
@ -98,7 +47,7 @@ export async function getFeaturedPackages(): Promise<Package[]> {
export async function getPackageReviews(full_name: string): Promise<Review[]> { export async function getPackageReviews(full_name: string): Promise<Review[]> {
console.log(`getting reviews for ${full_name}`); console.log(`getting reviews for ${full_name}`);
const reviews: Review[] = await get<Review[]>( const reviews: Review[] = await apiGet<Review[]>(
`packages/${full_name.replaceAll('/', ':')}/reviews` `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[]> { 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 posts.map((post) => {
return { return {
title: post.title, title: post.title,
@ -164,36 +81,30 @@ export async function getTopPackages(): Promise<GUIPackage[]> {
export async function getAllPosts(tag?: string): Promise<AirtablePost[]> { export async function getAllPosts(tag?: string): Promise<AirtablePost[]> {
// add filter here someday: tag = news | course // 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; return posts;
} }
export async function getCategorizedPackages(): Promise<Category[]> { export async function getCategorizedPackages(): Promise<Category[]> {
const categories = await get<Category[]>('/packages/categorized'); const categories = await apiGet<Category[]>('/packages/categorized');
return categories; return categories;
} }
type DeviceAuth = {
status: AuthStatus;
user: Developer;
key: string;
};
export async function getDeviceAuth(deviceId: string): Promise<DeviceAuth> { 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; return data;
} }
export async function getPackageBottles(packageName: string): Promise<Bottle[]> { export async function getPackageBottles(packageName: string): Promise<Bottle[]> {
console.log('getting bottles for ', packageName); console.log('getting bottles for ', packageName);
const client = await getClient(); 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()); const { data } = await client.get<Bottle[]>(uri.toString());
console.log('got bottles', data); console.log('got bottles', data);
return data; return data;
} }
export async function registerDevice(): Promise<string> { export async function registerDevice(): Promise<string> {
const { deviceId } = await get<{ deviceId: string }>('/auth/registerDevice'); const { deviceId } = await apiGet<{ deviceId: string }>('/auth/registerDevice');
return deviceId; 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 initAuthStore from './stores/auth';
import initNavStore from './stores/nav'; import initNavStore from './stores/nav';
export const backLink = writable<string>('/');
export const featuredPackages = writable<Package[]>([]); export const featuredPackages = writable<Package[]>([]);
function initPackagesStore() { function initPackagesStore() {

View file

@ -3,7 +3,7 @@
// please use the package @tea/ui/src/types.ts // please use the package @tea/ui/src/types.ts
// things that go there are shared types/shapes like ie: Package // 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 { export enum PackageStates {
AVAILABLE, AVAILABLE,
@ -36,3 +36,9 @@ export enum AuthStatus {
SUCCESS = 'SUCCESS', SUCCESS = 'SUCCESS',
FAILED = 'FAILED' 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 '@tauri-apps/cli': ^1.2.2
'@tea/ui': workspace:* '@tea/ui': workspace:*
'@types/bcryptjs': ^2.4.2 '@types/bcryptjs': ^2.4.2
'@types/url-join': ^4.0.1
'@typescript-eslint/eslint-plugin': ^5.27.0 '@typescript-eslint/eslint-plugin': ^5.27.0
'@typescript-eslint/parser': ^5.27.0 '@typescript-eslint/parser': ^5.27.0
autoprefixer: ^10.4.13 autoprefixer: ^10.4.13
@ -38,16 +39,19 @@ importers:
tailwindcss: ^3.2.4 tailwindcss: ^3.2.4
tslib: ^2.3.1 tslib: ^2.3.1
typescript: ^4.7.4 typescript: ^4.7.4
url-join: ^5.0.0
vite: ^4.0.0 vite: ^4.0.0
dependencies: dependencies:
'@tauri-apps/api': 1.2.0 '@tauri-apps/api': 1.2.0
'@types/bcryptjs': 2.4.2 '@types/bcryptjs': 2.4.2
'@types/url-join': 4.0.1
bcryptjs: 2.4.3 bcryptjs: 2.4.3
buffer: 6.0.3 buffer: 6.0.3
fuse.js: 6.6.2 fuse.js: 6.6.2
lodash: 4.17.21 lodash: 4.17.21
lorem-ipsum: 2.0.8 lorem-ipsum: 2.0.8
svelte-watch-resize: 1.0.3 svelte-watch-resize: 1.0.3
url-join: 5.0.0
devDependencies: devDependencies:
'@playwright/test': 1.25.0 '@playwright/test': 1.25.0
'@sveltejs/adapter-auto': 1.0.0_@sveltejs+kit@1.0.1 '@sveltejs/adapter-auto': 1.0.0_@sveltejs+kit@1.0.1
@ -3896,6 +3900,10 @@ packages:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: true dev: true
/@types/url-join/4.0.1:
resolution: {integrity: sha512-wDXw9LEEUHyV+7UWy7U315nrJGJ7p1BzaCxDpEoLr789Dk1WDVMMlf3iBfbG2F8NdWnYyFbtTxUn2ZNbm1Q4LQ==}
dev: false
/@types/webpack-env/1.18.0: /@types/webpack-env/1.18.0:
resolution: {integrity: sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==} resolution: {integrity: sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==}
dev: true dev: true
@ -10345,6 +10353,11 @@ packages:
deprecated: Please see https://github.com/lydell/urix#deprecated deprecated: Please see https://github.com/lydell/urix#deprecated
dev: true 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: /use/3.1.1:
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}