The Peregrine web client is a TypeScript library that
TypeScript API
ProxyClient
A client that uses Proxies to provide an idiomatic API.
To construct a ProxyClient
, a generic parameter type must be provided which
adheres to the RemoteInterface
type.
These types are not verified against the actual runtime data the client sends to and receives from the remote interface—it is up to you to keep the TypeScript interface up to date.
Peregrine does not provide any tooling (see Philosophy), but perhaps a third-party tool could generate these types from the remote interfaces defined in the native platforms.
To demonstrate how the generic parameter is specified, the following example
defines two functions and an observable in the MyRemoteInterface
type, which
is then passed into ProxyClient
as it is constructed.
export interface DeviceInfo {
platform: string
model: string
manufacturer: string
}
export type ConnectionType = 'cellular' | 'wifi' | 'none'
export type MyRemoteInterface = {
echo: (input: string) => Promise<string>
getDeviceInfo: () => Promise<DeviceInfo>
networkConnection$: AsyncGenerator<ConnectionType>
}
export const client = new ProxyClient<MyRemoteInterface>()
Peregrine clients don’t connect automatically upon instantiation. The
connect()
method must be called.
Decoupling initialization from instantiation removes unintended side-effects and
allows you to connect and disconnect client(s) at will.
client.connect(window)
The ProxyClient
class extends
StringClient
,
which can be used directly to avoid the use of proxies, if you prefer.
Methods
connect(context: Window): Promise<void>
Connect the client to the remote interface via the provided context.
disconnect(): Promise<void>
Disconnect the client from the remote interface.
url(path: string): Promise<URL>
Get a full URL
to a
subpath of a configured path handler.
In the following example, we’ll get the URL of a file given the following path handler configuration.
pathHandlers: [
"/photos/": InternalStoragePathHandler(directory: photosURL)
]
const url = await client.url('/photos/IMG_1234.jpg')
get<N extends keyof T>(name: N): T[N]
Synchronously get the handle for a remote function or remote observable by name.
You likely won’t need to use this method. You can get handles by accessing
properties directly from ProxyClient
. The following example highlights two
equivalent ways of getting an observable handle from the client.
const handle1 = client.get('networkConnection$')
const handle2 = client.networkConnection$
invoke<N extends keyof RemoteFunctions<T>>(
name: N,
arg?: Parameters<RemoteFunctions<T>[N]>[0],
): ReturnType<RemoteFunctions<T>[N]>
name: N,
arg?: Parameters<RemoteFunctions<T>[N]>[0],
): ReturnType<RemoteFunctions<T>[N]>
Invoke a function from the remote interface by name with the provided argument.
You likely won’t need to use this method. You can invoke remote functions by
invoking methods directly on ProxyClient
. The following example highlights two
equivalent ways of invoking remote functions.
const result1 = await client.invoke('echo', 'Hello World!')
const result2 = await client.echo('Hello World!')
RemoteInterface
type RemoteInterface = Record<
string,
RemoteFunction<any, any> | RemoteObservable<any>
>
RemoteFunction
type RemoteFunction<I, O> = (arg: I) => Promise<O>
Remote functions take exactly one parameter (which can be left omitted) and always return a promise.
If successful, the promise will resolve with the data from the remote interface.
Data can be a string, an object, or (on iOS) an ArrayBuffer. If unsuccessful,
the promise will reject with a ClientError
exception.
RemoteObservable
type RemoteObservable<T> = AsyncGenerator<T>
Remote observables get converted into async generators, which can be iterated over by using for-await…of loops, like in the following example.
for await (const connectionType of client.networkConnection$) {
console.log(`Network connection changed: ${connectionType}`)
}
For a more declarative API in reactive apps, it is easy to convert async generators to RxJS observables. This can be an idiomatic choice for simulating a single pipeline of asynchronous events across platforms.
import { from } from 'rxjs'
const connection$ = from(client.networkConnection$)
connection$.subscribe(connectionType => {
console.log(`Network connection changed: ${connectionType}`)
})
RemoteFunctions
type RemoteFunctions<T extends RemoteInterface> = {
[K in keyof T]: T[K] extends RemoteFunction<any, any> ? T[K] : never
}
ClientError
An exception that contains the message and an optional code from the remote interface.
Properties
readonly message: string
The human-readable error message.
readonly code: string | null
The machine-readable error code.