diff --git a/package.json b/package.json index de09b821..f45a3e54 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "@adxp/auth": "*", "@adxp/common": "*", + "@adxp/mock-api": "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#dc669c19d46b6b98dd692f493276303667670502", "@react-native-async-storage/async-storage": "^1.17.6", "@react-navigation/bottom-tabs": "^6.3.1", "@react-navigation/native": "^6.0.10", @@ -32,6 +33,7 @@ "react-native-inappbrowser-reborn": "^3.6.3", "react-native-safe-area-context": "^4.3.1", "react-native-screens": "^3.13.1", + "react-native-url-polyfill": "^1.3.0", "react-native-web": "^0.17.7", "ucans": "0.9.1" }, diff --git a/scripts/testing-server.mjs b/scripts/testing-server.mjs index adc214cf..f3c68e53 100644 --- a/scripts/testing-server.mjs +++ b/scripts/testing-server.mjs @@ -2,8 +2,11 @@ import {IpldStore} from '@adxp/common' import PDSServer from '@adxp/server/dist/server.js' import PDSDatabase from '@adxp/server/dist/db/index.js' import WSRelayServer from '@adxp/ws-relay/dist/index.js' +import AuthLobbyServer from '@adxp/auth-lobby' const PDS_PORT = 2583 +const AUTH_LOBBY1_PORT = 3001 +const AUTH_LOBBY2_PORT = 3002 const WSR_PORT = 3005 async function start() { @@ -15,11 +18,19 @@ async function start() { await db.createTables() PDSServer(serverBlockstore, db, PDS_PORT) + init(AuthLobbyServer, AUTH_LOBBY1_PORT, 'Auth lobby') + if (process.argv.includes('--relay')) { - WSRelayServer(WSR_PORT) - console.log(`🔁 Relay server running on port ${WSR_PORT}`) + init(AuthLobbyServer, AUTH_LOBBY2_PORT, 'Auth lobby 2') + init(WSRelayServer, WSR_PORT, 'Relay server') } else { - console.log('Include --relay to start the WS Relay') + console.log('Include --relay to start the WS Relay and second auth lobby') } } start() + +function init(fn, port, name) { + const s = fn(port) + s.on('listening', () => console.log(`✔ ${name} running on port ${port}`)) + s.on('error', e => console.log(`${name} failed to start:`, e)) +} diff --git a/src/App.native.tsx b/src/App.native.tsx index 511a8401..1326b184 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -1,7 +1,8 @@ +import 'react-native-url-polyfill/auto' import React, {useState, useEffect} from 'react' import {whenWebCrypto} from './platform/polyfills.native' import {RootStore, setupState, RootStoreProvider} from './state' -import * as Routes from './routes' +import * as Routes from './view/routes' function App() { const [rootStore, setRootStore] = useState(undefined) diff --git a/src/App.web.tsx b/src/App.web.tsx index 2fadf993..34b6ac6c 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -1,6 +1,6 @@ import React, {useState, useEffect} from 'react' import {RootStore, setupState, RootStoreProvider} from './state' -import * as Routes from './routes' +import * as Routes from './view/routes' function App() { const [rootStore, setRootStore] = useState(undefined) diff --git a/src/api/index.ts b/src/api/index.ts deleted file mode 100644 index 6f0dc0b3..00000000 --- a/src/api/index.ts +++ /dev/null @@ -1,118 +0,0 @@ -// import {MicroblogDelegator, MicroblogReader, auth} from '@adx/common' -// import * as ucan from 'ucans' - -class MicroblogReader { - constructor(public url: string, public did: any) {} -} -class MicroblogDelegator { - constructor( - public url: string, - public did: any, - public keypair: any, - public ucanStore: any, - ) {} -} -const auth = { - async claimFull(_one: any, _two: any) { - return { - encoded() { - return 'todo' - }, - } - }, -} - -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?: any //ucan.EdKeypair - rootAuthToken?: string - ucanStore?: any //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 = true //await ucan.EdKeypair.create() - cfg.did = cfg.keypair.did() - cfg.rootAuthToken = (await auth.claimFull(cfg.did, cfg.keypair)).encoded() - cfg.ucanStore = true // 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 = true // ucan.EdKeypair.fromSecretKey(state.secretKeyStr) - this.did = this.keypair.did() - this.rootAuthToken = state.rootAuthToken - this.ucanStore = true // await ucan.Store.fromTokens([this.rootAuthToken]) - } - } -} diff --git a/src/platform/auth-flow.native.ts b/src/platform/auth-flow.native.ts index 596632f1..3c9bd09e 100644 --- a/src/platform/auth-flow.native.ts +++ b/src/platform/auth-flow.native.ts @@ -4,7 +4,7 @@ import * as ucan from 'ucans' import {InAppBrowser} from 'react-native-inappbrowser-reborn' import {isWeb} from '../platform/detection' import {extractHashFragment, makeAppUrl} from '../platform/urls' -import {ReactNativeStore, parseUrlForUcan} from '../state/auth' +import {ReactNativeStore, parseUrlForUcan} from '../state/lib/auth' import * as env from '../env' export async function requestAppUcan( diff --git a/src/platform/auth-flow.ts b/src/platform/auth-flow.ts index b96fc58e..fbc85a37 100644 --- a/src/platform/auth-flow.ts +++ b/src/platform/auth-flow.ts @@ -1,7 +1,7 @@ import * as auth from '@adxp/auth' import * as ucan from 'ucans' import {makeAppUrl} from '../platform/urls' -import {ReactNativeStore} from '../state/auth' +import {ReactNativeStore} from '../state/lib/auth' import * as env from '../env' export async function requestAppUcan( diff --git a/src/state/env.ts b/src/state/env.ts index c1e11ebd..0ee59788 100644 --- a/src/state/env.ts +++ b/src/state/env.ts @@ -4,22 +4,35 @@ */ import {getEnv, IStateTreeNode} from 'mobx-state-tree' -import {ReactNativeStore} from './auth' -import {API} from '../api' +// import {ReactNativeStore} from './auth' +import {AdxClient, blueskywebSchemas, AdxRepoClient} from '@adxp/mock-api' +import * as storage from './lib/storage' + +export const adx = new AdxClient({ + pds: 'http://localhost', + schemas: blueskywebSchemas, +}) export class Environment { - api = new API() - authStore?: ReactNativeStore + adx = adx + // authStore?: ReactNativeStore constructor() {} async setup() { - this.authStore = await ReactNativeStore.load() + await adx.setupMock( + () => storage.load('mock-root'), + async root => { + await storage.save('mock-root', root) + }, + generateMockData, + ) + // this.authStore = await ReactNativeStore.load() } } /** - * Extension to the MST models that adds the environment property. + * Extension to the MST models that adds the env property. * Usage: * * .extend(withEnvironment) @@ -27,8 +40,204 @@ export class Environment { */ export const withEnvironment = (self: IStateTreeNode) => ({ views: { - get environment() { + get env() { return getEnv(self) }, }, }) + +// TEMPORARY +// mock api config +// ======= + +function* dateGen() { + let start = 1657846031914 + while (true) { + yield new Date(start).toISOString() + start += 1e3 + } +} +const date = dateGen() + +function repo(didOrName: string) { + const userDb = adx.mockDb.getUser(didOrName) + if (!userDb) throw new Error(`User not found: ${didOrName}`) + return adx.mainPds.repo(userDb.did, userDb.writable) +} + +export async function generateMockData() { + await adx.mockDb.addUser({name: 'alice.com', writable: true}) + await adx.mockDb.addUser({name: 'bob.com', writable: true}) + await adx.mockDb.addUser({name: 'carla.com', writable: true}) + + const alice = repo('alice.com') + const bob = repo('bob.com') + const carla = repo('carla.com') + + await alice.collection('blueskyweb.xyz:Profiles').put('Profile', 'profile', { + $type: 'blueskyweb.xyz:Profile', + displayName: 'Alice', + description: 'Test user 1', + }) + await bob.collection('blueskyweb.xyz:Profiles').put('Profile', 'profile', { + $type: 'blueskyweb.xyz:Profile', + displayName: 'Bob', + description: 'Test user 2', + }) + await carla.collection('blueskyweb.xyz:Profiles').put('Profile', 'profile', { + $type: 'blueskyweb.xyz:Profile', + displayName: 'Carla', + description: 'Test user 3', + }) + + // everybody follows everybody + const follow = async (who: AdxRepoClient, subjectName: string) => { + const subjectDb = adx.mockDb.getUser(subjectName) + return who.collection('blueskyweb.xyz:Follows').create('Follow', { + $type: 'blueskyweb.xyz:Follow', + subject: { + did: subjectDb?.did, + name: subjectDb?.name, + }, + createdAt: date.next().value, + }) + } + await follow(alice, 'bob.com') + await follow(alice, 'carla.com') + await follow(bob, 'alice.com') + await follow(bob, 'carla.com') + await follow(carla, 'alice.com') + await follow(carla, 'bob.com') + + // 2 posts on each user + const alicePosts: {uri: string}[] = [] + for (let i = 0; i < 2; i++) { + alicePosts.push( + await alice.collection('blueskyweb.xyz:Posts').create('Post', { + $type: 'blueskyweb.xyz:Post', + text: `Alice post ${i + 1}`, + createdAt: date.next().value, + }), + ) + await bob.collection('blueskyweb.xyz:Posts').create('Post', { + $type: 'blueskyweb.xyz:Post', + text: `Bob post ${i + 1}`, + createdAt: date.next().value, + }) + await carla.collection('blueskyweb.xyz:Posts').create('Post', { + $type: 'blueskyweb.xyz:Post', + text: `Carla post ${i + 1}`, + createdAt: date.next().value, + }) + } + + // small thread of replies on alice's first post + const bobReply1 = await bob + .collection('blueskyweb.xyz:Posts') + .create('Post', { + $type: 'blueskyweb.xyz:Post', + text: 'Bob reply', + reply: {root: alicePosts[0].uri, parent: alicePosts[0].uri}, + createdAt: date.next().value, + }) + const carlaReply1 = await carla + .collection('blueskyweb.xyz:Posts') + .create('Post', { + $type: 'blueskyweb.xyz:Post', + text: 'Carla reply', + reply: {root: alicePosts[0].uri, parent: alicePosts[0].uri}, + createdAt: date.next().value, + }) + const aliceReply1 = await alice + .collection('blueskyweb.xyz:Posts') + .create('Post', { + $type: 'blueskyweb.xyz:Post', + text: 'Alice reply', + reply: {root: alicePosts[0].uri, parent: bobReply1.uri}, + createdAt: date.next().value, + }) + + // bob and carla repost alice's first post + await bob.collection('blueskyweb.xyz:Posts').create('Repost', { + $type: 'blueskyweb.xyz:Repost', + subject: alicePosts[0].uri, + createdAt: date.next().value, + }) + await carla.collection('blueskyweb.xyz:Posts').create('Repost', { + $type: 'blueskyweb.xyz:Repost', + subject: alicePosts[0].uri, + createdAt: date.next().value, + }) + + // bob likes all of alice's posts + for (let i = 0; i < 2; i++) { + await bob.collection('blueskyweb.xyz:Likes').create('Like', { + $type: 'blueskyweb.xyz:Like', + subject: alicePosts[i].uri, + createdAt: date.next().value, + }) + } + + // carla likes all of alice's posts and everybody's replies + for (let i = 0; i < 2; i++) { + await carla.collection('blueskyweb.xyz:Likes').create('Like', { + $type: 'blueskyweb.xyz:Like', + subject: alicePosts[i].uri, + createdAt: date.next().value, + }) + } + await carla.collection('blueskyweb.xyz:Likes').create('Like', { + $type: 'blueskyweb.xyz:Like', + subject: aliceReply1.uri, + createdAt: date.next().value, + }) + await carla.collection('blueskyweb.xyz:Likes').create('Like', { + $type: 'blueskyweb.xyz:Like', + subject: bobReply1.uri, + createdAt: date.next().value, + }) + + // give alice 3 badges, 2 from bob and 2 from carla, with one ignored + const inviteBadge = await bob + .collection('blueskyweb.xyz:Badges') + .create('Badge', { + $type: 'blueskyweb.xyz:Badge', + subject: {did: alice.did, name: 'alice.com'}, + assertion: {type: 'invite'}, + createdAt: date.next().value, + }) + const techTagBadge1 = await bob + .collection('blueskyweb.xyz:Badges') + .create('Badge', { + $type: 'blueskyweb.xyz:Badge', + subject: {did: alice.did, name: 'alice.com'}, + assertion: {type: 'tag', tag: 'tech'}, + createdAt: date.next().value, + }) + const techTagBadge2 = await carla + .collection('blueskyweb.xyz:Badges') + .create('Badge', { + $type: 'blueskyweb.xyz:Badge', + subject: {did: alice.did, name: 'alice.com'}, + assertion: {type: 'tag', tag: 'tech'}, + createdAt: date.next().value, + }) + const employeeBadge = await bob + .collection('blueskyweb.xyz:Badges') + .create('Badge', { + $type: 'blueskyweb.xyz:Badge', + subject: {did: alice.did, name: 'alice.com'}, + assertion: {type: 'employee'}, + createdAt: date.next().value, + }) + await alice.collection('blueskyweb.xyz:Profiles').put('Profile', 'profile', { + $type: 'blueskyweb.xyz:Profile', + displayName: 'Alice', + description: 'Test user 1', + badges: [ + {uri: inviteBadge.uri}, + {uri: techTagBadge1.uri}, + {uri: techTagBadge2.uri}, + ], + }) +} diff --git a/src/state/index.ts b/src/state/index.ts index 6040f8f9..24c3b943 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -5,8 +5,8 @@ import { createDefaultRootStore, } from './models/root-store' import {Environment} from './env' -import * as storage from './storage' -import * as auth from './auth' +import * as storage from './lib/storage' +// import * as auth from './auth' TODO const ROOT_STATE_STORAGE_KEY = 'root' @@ -29,15 +29,19 @@ export async function setupState() { storage.save(ROOT_STATE_STORAGE_KEY, snapshot), ) - if (env.authStore) { - const isAuthed = await auth.isAuthed(env.authStore) - rootStore.session.setAuthed(isAuthed) + // TODO + rootStore.session.setAuthed(true) + // if (env.authStore) { + // const isAuthed = await auth.isAuthed(env.authStore) + // rootStore.session.setAuthed(isAuthed) - // handle redirect from auth - if (await auth.initialLoadUcanCheck(env.authStore)) { - rootStore.session.setAuthed(true) - } - } + // // handle redirect from auth + // if (await auth.initialLoadUcanCheck(env.authStore)) { + // rootStore.session.setAuthed(true) + // } + // } + await rootStore.me.load() + console.log(rootStore.me) return rootStore } diff --git a/src/state/auth.ts b/src/state/lib/auth.ts similarity index 95% rename from src/state/auth.ts rename to src/state/lib/auth.ts index a8483b92..d758745e 100644 --- a/src/state/auth.ts +++ b/src/state/lib/auth.ts @@ -1,7 +1,11 @@ import * as auth from '@adxp/auth' import * as ucan from 'ucans' -import {getInitialURL, extractHashFragment, clearHash} from '../platform/urls' -import * as authFlow from '../platform/auth-flow' +import { + getInitialURL, + extractHashFragment, + clearHash, +} from '../../platform/urls' +import * as authFlow from '../../platform/auth-flow' import * as storage from './storage' const SCOPE = auth.writeCap( diff --git a/src/state/storage.ts b/src/state/lib/storage.ts similarity index 100% rename from src/state/storage.ts rename to src/state/lib/storage.ts diff --git a/src/state/models/me.ts b/src/state/models/me.ts new file mode 100644 index 00000000..bc4b1314 --- /dev/null +++ b/src/state/models/me.ts @@ -0,0 +1,48 @@ +import {Instance, SnapshotOut, types, flow, getRoot} from 'mobx-state-tree' +import {RootStore} from './root-store' +import {withEnvironment} from '../env' + +export const MeModel = types + .model('Me') + .props({ + did: types.maybe(types.string), + name: types.maybe(types.string), + displayName: types.maybe(types.string), + description: types.maybe(types.string), + }) + .extend(withEnvironment) + .actions(self => ({ + load: flow(function* () { + const sess = (getRoot(self) as RootStore).session + if (sess.isAuthed) { + // TODO temporary + const userDb = self.env.adx.mockDb.mainUser + self.did = userDb.did + self.name = userDb.name + const profile = yield self.env.adx + .repo(self.did, true) + .collection('blueskyweb.xyz:Profiles') + .get('Profile', 'profile') + .catch(_ => undefined) + if (profile?.valid) { + self.displayName = profile.value.displayName + self.description = profile.value.description + } else { + self.displayName = '' + self.description = '' + } + } else { + self.did = undefined + self.name = undefined + self.displayName = undefined + self.description = undefined + } + }), + })) + +export interface Me extends Instance {} +export interface MeSnapshot extends SnapshotOut {} + +export function createDefaultMe() { + return {} +} diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index 143c59ea..b38b36e8 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -5,9 +5,11 @@ import {Instance, SnapshotOut, types} from 'mobx-state-tree' import {createContext, useContext} from 'react' import {SessionModel, createDefaultSession} from './session' +import {MeModel, createDefaultMe} from './me' export const RootStoreModel = types.model('RootStore').props({ session: SessionModel, + me: MeModel, }) export interface RootStore extends Instance {} @@ -16,6 +18,7 @@ export interface RootStoreSnapshot extends SnapshotOut {} export function createDefaultRootStore() { return { session: createDefaultSession(), + me: createDefaultMe(), } } diff --git a/src/state/models/session.ts b/src/state/models/session.ts index c032d759..3b52b8fc 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -1,6 +1,6 @@ import {Instance, SnapshotOut, types, flow} from 'mobx-state-tree' // import {UserConfig} from '../../api' -import * as auth from '../auth' +import * as auth from '../lib/auth' import {withEnvironment} from '../env' export const SessionModel = types @@ -24,10 +24,10 @@ export const SessionModel = types self.uiIsProcessing = true self.uiError = undefined try { - if (!self.environment.authStore) { + if (!self.env.authStore) { throw new Error('Auth store not initialized') } - const res = yield auth.requestAppUcan(self.environment.authStore) + const res = yield auth.requestAppUcan(self.env.authStore) self.isAuthed = res self.uiIsProcessing = false return res @@ -42,10 +42,10 @@ export const SessionModel = types self.uiIsProcessing = true self.uiError = undefined try { - if (!self.environment.authStore) { + if (!self.env.authStore) { throw new Error('Auth store not initialized') } - const res = yield auth.logout(self.environment.authStore) + const res = yield auth.logout(self.env.authStore) self.isAuthed = false self.uiIsProcessing = false return res @@ -65,7 +65,7 @@ export const SessionModel = types // secretKeyStr: self.secretKeyStr, // rootAuthToken: self.rootAuthToken, // }) - // self.environment.api.setUserCfg(cfg) + // self.env.api.setUserCfg(cfg) self.isAuthed = true self.uiIsProcessing = false return true @@ -86,7 +86,7 @@ export const SessionModel = types // self.secretKeyStr = state.secretKeyStr // self.rootAuthToken = state.rootAuthToken self.isAuthed = true - // self.environment.api.setUserCfg(cfg) + // self.env.api.setUserCfg(cfg) } catch (e: any) { console.error('Failed to create test account', e) self.uiError = e.toString() diff --git a/src/routes/index.tsx b/src/view/routes/index.tsx similarity index 97% rename from src/routes/index.tsx rename to src/view/routes/index.tsx index 32398e9a..6351dea6 100644 --- a/src/routes/index.tsx +++ b/src/view/routes/index.tsx @@ -10,8 +10,8 @@ import {createNativeStackNavigator} from '@react-navigation/native-stack' import {createBottomTabNavigator} from '@react-navigation/bottom-tabs' import {observer} from 'mobx-react-lite' import type {RootTabsParamList} from './types' -import {useStores} from '../state' -import * as platform from '../platform/detection' +import {useStores} from '../../state' +import * as platform from '../../platform/detection' import {Home} from '../screens/Home' import {Search} from '../screens/Search' import {Notifications} from '../screens/Notifications' diff --git a/src/routes/types.ts b/src/view/routes/types.ts similarity index 100% rename from src/routes/types.ts rename to src/view/routes/types.ts diff --git a/src/screens/Home.tsx b/src/view/screens/Home.tsx similarity index 89% rename from src/screens/Home.tsx rename to src/view/screens/Home.tsx index ed95121e..5210d9d4 100644 --- a/src/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -1,8 +1,8 @@ import React from 'react' import {Text, Button, View} from 'react-native' -import {Shell} from '../platform/shell' +import {Shell} from '../shell' import type {RootTabsScreenProps} from '../routes/types' -import {useStores} from '../state' +import {useStores} from '../../state' export function Home({navigation}: RootTabsScreenProps<'Home'>) { const store = useStores() diff --git a/src/screens/Login.tsx b/src/view/screens/Login.tsx similarity index 92% rename from src/screens/Login.tsx rename to src/view/screens/Login.tsx index 36280e87..20755736 100644 --- a/src/screens/Login.tsx +++ b/src/view/screens/Login.tsx @@ -1,9 +1,9 @@ import React from 'react' import {Text, Button, View, ActivityIndicator} from 'react-native' import {observer} from 'mobx-react-lite' -import {Shell} from '../platform/shell' +import {Shell} from '../shell' import type {RootTabsScreenProps} from '../routes/types' -import {useStores} from '../state' +import {useStores} from '../../state' export const Login = observer(({navigation}: RootTabsScreenProps<'Login'>) => { const store = useStores() diff --git a/src/screens/Menu.tsx b/src/view/screens/Menu.tsx similarity index 92% rename from src/screens/Menu.tsx rename to src/view/screens/Menu.tsx index 9cdda4f2..8cf93676 100644 --- a/src/screens/Menu.tsx +++ b/src/view/screens/Menu.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {Shell} from '../platform/shell' +import {Shell} from '../shell' import {ScrollView, Text, View} from 'react-native' import type {RootTabsScreenProps} from '../routes/types' diff --git a/src/screens/NotFound.tsx b/src/view/screens/NotFound.tsx similarity index 92% rename from src/screens/NotFound.tsx rename to src/view/screens/NotFound.tsx index f4d9d510..3f6dd7aa 100644 --- a/src/screens/NotFound.tsx +++ b/src/view/screens/NotFound.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {Shell} from '../platform/shell' +import {Shell} from '../shell' import {Text, Button, View} from 'react-native' import type {RootTabsScreenProps} from '../routes/types' diff --git a/src/screens/Notifications.tsx b/src/view/screens/Notifications.tsx similarity index 91% rename from src/screens/Notifications.tsx rename to src/view/screens/Notifications.tsx index 292f4593..5bade68f 100644 --- a/src/screens/Notifications.tsx +++ b/src/view/screens/Notifications.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {Shell} from '../platform/shell' +import {Shell} from '../shell' import {Text, View} from 'react-native' import type {RootTabsScreenProps} from '../routes/types' diff --git a/src/screens/Profile.tsx b/src/view/screens/Profile.tsx similarity index 91% rename from src/screens/Profile.tsx rename to src/view/screens/Profile.tsx index 76915b48..2c93f4bf 100644 --- a/src/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {Shell} from '../platform/shell' +import {Shell} from '../shell' import {View, Text} from 'react-native' import type {RootTabsScreenProps} from '../routes/types' diff --git a/src/screens/Search.tsx b/src/view/screens/Search.tsx similarity index 90% rename from src/screens/Search.tsx rename to src/view/screens/Search.tsx index d456cd19..2f111cf7 100644 --- a/src/screens/Search.tsx +++ b/src/view/screens/Search.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {Shell} from '../platform/shell' +import {Shell} from '../shell' import {Text, View} from 'react-native' import type {RootTabsScreenProps} from '../routes/types' diff --git a/src/screens/Signup.tsx b/src/view/screens/Signup.tsx similarity index 93% rename from src/screens/Signup.tsx rename to src/view/screens/Signup.tsx index e09ab5dd..8ca47e3e 100644 --- a/src/screens/Signup.tsx +++ b/src/view/screens/Signup.tsx @@ -1,9 +1,9 @@ import React from 'react' import {Text, Button, View, ActivityIndicator} from 'react-native' import {observer} from 'mobx-react-lite' -import {Shell} from '../platform/shell' +import {Shell} from '../shell' import type {RootTabsScreenProps} from '../routes/types' -import {useStores} from '../state' +import {useStores} from '../../state' export const Signup = observer( ({navigation}: RootTabsScreenProps<'Signup'>) => { diff --git a/src/platform/desktop-web/left-column.tsx b/src/view/shell/desktop-web/left-column.tsx similarity index 100% rename from src/platform/desktop-web/left-column.tsx rename to src/view/shell/desktop-web/left-column.tsx diff --git a/src/platform/desktop-web/right-column.tsx b/src/view/shell/desktop-web/right-column.tsx similarity index 100% rename from src/platform/desktop-web/right-column.tsx rename to src/view/shell/desktop-web/right-column.tsx diff --git a/src/platform/desktop-web/shell.tsx b/src/view/shell/desktop-web/shell.tsx similarity index 95% rename from src/platform/desktop-web/shell.tsx rename to src/view/shell/desktop-web/shell.tsx index ef880306..13acbbfe 100644 --- a/src/platform/desktop-web/shell.tsx +++ b/src/view/shell/desktop-web/shell.tsx @@ -3,7 +3,7 @@ import {observer} from 'mobx-react-lite' import {View, StyleSheet} from 'react-native' import {DesktopLeftColumn} from './left-column' import {DesktopRightColumn} from './right-column' -import {useStores} from '../../state' +import {useStores} from '../../../state' export const DesktopWebShell: React.FC = observer(({children}) => { const store = useStores() diff --git a/src/platform/shell.tsx b/src/view/shell/index.tsx similarity index 84% rename from src/platform/shell.tsx rename to src/view/shell/index.tsx index ec8d51e1..db60ed14 100644 --- a/src/platform/shell.tsx +++ b/src/view/shell/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import {SafeAreaView} from 'react-native' -import {isDesktopWeb} from './detection' +import {isDesktopWeb} from '../../platform/detection' import {DesktopWebShell} from './desktop-web/shell' export const Shell: React.FC = ({children}) => { diff --git a/yarn.lock b/yarn.lock index 4de0d188..1f989b00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,6 +55,14 @@ ucans "0.9.0-alpha3" uint8arrays "^3.0.0" +"@adxp/mock-api@git+ssh://git@github.com:bluesky-social/adx-mock-api.git#dc669c19d46b6b98dd692f493276303667670502": + version "0.0.1" + resolved "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#dc669c19d46b6b98dd692f493276303667670502" + dependencies: + ajv "^8.11.0" + ajv-formats "^2.1.1" + zod "^3.14.2" + "@adxp/server@*": version "0.0.2" resolved "https://registry.yarnpkg.com/@adxp/server/-/server-0.0.2.tgz#706d248a5e481a4582657c12d919acb4d96106fb" @@ -3221,7 +3229,7 @@ ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.0.1, ajv@^8.6.0, ajv@^8.8.0: +ajv@^8.0.0, ajv@^8.0.1, ajv@^8.11.0, ajv@^8.6.0, ajv@^8.8.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== @@ -4028,7 +4036,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^5.5.0: +buffer@^5.4.3, buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -11059,6 +11067,13 @@ react-native-screens@^3.13.1: react-freeze "^1.0.0" warn-once "^0.1.0" +react-native-url-polyfill@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz#c1763de0f2a8c22cc3e959b654c8790622b6ef6a" + integrity sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ== + dependencies: + whatwg-url-without-unicode "8.0.0-3" + react-native-web@^0.17.7: version "0.17.7" resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.17.7.tgz#038899dbc94467a0ca0be214b88a30e0c117b176" @@ -13249,6 +13264,15 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url-without-unicode@8.0.0-3: + version "8.0.0-3" + resolved "https://registry.yarnpkg.com/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz#ab6df4bf6caaa6c85a59f6e82c026151d4bb376b" + integrity sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig== + dependencies: + buffer "^5.4.3" + punycode "^2.1.1" + webidl-conversions "^5.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"