Add mock API and reorg code for clarity
parent
de87ec17d1
commit
1d00f3b984
|
@ -15,6 +15,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adxp/auth": "*",
|
"@adxp/auth": "*",
|
||||||
"@adxp/common": "*",
|
"@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-native-async-storage/async-storage": "^1.17.6",
|
||||||
"@react-navigation/bottom-tabs": "^6.3.1",
|
"@react-navigation/bottom-tabs": "^6.3.1",
|
||||||
"@react-navigation/native": "^6.0.10",
|
"@react-navigation/native": "^6.0.10",
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
"react-native-inappbrowser-reborn": "^3.6.3",
|
"react-native-inappbrowser-reborn": "^3.6.3",
|
||||||
"react-native-safe-area-context": "^4.3.1",
|
"react-native-safe-area-context": "^4.3.1",
|
||||||
"react-native-screens": "^3.13.1",
|
"react-native-screens": "^3.13.1",
|
||||||
|
"react-native-url-polyfill": "^1.3.0",
|
||||||
"react-native-web": "^0.17.7",
|
"react-native-web": "^0.17.7",
|
||||||
"ucans": "0.9.1"
|
"ucans": "0.9.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,8 +2,11 @@ import {IpldStore} from '@adxp/common'
|
||||||
import PDSServer from '@adxp/server/dist/server.js'
|
import PDSServer from '@adxp/server/dist/server.js'
|
||||||
import PDSDatabase from '@adxp/server/dist/db/index.js'
|
import PDSDatabase from '@adxp/server/dist/db/index.js'
|
||||||
import WSRelayServer from '@adxp/ws-relay/dist/index.js'
|
import WSRelayServer from '@adxp/ws-relay/dist/index.js'
|
||||||
|
import AuthLobbyServer from '@adxp/auth-lobby'
|
||||||
|
|
||||||
const PDS_PORT = 2583
|
const PDS_PORT = 2583
|
||||||
|
const AUTH_LOBBY1_PORT = 3001
|
||||||
|
const AUTH_LOBBY2_PORT = 3002
|
||||||
const WSR_PORT = 3005
|
const WSR_PORT = 3005
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
|
@ -15,11 +18,19 @@ async function start() {
|
||||||
await db.createTables()
|
await db.createTables()
|
||||||
PDSServer(serverBlockstore, db, PDS_PORT)
|
PDSServer(serverBlockstore, db, PDS_PORT)
|
||||||
|
|
||||||
|
init(AuthLobbyServer, AUTH_LOBBY1_PORT, 'Auth lobby')
|
||||||
|
|
||||||
if (process.argv.includes('--relay')) {
|
if (process.argv.includes('--relay')) {
|
||||||
WSRelayServer(WSR_PORT)
|
init(AuthLobbyServer, AUTH_LOBBY2_PORT, 'Auth lobby 2')
|
||||||
console.log(`🔁 Relay server running on port ${WSR_PORT}`)
|
init(WSRelayServer, WSR_PORT, 'Relay server')
|
||||||
} else {
|
} else {
|
||||||
console.log('Include --relay to start the WS Relay')
|
console.log('Include --relay to start the WS Relay and second auth lobby')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
start()
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import 'react-native-url-polyfill/auto'
|
||||||
import React, {useState, useEffect} from 'react'
|
import React, {useState, useEffect} from 'react'
|
||||||
import {whenWebCrypto} from './platform/polyfills.native'
|
import {whenWebCrypto} from './platform/polyfills.native'
|
||||||
import {RootStore, setupState, RootStoreProvider} from './state'
|
import {RootStore, setupState, RootStoreProvider} from './state'
|
||||||
import * as Routes from './routes'
|
import * as Routes from './view/routes'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)
|
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, {useState, useEffect} from 'react'
|
import React, {useState, useEffect} from 'react'
|
||||||
import {RootStore, setupState, RootStoreProvider} from './state'
|
import {RootStore, setupState, RootStoreProvider} from './state'
|
||||||
import * as Routes from './routes'
|
import * as Routes from './view/routes'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)
|
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)
|
||||||
|
|
118
src/api/index.ts
118
src/api/index.ts
|
@ -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<SerializedUserConfig> {
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ import * as ucan from 'ucans'
|
||||||
import {InAppBrowser} from 'react-native-inappbrowser-reborn'
|
import {InAppBrowser} from 'react-native-inappbrowser-reborn'
|
||||||
import {isWeb} from '../platform/detection'
|
import {isWeb} from '../platform/detection'
|
||||||
import {extractHashFragment, makeAppUrl} from '../platform/urls'
|
import {extractHashFragment, makeAppUrl} from '../platform/urls'
|
||||||
import {ReactNativeStore, parseUrlForUcan} from '../state/auth'
|
import {ReactNativeStore, parseUrlForUcan} from '../state/lib/auth'
|
||||||
import * as env from '../env'
|
import * as env from '../env'
|
||||||
|
|
||||||
export async function requestAppUcan(
|
export async function requestAppUcan(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as auth from '@adxp/auth'
|
import * as auth from '@adxp/auth'
|
||||||
import * as ucan from 'ucans'
|
import * as ucan from 'ucans'
|
||||||
import {makeAppUrl} from '../platform/urls'
|
import {makeAppUrl} from '../platform/urls'
|
||||||
import {ReactNativeStore} from '../state/auth'
|
import {ReactNativeStore} from '../state/lib/auth'
|
||||||
import * as env from '../env'
|
import * as env from '../env'
|
||||||
|
|
||||||
export async function requestAppUcan(
|
export async function requestAppUcan(
|
||||||
|
|
223
src/state/env.ts
223
src/state/env.ts
|
@ -4,22 +4,35 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getEnv, IStateTreeNode} from 'mobx-state-tree'
|
import {getEnv, IStateTreeNode} from 'mobx-state-tree'
|
||||||
import {ReactNativeStore} from './auth'
|
// import {ReactNativeStore} from './auth'
|
||||||
import {API} from '../api'
|
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 {
|
export class Environment {
|
||||||
api = new API()
|
adx = adx
|
||||||
authStore?: ReactNativeStore
|
// authStore?: ReactNativeStore
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
async setup() {
|
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:
|
* Usage:
|
||||||
*
|
*
|
||||||
* .extend(withEnvironment)
|
* .extend(withEnvironment)
|
||||||
|
@ -27,8 +40,204 @@ export class Environment {
|
||||||
*/
|
*/
|
||||||
export const withEnvironment = (self: IStateTreeNode) => ({
|
export const withEnvironment = (self: IStateTreeNode) => ({
|
||||||
views: {
|
views: {
|
||||||
get environment() {
|
get env() {
|
||||||
return getEnv<Environment>(self)
|
return getEnv<Environment>(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},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import {
|
||||||
createDefaultRootStore,
|
createDefaultRootStore,
|
||||||
} from './models/root-store'
|
} from './models/root-store'
|
||||||
import {Environment} from './env'
|
import {Environment} from './env'
|
||||||
import * as storage from './storage'
|
import * as storage from './lib/storage'
|
||||||
import * as auth from './auth'
|
// import * as auth from './auth' TODO
|
||||||
|
|
||||||
const ROOT_STATE_STORAGE_KEY = 'root'
|
const ROOT_STATE_STORAGE_KEY = 'root'
|
||||||
|
|
||||||
|
@ -29,15 +29,19 @@ export async function setupState() {
|
||||||
storage.save(ROOT_STATE_STORAGE_KEY, snapshot),
|
storage.save(ROOT_STATE_STORAGE_KEY, snapshot),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (env.authStore) {
|
// TODO
|
||||||
const isAuthed = await auth.isAuthed(env.authStore)
|
rootStore.session.setAuthed(true)
|
||||||
rootStore.session.setAuthed(isAuthed)
|
// if (env.authStore) {
|
||||||
|
// const isAuthed = await auth.isAuthed(env.authStore)
|
||||||
|
// rootStore.session.setAuthed(isAuthed)
|
||||||
|
|
||||||
// handle redirect from auth
|
// // handle redirect from auth
|
||||||
if (await auth.initialLoadUcanCheck(env.authStore)) {
|
// if (await auth.initialLoadUcanCheck(env.authStore)) {
|
||||||
rootStore.session.setAuthed(true)
|
// rootStore.session.setAuthed(true)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
await rootStore.me.load()
|
||||||
|
console.log(rootStore.me)
|
||||||
|
|
||||||
return rootStore
|
return rootStore
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import * as auth from '@adxp/auth'
|
import * as auth from '@adxp/auth'
|
||||||
import * as ucan from 'ucans'
|
import * as ucan from 'ucans'
|
||||||
import {getInitialURL, extractHashFragment, clearHash} from '../platform/urls'
|
import {
|
||||||
import * as authFlow from '../platform/auth-flow'
|
getInitialURL,
|
||||||
|
extractHashFragment,
|
||||||
|
clearHash,
|
||||||
|
} from '../../platform/urls'
|
||||||
|
import * as authFlow from '../../platform/auth-flow'
|
||||||
import * as storage from './storage'
|
import * as storage from './storage'
|
||||||
|
|
||||||
const SCOPE = auth.writeCap(
|
const SCOPE = auth.writeCap(
|
|
@ -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<typeof MeModel> {}
|
||||||
|
export interface MeSnapshot extends SnapshotOut<typeof MeModel> {}
|
||||||
|
|
||||||
|
export function createDefaultMe() {
|
||||||
|
return {}
|
||||||
|
}
|
|
@ -5,9 +5,11 @@
|
||||||
import {Instance, SnapshotOut, types} from 'mobx-state-tree'
|
import {Instance, SnapshotOut, types} from 'mobx-state-tree'
|
||||||
import {createContext, useContext} from 'react'
|
import {createContext, useContext} from 'react'
|
||||||
import {SessionModel, createDefaultSession} from './session'
|
import {SessionModel, createDefaultSession} from './session'
|
||||||
|
import {MeModel, createDefaultMe} from './me'
|
||||||
|
|
||||||
export const RootStoreModel = types.model('RootStore').props({
|
export const RootStoreModel = types.model('RootStore').props({
|
||||||
session: SessionModel,
|
session: SessionModel,
|
||||||
|
me: MeModel,
|
||||||
})
|
})
|
||||||
|
|
||||||
export interface RootStore extends Instance<typeof RootStoreModel> {}
|
export interface RootStore extends Instance<typeof RootStoreModel> {}
|
||||||
|
@ -16,6 +18,7 @@ export interface RootStoreSnapshot extends SnapshotOut<typeof RootStoreModel> {}
|
||||||
export function createDefaultRootStore() {
|
export function createDefaultRootStore() {
|
||||||
return {
|
return {
|
||||||
session: createDefaultSession(),
|
session: createDefaultSession(),
|
||||||
|
me: createDefaultMe(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {Instance, SnapshotOut, types, flow} from 'mobx-state-tree'
|
import {Instance, SnapshotOut, types, flow} from 'mobx-state-tree'
|
||||||
// import {UserConfig} from '../../api'
|
// import {UserConfig} from '../../api'
|
||||||
import * as auth from '../auth'
|
import * as auth from '../lib/auth'
|
||||||
import {withEnvironment} from '../env'
|
import {withEnvironment} from '../env'
|
||||||
|
|
||||||
export const SessionModel = types
|
export const SessionModel = types
|
||||||
|
@ -24,10 +24,10 @@ export const SessionModel = types
|
||||||
self.uiIsProcessing = true
|
self.uiIsProcessing = true
|
||||||
self.uiError = undefined
|
self.uiError = undefined
|
||||||
try {
|
try {
|
||||||
if (!self.environment.authStore) {
|
if (!self.env.authStore) {
|
||||||
throw new Error('Auth store not initialized')
|
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.isAuthed = res
|
||||||
self.uiIsProcessing = false
|
self.uiIsProcessing = false
|
||||||
return res
|
return res
|
||||||
|
@ -42,10 +42,10 @@ export const SessionModel = types
|
||||||
self.uiIsProcessing = true
|
self.uiIsProcessing = true
|
||||||
self.uiError = undefined
|
self.uiError = undefined
|
||||||
try {
|
try {
|
||||||
if (!self.environment.authStore) {
|
if (!self.env.authStore) {
|
||||||
throw new Error('Auth store not initialized')
|
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.isAuthed = false
|
||||||
self.uiIsProcessing = false
|
self.uiIsProcessing = false
|
||||||
return res
|
return res
|
||||||
|
@ -65,7 +65,7 @@ export const SessionModel = types
|
||||||
// secretKeyStr: self.secretKeyStr,
|
// secretKeyStr: self.secretKeyStr,
|
||||||
// rootAuthToken: self.rootAuthToken,
|
// rootAuthToken: self.rootAuthToken,
|
||||||
// })
|
// })
|
||||||
// self.environment.api.setUserCfg(cfg)
|
// self.env.api.setUserCfg(cfg)
|
||||||
self.isAuthed = true
|
self.isAuthed = true
|
||||||
self.uiIsProcessing = false
|
self.uiIsProcessing = false
|
||||||
return true
|
return true
|
||||||
|
@ -86,7 +86,7 @@ export const SessionModel = types
|
||||||
// self.secretKeyStr = state.secretKeyStr
|
// self.secretKeyStr = state.secretKeyStr
|
||||||
// self.rootAuthToken = state.rootAuthToken
|
// self.rootAuthToken = state.rootAuthToken
|
||||||
self.isAuthed = true
|
self.isAuthed = true
|
||||||
// self.environment.api.setUserCfg(cfg)
|
// self.env.api.setUserCfg(cfg)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error('Failed to create test account', e)
|
console.error('Failed to create test account', e)
|
||||||
self.uiError = e.toString()
|
self.uiError = e.toString()
|
||||||
|
|
|
@ -10,8 +10,8 @@ import {createNativeStackNavigator} from '@react-navigation/native-stack'
|
||||||
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'
|
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import type {RootTabsParamList} from './types'
|
import type {RootTabsParamList} from './types'
|
||||||
import {useStores} from '../state'
|
import {useStores} from '../../state'
|
||||||
import * as platform from '../platform/detection'
|
import * as platform from '../../platform/detection'
|
||||||
import {Home} from '../screens/Home'
|
import {Home} from '../screens/Home'
|
||||||
import {Search} from '../screens/Search'
|
import {Search} from '../screens/Search'
|
||||||
import {Notifications} from '../screens/Notifications'
|
import {Notifications} from '../screens/Notifications'
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Text, Button, View} from 'react-native'
|
import {Text, Button, View} from 'react-native'
|
||||||
import {Shell} from '../platform/shell'
|
import {Shell} from '../shell'
|
||||||
import type {RootTabsScreenProps} from '../routes/types'
|
import type {RootTabsScreenProps} from '../routes/types'
|
||||||
import {useStores} from '../state'
|
import {useStores} from '../../state'
|
||||||
|
|
||||||
export function Home({navigation}: RootTabsScreenProps<'Home'>) {
|
export function Home({navigation}: RootTabsScreenProps<'Home'>) {
|
||||||
const store = useStores()
|
const store = useStores()
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Text, Button, View, ActivityIndicator} from 'react-native'
|
import {Text, Button, View, ActivityIndicator} from 'react-native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {Shell} from '../platform/shell'
|
import {Shell} from '../shell'
|
||||||
import type {RootTabsScreenProps} from '../routes/types'
|
import type {RootTabsScreenProps} from '../routes/types'
|
||||||
import {useStores} from '../state'
|
import {useStores} from '../../state'
|
||||||
|
|
||||||
export const Login = observer(({navigation}: RootTabsScreenProps<'Login'>) => {
|
export const Login = observer(({navigation}: RootTabsScreenProps<'Login'>) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Shell} from '../platform/shell'
|
import {Shell} from '../shell'
|
||||||
import {ScrollView, Text, View} from 'react-native'
|
import {ScrollView, Text, View} from 'react-native'
|
||||||
import type {RootTabsScreenProps} from '../routes/types'
|
import type {RootTabsScreenProps} from '../routes/types'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Shell} from '../platform/shell'
|
import {Shell} from '../shell'
|
||||||
import {Text, Button, View} from 'react-native'
|
import {Text, Button, View} from 'react-native'
|
||||||
import type {RootTabsScreenProps} from '../routes/types'
|
import type {RootTabsScreenProps} from '../routes/types'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Shell} from '../platform/shell'
|
import {Shell} from '../shell'
|
||||||
import {Text, View} from 'react-native'
|
import {Text, View} from 'react-native'
|
||||||
import type {RootTabsScreenProps} from '../routes/types'
|
import type {RootTabsScreenProps} from '../routes/types'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Shell} from '../platform/shell'
|
import {Shell} from '../shell'
|
||||||
import {View, Text} from 'react-native'
|
import {View, Text} from 'react-native'
|
||||||
import type {RootTabsScreenProps} from '../routes/types'
|
import type {RootTabsScreenProps} from '../routes/types'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Shell} from '../platform/shell'
|
import {Shell} from '../shell'
|
||||||
import {Text, View} from 'react-native'
|
import {Text, View} from 'react-native'
|
||||||
import type {RootTabsScreenProps} from '../routes/types'
|
import type {RootTabsScreenProps} from '../routes/types'
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Text, Button, View, ActivityIndicator} from 'react-native'
|
import {Text, Button, View, ActivityIndicator} from 'react-native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {Shell} from '../platform/shell'
|
import {Shell} from '../shell'
|
||||||
import type {RootTabsScreenProps} from '../routes/types'
|
import type {RootTabsScreenProps} from '../routes/types'
|
||||||
import {useStores} from '../state'
|
import {useStores} from '../../state'
|
||||||
|
|
||||||
export const Signup = observer(
|
export const Signup = observer(
|
||||||
({navigation}: RootTabsScreenProps<'Signup'>) => {
|
({navigation}: RootTabsScreenProps<'Signup'>) => {
|
|
@ -3,7 +3,7 @@ import {observer} from 'mobx-react-lite'
|
||||||
import {View, StyleSheet} from 'react-native'
|
import {View, StyleSheet} from 'react-native'
|
||||||
import {DesktopLeftColumn} from './left-column'
|
import {DesktopLeftColumn} from './left-column'
|
||||||
import {DesktopRightColumn} from './right-column'
|
import {DesktopRightColumn} from './right-column'
|
||||||
import {useStores} from '../../state'
|
import {useStores} from '../../../state'
|
||||||
|
|
||||||
export const DesktopWebShell: React.FC = observer(({children}) => {
|
export const DesktopWebShell: React.FC = observer(({children}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {SafeAreaView} from 'react-native'
|
import {SafeAreaView} from 'react-native'
|
||||||
import {isDesktopWeb} from './detection'
|
import {isDesktopWeb} from '../../platform/detection'
|
||||||
import {DesktopWebShell} from './desktop-web/shell'
|
import {DesktopWebShell} from './desktop-web/shell'
|
||||||
|
|
||||||
export const Shell: React.FC = ({children}) => {
|
export const Shell: React.FC = ({children}) => {
|
28
yarn.lock
28
yarn.lock
|
@ -55,6 +55,14 @@
|
||||||
ucans "0.9.0-alpha3"
|
ucans "0.9.0-alpha3"
|
||||||
uint8arrays "^3.0.0"
|
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@*":
|
"@adxp/server@*":
|
||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@adxp/server/-/server-0.0.2.tgz#706d248a5e481a4582657c12d919acb4d96106fb"
|
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"
|
json-schema-traverse "^0.4.1"
|
||||||
uri-js "^4.2.2"
|
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"
|
version "8.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
|
||||||
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
|
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"
|
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||||
|
|
||||||
buffer@^5.5.0:
|
buffer@^5.4.3, buffer@^5.5.0:
|
||||||
version "5.7.1"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
||||||
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
|
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
|
||||||
|
@ -11059,6 +11067,13 @@ react-native-screens@^3.13.1:
|
||||||
react-freeze "^1.0.0"
|
react-freeze "^1.0.0"
|
||||||
warn-once "^0.1.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:
|
react-native-web@^0.17.7:
|
||||||
version "0.17.7"
|
version "0.17.7"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.17.7.tgz#038899dbc94467a0ca0be214b88a30e0c117b176"
|
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"
|
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
||||||
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
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:
|
whatwg-url@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||||
|
|
Loading…
Reference in New Issue