Skip to content
This repository was archived by the owner on Oct 4, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions packages/common/src/services/audius-backend/eagerLoadUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { paramsToQueryString } from '../../utils/paramsToQueryString'
import { Env } from '../env'
import { LocalStorage } from '../local-storage'

Expand Down Expand Up @@ -28,3 +29,56 @@ export const getEagerDiscprov = async (
}
return eagerDiscprov
}

/**
* Takes a request object provided from the audius libs API and makes the request
* using the fetch API.
*/
export const makeEagerRequest = async (
req: any,
endpoint: string,
requiresUser = false,
localStorage: LocalStorage,
env: Env
) => {
const eagerDiscprov = await getEagerDiscprov(localStorage, env)
const discprovEndpoint = endpoint ?? eagerDiscprov
const user = await localStorage.getAudiusAccountUser()
if (!user && requiresUser) throw new Error('User required to continue')

const headers: { [key: string]: string } = {}
if (user && user.user_id) {
headers['X-User-ID'] = user.user_id
}

let baseUrl = `${discprovEndpoint}/${req.endpoint}`
if (req.urlParams) {
baseUrl = `${baseUrl}${req.urlParams}`
}

let res: any
if (req?.method?.toLowerCase() === 'post') {
headers['Content-Type'] = 'application/json'
const url = `${baseUrl}?${paramsToQueryString(
req.queryParams,
discprovEndpoint === eagerDiscprov
)}`
res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(req.data)
})
} else {
const url = `${baseUrl}?${paramsToQueryString(
req.queryParams,
discprovEndpoint === eagerDiscprov
)}`
res = await fetch(url, {
headers
})
}

const json = await res.json()
if (json.data) return json.data
return json
}
1 change: 1 addition & 0 deletions packages/common/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export * from './sagaHelpers'
export * as hlsUtils from './hls'
export * from './fileUtils'
export * from './constants'
export * from './paramsToQueryString'
25 changes: 25 additions & 0 deletions packages/common/src/utils/paramsToQueryString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Converts a provided URL params object to a query string
*/
export const paramsToQueryString = (
params: { [key: string]: string | string[] },
formatWithoutArray = false
) => {
if (!params) return ''
return Object.keys(params)
.map((k) => {
if (Array.isArray(params[k])) {
return (params[k] as string[])
.map((val: string) =>
formatWithoutArray
? `${encodeURIComponent(k)}=${encodeURIComponent(val)}`
: `${encodeURIComponent(k)}[]=${encodeURIComponent(val)}`
)
.join('&')
}
return (
encodeURIComponent(k) + '=' + encodeURIComponent(params[k] as string)
)
})
.join('&')
}
3 changes: 2 additions & 1 deletion packages/mobile/src/services/audius-api-client/apiClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { AudiusAPIClient } from '@audius/common'

import { audiusBackendInstance, audiusLibs } from '../audius-backend-instance'
import { audiusBackendInstance } from '../audius-backend-instance'
import { env } from '../env'
import { audiusLibs } from '../libs'
import { localStorage } from '../local-storage'
import { remoteConfigInstance } from '../remote-config'

Expand Down
49 changes: 9 additions & 40 deletions packages/mobile/src/services/audius-backend-instance.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,24 @@
import EventEmitter from 'events'

import { audiusBackend } from '@audius/common'
import * as nativeLibs from '@audius/sdk/dist/native-libs'
import type { AudiusLibs } from '@audius/sdk/dist/native-libs'
import AsyncStorage from '@react-native-async-storage/async-storage'
import Config from 'react-native-config'
import scrypt from 'react-native-scrypt'

import { track } from 'app/services/analytics'
import { reportToSentry } from 'app/utils/reportToSentry'

import { withEagerOption } from './eagerLoadUtils'
import { env } from './env'
import {
libsInitEventEmitter,
LIBS_INITTED_EVENT,
setLibs,
waitForLibsInit
} from './libs'
import { monitoringCallbacks } from './monitoringCallbacks'
import { getFeatureEnabled } from './remote-config'
import { remoteConfigInstance } from './remote-config/remote-config-instance'

// TODO: declare this at the root and use actual audiusLibs type
declare global {
interface Window {
audiusLibs: any
}
}

const libsInitEventEmitter = new EventEmitter()

export let audiusLibs: AudiusLibs

const LIBS_INITTED_EVENT = 'LIBS_INITTED_EVENT'

/**
* Wait for the `LIBS_INITTED_EVENT` or pass through if there
* already exists a mounted `window.audiusLibs` object.
*/
const waitForLibsInit = async () => {
// If libs is already defined, it has already loaded & initted
// so do nothing
if (audiusLibs) return
// Add an event listener and resolve when that returns
return new Promise<void>((resolve) => {
if (audiusLibs) {
resolve()
} else {
libsInitEventEmitter.addListener(LIBS_INITTED_EVENT, resolve)
}
})
}

function bufferFromHexString(hexString: string) {
const byteArray = hexString
.match(/.{1,2}/g)
Expand Down Expand Up @@ -121,7 +94,7 @@ export const audiusBackendInstance = audiusBackend({
monitoringCallbacks,
nativeMobile: true,
onLibsInit: (libs) => {
audiusLibs = libs
setLibs(libs)
libsInitEventEmitter.emit(LIBS_INITTED_EVENT)
},
recaptchaSiteKey: Config.RECAPTCHA_SITE_KEY,
Expand Down Expand Up @@ -158,10 +131,6 @@ export const audiusBackendInstance = audiusBackend({
},
getLibs: async () => nativeLibs,
waitForLibsInit,
withEagerOption: ({ normal }, ...args) => {
if (audiusLibs) {
return normal(audiusLibs)(...args)
}
},
withEagerOption,
disableImagePreload: true
})
52 changes: 52 additions & 0 deletions packages/mobile/src/services/eagerLoadUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Utilities to assist in eager pre-fetching content from the
* protocol before libs has initialized.
*/

import { getEagerDiscprov, makeEagerRequest } from '@audius/common'

import { env } from './env'
import { audiusLibs, waitForLibsInit } from './libs'
import { localStorage } from './local-storage'

/**
* Wraps a normal libs method call with method that calls the
* provided eager variant if libs is not already loaded.
* In the case the eager version returns an error, we wait for
* libs to inititalize and then call the normal method.
*/
export const withEagerOption = async (
{
normal,
eager,
endpoint,
requiresUser = false
}: {
normal: (libs: any) => any
eager: (...args: any) => any
endpoint?: string
requiresUser?: boolean
},
...args: any
) => {
const disprovEndpoint =
endpoint ?? (await getEagerDiscprov(localStorage, env))
if (audiusLibs) {
return normal(audiusLibs)(...args)
} else {
try {
const req = eager(...args)
const res = await makeEagerRequest(
req,
disprovEndpoint as string,
requiresUser,
localStorage,
env
)
return res
} catch (e) {
await waitForLibsInit()
return normal(audiusLibs)(...args)
}
}
}
36 changes: 36 additions & 0 deletions packages/mobile/src/services/libs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import EventEmitter from 'events'

import type { AudiusLibs } from '@audius/sdk/dist/native-libs'

// TODO: declare this at the root and use actual audiusLibs type
declare global {
interface Window {
audiusLibs: any
}
}

export const libsInitEventEmitter = new EventEmitter()

export let audiusLibs: AudiusLibs

export const LIBS_INITTED_EVENT = 'LIBS_INITTED_EVENT'

export const setLibs = (libs: AudiusLibs) => (audiusLibs = libs)

/**
* Wait for the `LIBS_INITTED_EVENT` or pass through if there
* already exists a mounted `window.audiusLibs` object.
*/
export const waitForLibsInit = async () => {
// If libs is already defined, it has already loaded & initted
// so do nothing
if (audiusLibs) return
// Add an event listener and resolve when that returns
return new Promise<void>((resolve) => {
if (audiusLibs) {
resolve()
} else {
libsInitEventEmitter.addListener(LIBS_INITTED_EVENT, resolve)
}
})
}
85 changes: 5 additions & 80 deletions packages/web/src/services/audius-backend/eagerLoadUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* protocol before libs has initialized.
*/

import { getEagerDiscprov } from '@audius/common'
import { getEagerDiscprov, makeEagerRequest } from '@audius/common'

import { env } from 'services/env'
import { localStorage } from 'services/local-storage'
Expand Down Expand Up @@ -56,10 +56,12 @@ export const withEagerOption = async (
} else {
try {
const req = eager(...args)
const res = await makeRequest(
const res = await makeEagerRequest(
req,
disprovEndpoint as string,
requiresUser
requiresUser,
localStorage,
env
)
return res
} catch (e) {
Expand All @@ -69,80 +71,3 @@ export const withEagerOption = async (
}
}
}

/**
* Convertsa a provided URL params object to a query string
*/
const parmsToQS = (
params: { [key: string]: string | string[] },
formatWithoutArray = false
) => {
if (!params) return ''
return Object.keys(params)
.map((k) => {
if (Array.isArray(params[k])) {
return (params[k] as string[])
.map((val: string) =>
formatWithoutArray
? `${encodeURIComponent(k)}=${encodeURIComponent(val)}`
: `${encodeURIComponent(k)}[]=${encodeURIComponent(val)}`
)
.join('&')
}
return (
encodeURIComponent(k) + '=' + encodeURIComponent(params[k] as string)
)
})
.join('&')
}

/**
* Takes a request object provided from the audius libs API and makes the request
* using the fetch API.
*/
const makeRequest = async (
req: any,
endpoint: string,
requiresUser = false
) => {
const eagerDiscprov = await getEagerDiscprov(localStorage, env)
const discprovEndpoint = endpoint ?? eagerDiscprov
const user = await localStorage.getAudiusAccountUser()
if (!user && requiresUser) throw new Error('User required to continue')

const headers: { [key: string]: string } = {}
if (user && user.user_id) {
headers['X-User-ID'] = user.user_id
}

let baseUrl = `${discprovEndpoint}/${req.endpoint}`
if (req.urlParams) {
baseUrl = `${baseUrl}${req.urlParams}`
}

let res: any
if (req?.method?.toLowerCase() === 'post') {
headers['Content-Type'] = 'application/json'
const url = `${baseUrl}?${parmsToQS(
req.queryParams,
discprovEndpoint === eagerDiscprov
)}`
res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(req.data)
})
} else {
const url = `${baseUrl}?${parmsToQS(
req.queryParams,
discprovEndpoint === eagerDiscprov
)}`
res = await fetch(url, {
headers
})
}

const json = await res.json()
if (json.data) return json.data
return json
}