From faddda83f04b46bcdaab5c225cab696fc7a820cd Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Fri, 10 Jun 2022 11:55:09 -0500 Subject: [PATCH] (WIP) Add initial API client --- .eslintrc.js | 2 +- package.json | 4 +- scripts/testing-server.mjs | 16 ++++++ src/api/index.ts | 97 +++++++++++++++++++++++++++++++++++++ src/screens/Login.tsx | 26 ++++++++-- src/screens/Signup.tsx | 50 +++++++++++-------- src/state/env.ts | 3 ++ src/state/models/session.ts | 58 +++++++++++++++++++++- yarn.lock | 31 ++++++++++++ 9 files changed, 260 insertions(+), 27 deletions(-) create mode 100644 scripts/testing-server.mjs create mode 100644 src/api/index.ts diff --git a/.eslintrc.js b/.eslintrc.js index 898ffe6d..ba805bc3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,7 +5,7 @@ module.exports = { // plugins: ['@typescript-eslint'], overrides: [ { - files: ['*.ts', '*.tsx'], + files: ['*.js', '*.mjs', '*.ts', '*.tsx'], rules: { '@typescript-eslint/no-shadow': 'off', 'no-shadow': 'off', diff --git a/package.json b/package.json index 763f6d89..cfa1f892 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "ios": "react-native run-ios", "web": "react-scripts start", "start": "react-native start", + "dev-backend": "node ./scripts/testing-server.mjs", "test": "jest", "lint": "eslint . --ext .js,.jsx,.ts,.tsx" }, @@ -24,7 +25,8 @@ "react-native": "0.68.2", "react-native-safe-area-context": "^4.3.1", "react-native-screens": "^3.13.1", - "react-native-web": "^0.17.7" + "react-native-web": "^0.17.7", + "ucans": "0.9.0-alpha3" }, "devDependencies": { "@babel/core": "^7.12.9", diff --git a/scripts/testing-server.mjs b/scripts/testing-server.mjs new file mode 100644 index 00000000..3517acdf --- /dev/null +++ b/scripts/testing-server.mjs @@ -0,0 +1,16 @@ +import {IpldStore} from '@adx/common' +import server from '@adx/server/dist/server.js' +import Database from '@adx/server/dist/db/index.js' + +const PORT = 1986 + +async function start() { + console.log('Initializing...') + + const db = Database.memory() + const serverBlockstore = IpldStore.createInMemory() + await db.dropTables() + await db.createTables() + server(serverBlockstore, db, PORT) +} +start() diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 00000000..f83e6541 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,97 @@ +import {MicroblogDelegator, MicroblogReader, auth} from '@adx/common' +import * as ucan from 'ucans' + +export class API { + userCfg?: UserConfig + reader?: MicroblogReader + writer?: MicroblogDelegator + + setUserCfg(cfg: UserConfig) { + this.userCfg = cfg + this.createReader() + this.createWriter() + } + + private createReader() { + if (!this.userCfg?.serverUrl) { + this.reader = undefined + } else { + this.reader = new MicroblogReader( + this.userCfg.serverUrl, + this.userCfg.did, + ) + } + } + + private createWriter() { + if ( + this.userCfg?.serverUrl && + this.userCfg?.did && + this.userCfg?.keypair && + this.userCfg?.ucanStore + ) { + this.writer = new MicroblogDelegator( + this.userCfg.serverUrl, + this.userCfg.did, + this.userCfg.keypair, + this.userCfg.ucanStore, + ) + } else { + this.writer = undefined + } + } +} + +export interface SerializedUserConfig { + serverUrl?: string + secretKeyStr?: string + rootAuthToken?: string +} + +export class UserConfig { + serverUrl?: string + did?: string + keypair?: ucan.EdKeypair + rootAuthToken?: string + ucanStore?: ucan.Store + + get hasWriteCaps() { + return Boolean(this.did && this.keypair && this.ucanStore) + } + + static async createTest(serverUrl: string) { + const cfg = new UserConfig() + cfg.serverUrl = serverUrl + cfg.keypair = await ucan.EdKeypair.create() + cfg.did = cfg.keypair.did() + cfg.rootAuthToken = (await auth.claimFull(cfg.did, cfg.keypair)).encoded() + cfg.ucanStore = await ucan.Store.fromTokens([cfg.rootAuthToken]) + return cfg + } + + static async hydrate(state: SerializedUserConfig) { + const cfg = new UserConfig() + await cfg.hydrate(state) + return cfg + } + + async serialize(): Promise { + return { + serverUrl: this.serverUrl, + secretKeyStr: this.keypair + ? await this.keypair.export('base64') + : undefined, + rootAuthToken: this.rootAuthToken, + } + } + + async hydrate(state: SerializedUserConfig) { + this.serverUrl = state.serverUrl + if (state.secretKeyStr && state.rootAuthToken) { + this.keypair = ucan.EdKeypair.fromSecretKey(state.secretKeyStr) + this.did = this.keypair.did() + this.rootAuthToken = state.rootAuthToken + this.ucanStore = await ucan.Store.fromTokens([this.rootAuthToken]) + } + } +} diff --git a/src/screens/Login.tsx b/src/screens/Login.tsx index 0eea085d..8451eb3c 100644 --- a/src/screens/Login.tsx +++ b/src/screens/Login.tsx @@ -1,18 +1,34 @@ import React from 'react' -import {Text, Button, View} from 'react-native' +import {Text, Button, View, ActivityIndicator} from 'react-native' +import {observer} from 'mobx-react-lite' import {Shell} from '../platform/shell' import type {RootTabsScreenProps} from '../routes/types' import {useStores} from '../state' -export function Login({navigation}: RootTabsScreenProps<'Login'>) { +export const Login = observer(({navigation}: RootTabsScreenProps<'Login'>) => { const store = useStores() return ( Sign In -