mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-01 19:46:36 +03:00
@urbit/http-api: fix package configuration
This commit is contained in:
parent
f4ddda2ce5
commit
2cba9b1a0b
@ -1,521 +0,0 @@
|
||||
import { isBrowser, isNode } from 'browser-or-node';
|
||||
import { Action, Scry, Thread } from '@urbit/api';
|
||||
import { fetchEventSource, EventSourceMessage, EventStreamContentType } from '@microsoft/fetch-event-source';
|
||||
|
||||
import { AuthenticationInterface, SubscriptionInterface, CustomEventHandler, PokeInterface, SubscriptionRequestInterface, headers, UrbitInterface, SSEOptions, PokeHandlers, Message } from './types';
|
||||
import { uncamelize, hexString } from './utils';
|
||||
|
||||
/**
|
||||
* A class for interacting with an urbit ship, given its URL and code
|
||||
*/
|
||||
export class Urbit implements UrbitInterface {
|
||||
/**
|
||||
* UID will be used for the channel: The current unix time plus a random hex string
|
||||
*/
|
||||
uid: string = `${Math.floor(Date.now() / 1000)}-${hexString(6)}`;
|
||||
|
||||
/**
|
||||
* Last Event ID is an auto-updated index of which events have been sent over this channel
|
||||
*/
|
||||
lastEventId: number = 0;
|
||||
|
||||
lastAcknowledgedEventId: number = 0;
|
||||
|
||||
/**
|
||||
* SSE Client is null for now; we don't want to start polling until it the channel exists
|
||||
*/
|
||||
sseClientInitialized: boolean = false;
|
||||
|
||||
/**
|
||||
* Cookie gets set when we log in.
|
||||
*/
|
||||
cookie?: string | undefined;
|
||||
|
||||
/**
|
||||
* A registry of requestId to successFunc/failureFunc
|
||||
*
|
||||
* These functions are registered during a +poke and are executed
|
||||
* in the onServerEvent()/onServerError() callbacks. Only one of
|
||||
* the functions will be called, and the outstanding poke will be
|
||||
* removed after calling the success or failure function.
|
||||
*/
|
||||
|
||||
outstandingPokes: Map<number, PokeHandlers> = new Map();
|
||||
|
||||
/**
|
||||
* A registry of requestId to subscription functions.
|
||||
*
|
||||
* These functions are registered during a +subscribe and are
|
||||
* executed in the onServerEvent()/onServerError() callbacks. The
|
||||
* event function will be called whenever a new piece of data on this
|
||||
* subscription is available, which may be 0, 1, or many times. The
|
||||
* disconnect function may be called exactly once.
|
||||
*/
|
||||
|
||||
outstandingSubscriptions: Map<number, SubscriptionRequestInterface> = new Map();
|
||||
|
||||
/**
|
||||
* Ship can be set, in which case we can do some magic stuff like send chats
|
||||
*/
|
||||
ship?: string | null;
|
||||
|
||||
/**
|
||||
* If verbose, logs output eagerly.
|
||||
*/
|
||||
verbose?: boolean;
|
||||
|
||||
onError?: (error: any) => void = null;
|
||||
|
||||
/** This is basic interpolation to get the channel URL of an instantiated Urbit connection. */
|
||||
get channelUrl(): string {
|
||||
return `${this.url}/~/channel/${this.uid}`;
|
||||
}
|
||||
|
||||
get fetchOptions(): any {
|
||||
const headers: headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (!isBrowser) {
|
||||
headers.Cookie = this.cookie;
|
||||
}
|
||||
return {
|
||||
credentials: 'include',
|
||||
accept: '*',
|
||||
headers
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Urbit connection.
|
||||
*
|
||||
* @param url The URL (with protocol and port) of the ship to be accessed
|
||||
* @param code The access code for the ship at that address
|
||||
*/
|
||||
constructor(
|
||||
public url: string,
|
||||
public code: string
|
||||
) {
|
||||
if (isBrowser) {
|
||||
window.addEventListener('beforeunload', this.delete);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* All-in-one hook-me-up.
|
||||
*
|
||||
* Given a ship, url, and code, this returns an airlock connection
|
||||
* that is ready to go. It `|hi`s itself to create the channel,
|
||||
* then opens the channel via EventSource.
|
||||
*
|
||||
* @param AuthenticationInterface
|
||||
*/
|
||||
static async authenticate({ ship, url, code, verbose = false }: AuthenticationInterface) {
|
||||
const airlock = new Urbit(`http://${url}`, code);
|
||||
airlock.verbose = verbose;
|
||||
airlock.ship = ship;
|
||||
await airlock.connect();
|
||||
await airlock.poke({ app: 'hood', mark: 'helm-hi', json: 'opening airlock' });
|
||||
await airlock.eventSource();
|
||||
return airlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the Urbit ship. Nothing can be done until this is called.
|
||||
* That's why we roll it into this.authenticate
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
if (this.verbose) {
|
||||
console.log(`password=${this.code} `, isBrowser ? "Connecting in browser context at " + `${this.url}/~/login` : "Connecting from node context");
|
||||
}
|
||||
return fetch(`${this.url}/~/login`, {
|
||||
method: 'post',
|
||||
body: `password=${this.code}`,
|
||||
credentials: 'include',
|
||||
}).then(response => {
|
||||
if (this.verbose) {
|
||||
console.log('Received authentication response', response);
|
||||
}
|
||||
const cookie = response.headers.get('set-cookie');
|
||||
if (!this.ship) {
|
||||
this.ship = new RegExp(/urbauth-~([\w-]+)/).exec(cookie)[1];
|
||||
}
|
||||
if (!isBrowser) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the SSE pipe for the appropriate channel.
|
||||
*/
|
||||
eventSource(): void {
|
||||
if (!this.sseClientInitialized) {
|
||||
const sseOptions: SSEOptions = {
|
||||
headers: {}
|
||||
};
|
||||
if (isBrowser) {
|
||||
sseOptions.withCredentials = true;
|
||||
} else if (isNode) {
|
||||
sseOptions.headers.Cookie = this.cookie;
|
||||
}
|
||||
if (this.lastEventId === 0) {
|
||||
// Can't receive events until the channel is open
|
||||
this.poke({ app: 'hood', mark: 'helm-hi', json: 'Opening API channel' });
|
||||
}
|
||||
fetchEventSource(this.channelUrl, {
|
||||
...this.fetchOptions,
|
||||
openWhenHidden: true,
|
||||
onopen: async (response) => {
|
||||
if (this.verbose) {
|
||||
console.log('Opened eventsource', response);
|
||||
}
|
||||
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
|
||||
return; // everything's good
|
||||
} else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
||||
if (this.onError) {
|
||||
this.onError(response.statusText);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
} else {
|
||||
if (this.onError) {
|
||||
this.onError(response.statusText);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
},
|
||||
onmessage: (event: EventSourceMessage) => {
|
||||
if (this.verbose) {
|
||||
console.log('Received SSE: ', event);
|
||||
}
|
||||
if (!event.id) return;
|
||||
this.ack(Number(event.id));
|
||||
if (event.data && JSON.parse(event.data)) {
|
||||
|
||||
const data: any = JSON.parse(event.data);
|
||||
|
||||
if (data.response === 'diff') {
|
||||
this.clearQueue();
|
||||
}
|
||||
|
||||
if (data.response === 'poke' && this.outstandingPokes.has(data.id)) {
|
||||
const funcs = this.outstandingPokes.get(data.id);
|
||||
if (data.hasOwnProperty('ok')) {
|
||||
funcs.onSuccess();
|
||||
} else if (data.hasOwnProperty('err')) {
|
||||
funcs.onError(data.err);
|
||||
} else {
|
||||
console.error('Invalid poke response', data);
|
||||
}
|
||||
this.outstandingPokes.delete(data.id);
|
||||
} else if (data.response === 'subscribe' ||
|
||||
(data.response === 'poke' && this.outstandingSubscriptions.has(data.id))) {
|
||||
const funcs = this.outstandingSubscriptions.get(data.id);
|
||||
if (data.hasOwnProperty('err')) {
|
||||
funcs.err(data.err);
|
||||
this.outstandingSubscriptions.delete(data.id);
|
||||
}
|
||||
} else if (data.response === 'diff' && this.outstandingSubscriptions.has(data.id)) {
|
||||
const funcs = this.outstandingSubscriptions.get(data.id);
|
||||
funcs.event(data.json);
|
||||
} else if (data.response === 'quit' && this.outstandingSubscriptions.has(data.id)) {
|
||||
const funcs = this.outstandingSubscriptions.get(data.id);
|
||||
funcs.quit(data);
|
||||
this.outstandingSubscriptions.delete(data.id);
|
||||
} else {
|
||||
console.log('Unrecognized response', data);
|
||||
}
|
||||
}
|
||||
},
|
||||
onerror: (error) => {
|
||||
if (this.onError) {
|
||||
this.onError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.sseClientInitialized = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Autoincrements the next event ID for the appropriate channel.
|
||||
*/
|
||||
getEventId(): number {
|
||||
this.lastEventId = Number(this.lastEventId) + 1;
|
||||
return this.lastEventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledges an event.
|
||||
*
|
||||
* @param eventId The event to acknowledge.
|
||||
*/
|
||||
async ack(eventId: number): Promise<number | void> {
|
||||
const message: Message = {
|
||||
action: 'ack',
|
||||
'event-id': eventId
|
||||
};
|
||||
await this.sendJSONtoChannel(message);
|
||||
return eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a wrapper method that can be used to send any action with data.
|
||||
*
|
||||
* Every message sent has some common parameters, like method, headers, and data
|
||||
* structure, so this method exists to prevent duplication.
|
||||
*
|
||||
* @param action The action to send
|
||||
* @param data The data to send with the action
|
||||
*
|
||||
* @returns void | number If successful, returns the number of the message that was sent
|
||||
*/
|
||||
// async sendMessage(action: Action, data?: object): Promise<number | void> {
|
||||
// const id = this.getEventId();
|
||||
// if (this.verbose) {
|
||||
// console.log(`Sending message ${id}:`, action, data,);
|
||||
// }
|
||||
// const message: Message = { id, action, ...data };
|
||||
// await this.sendJSONtoChannel(message);
|
||||
// return id;
|
||||
// }
|
||||
|
||||
outstandingJSON: Message[] = [];
|
||||
|
||||
debounceTimer: any = null;
|
||||
debounceInterval = 500;
|
||||
calm = true;
|
||||
|
||||
sendJSONtoChannel(json: Message): Promise<boolean | void> {
|
||||
this.outstandingJSON.push(json);
|
||||
return this.processQueue();
|
||||
}
|
||||
|
||||
processQueue(): Promise<boolean | void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const process = async () => {
|
||||
if (this.calm) {
|
||||
if (this.outstandingJSON.length === 0) resolve(true);
|
||||
this.calm = false; // We are now occupied
|
||||
const json = this.outstandingJSON;
|
||||
const body = JSON.stringify(json);
|
||||
this.outstandingJSON = [];
|
||||
if (body === '[]') {
|
||||
this.calm = true;
|
||||
return resolve(false);
|
||||
}
|
||||
try {
|
||||
await fetch(this.channelUrl, {
|
||||
...this.fetchOptions,
|
||||
method: 'PUT',
|
||||
body
|
||||
});
|
||||
} catch (error) {
|
||||
json.forEach(failed => this.outstandingJSON.push(failed));
|
||||
if (this.onError) {
|
||||
this.onError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
this.calm = true;
|
||||
if (!this.sseClientInitialized) {
|
||||
this.eventSource(); // We can open the channel for subscriptions once we've sent data over it
|
||||
}
|
||||
resolve(true);
|
||||
} else {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = setTimeout(process, this.debounceInterval);
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
this.debounceTimer = setTimeout(process, this.debounceInterval);
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// resetDebounceTimer() {
|
||||
// if (this.debounceTimer) {
|
||||
// clearTimeout(this.debounceTimer);
|
||||
// this.debounceTimer = null;
|
||||
// }
|
||||
// this.calm = false;
|
||||
// this.debounceTimer = setTimeout(() => {
|
||||
// this.calm = true;
|
||||
// }, this.debounceInterval);
|
||||
// }
|
||||
|
||||
clearQueue() {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pokes a ship with data.
|
||||
*
|
||||
* @param app The app to poke
|
||||
* @param mark The mark of the data being sent
|
||||
* @param json The data to send
|
||||
*/
|
||||
poke<T>(params: PokeInterface<T>): Promise<number> {
|
||||
const {
|
||||
app,
|
||||
mark,
|
||||
json,
|
||||
ship,
|
||||
onSuccess,
|
||||
onError
|
||||
} = {
|
||||
onSuccess: () => { },
|
||||
onError: () => { },
|
||||
ship: this.ship,
|
||||
...params
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const message: Message = {
|
||||
id: this.getEventId(),
|
||||
action: 'poke',
|
||||
ship,
|
||||
app,
|
||||
mark,
|
||||
json
|
||||
};
|
||||
this.outstandingPokes.set(message.id, {
|
||||
onSuccess: () => {
|
||||
onSuccess();
|
||||
resolve(message.id);
|
||||
},
|
||||
onError: (event) => {
|
||||
onError(event);
|
||||
reject(event.err);
|
||||
}
|
||||
});
|
||||
this.sendJSONtoChannel(message).then(() => {
|
||||
resolve(message.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to a path on an app on a ship.
|
||||
*
|
||||
* @param app The app to subsribe to
|
||||
* @param path The path to which to subscribe
|
||||
* @param handlers Handlers to deal with various events of the subscription
|
||||
*/
|
||||
async subscribe(params: SubscriptionRequestInterface): Promise<number> {
|
||||
const {
|
||||
app,
|
||||
path,
|
||||
ship,
|
||||
err,
|
||||
event,
|
||||
quit
|
||||
} = {
|
||||
err: () => { },
|
||||
event: () => { },
|
||||
quit: () => { },
|
||||
ship: this.ship,
|
||||
...params
|
||||
};
|
||||
|
||||
const message: Message = {
|
||||
id: this.getEventId(),
|
||||
action: 'subscribe',
|
||||
ship,
|
||||
app,
|
||||
path
|
||||
};
|
||||
|
||||
this.outstandingSubscriptions.set(message.id, {
|
||||
app, path, err, event, quit
|
||||
});
|
||||
|
||||
await this.sendJSONtoChannel(message);
|
||||
|
||||
return message.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes to a given subscription.
|
||||
*
|
||||
* @param subscription
|
||||
*/
|
||||
async unsubscribe(subscription: number) {
|
||||
return this.sendJSONtoChannel({
|
||||
id: this.getEventId(),
|
||||
action: 'unsubscribe',
|
||||
subscription
|
||||
}).then(() => {
|
||||
this.outstandingSubscriptions.delete(subscription);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the connection to a channel.
|
||||
*/
|
||||
delete() {
|
||||
if (isBrowser) {
|
||||
navigator.sendBeacon(this.channelUrl, JSON.stringify([{
|
||||
action: 'delete'
|
||||
}]));
|
||||
} else {
|
||||
// TODO
|
||||
// this.sendMessage('delete');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param app The app into which to scry
|
||||
* @param path The path at which to scry
|
||||
*/
|
||||
async scry(params: Scry): Promise<void | any> {
|
||||
const { app, path } = params;
|
||||
const response = await fetch(`${this.url}/~/scry/${app}${path}.json`, this.fetchOptions);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param inputMark The mark of the data being sent
|
||||
* @param outputMark The mark of the data being returned
|
||||
* @param threadName The thread to run
|
||||
* @param body The data to send to the thread
|
||||
*/
|
||||
async thread<T>(params: Thread<T>): Promise<T> {
|
||||
const { inputMark, outputMark, threadName, body } = params;
|
||||
const res = await fetch(`${this.url}/spider/${inputMark}/${threadName}/${outputMark}.json`, {
|
||||
...this.fetchOptions,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to connect to a ship that has its *.arvo.network domain configured.
|
||||
*
|
||||
* @param name Name of the ship e.g. zod
|
||||
* @param code Code to log in
|
||||
*/
|
||||
static async onArvoNetwork(ship: string, code: string): Promise<Urbit> {
|
||||
const url = `https://${ship}.arvo.network`;
|
||||
return await Urbit.authenticate({ ship, url, code });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default Urbit;
|
@ -1,3 +0,0 @@
|
||||
export * from './types';
|
||||
import Urbit from './Urbit';
|
||||
export { Urbit as default };
|
260
pkg/npm/http-api/package-lock.json
generated
260
pkg/npm/http-api/package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@urbit/http-api",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -479,6 +479,11 @@
|
||||
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.170",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
|
||||
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q=="
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz",
|
||||
@ -690,7 +695,9 @@
|
||||
}
|
||||
},
|
||||
"@urbit/api": {
|
||||
"version": "file:../api",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@urbit/api/-/api-1.1.0.tgz",
|
||||
"integrity": "sha512-oflDguSpB6u9VmlmbX23Z+aSjMCFj7QbmQEG6YjhZvuz2ok711/F5+B4NvGKOl0NfdG2ao2wK7m+z4AyH4pZjQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/lodash": "^4.14.168",
|
||||
@ -698,239 +705,13 @@
|
||||
"big-integer": "^1.6.48",
|
||||
"immer": "^9.0.1",
|
||||
"lodash": "^4.17.20"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
|
||||
"integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@blakeembrey/deque": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@blakeembrey/deque/-/deque-1.0.5.tgz",
|
||||
"integrity": "sha512-6xnwtvp9DY1EINIKdTfvfeAtCYw4OqBZJhtiqkT3ivjnEfa25VQ3TsKvaFfKm8MyGIEfE95qLe+bNEt3nB0Ylg=="
|
||||
},
|
||||
"@blakeembrey/template": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@blakeembrey/template/-/template-1.0.0.tgz",
|
||||
"integrity": "sha512-J6WGZqCLdRMHUkyRG6fBSIFJ0rL60/nsQNh5rQvsYZ5u0PsKw6XQcJcA3DWvd9cN3j/IQx5yB1fexhCafwwUUw=="
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.169",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.169.tgz",
|
||||
"integrity": "sha512-DvmZHoHTFJ8zhVYwCLWbQ7uAbYQEk52Ev2/ZiQ7Y7gQGeV9pjBqjnQpECMHfKS1rCYAhMI7LHVxwyZLZinJgdw=="
|
||||
},
|
||||
"@urbit/eslint-config": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@urbit/eslint-config/-/eslint-config-1.0.0.tgz",
|
||||
"integrity": "sha512-Xmzb6MvM7KorlPJEq/hURZZ4BHSVy/7CoQXWogsBSTv5MOZnMqwNKw6yt24k2AO/2UpHwjGptimaNLqFfesJbw=="
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.48",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
||||
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"requires": {
|
||||
"anymatch": "~3.1.1",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.5.0"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"optional": true
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw=="
|
||||
},
|
||||
"immer": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.2.tgz",
|
||||
"integrity": "sha512-mkcmzLtIfSp40vAqteRr1MbWNSoI7JE+/PB36FNPoSfJ9RQRmNKuTYCjKkyXyuq3Dgn07HuJBrwJd4ZSk2yUbw=="
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"requires": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
||||
},
|
||||
"onchange": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/onchange/-/onchange-7.1.0.tgz",
|
||||
"integrity": "sha512-ZJcqsPiWUAUpvmnJri5TPBooqJOPmC0ttN65juhN15Q8xA+Nbg3BaxBHXQ45EistKKlKElb0edmbPWnKSBkvMg==",
|
||||
"requires": {
|
||||
"@blakeembrey/deque": "^1.0.5",
|
||||
"@blakeembrey/template": "^1.0.0",
|
||||
"arg": "^4.1.3",
|
||||
"chokidar": "^3.3.1",
|
||||
"cross-spawn": "^7.0.1",
|
||||
"ignore": "^5.1.4",
|
||||
"tree-kill": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz",
|
||||
"integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.7",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"requires": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@urbit/eslint-config": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@urbit/eslint-config/-/eslint-config-1.0.0.tgz",
|
||||
"integrity": "sha512-Xmzb6MvM7KorlPJEq/hURZZ4BHSVy/7CoQXWogsBSTv5MOZnMqwNKw6yt24k2AO/2UpHwjGptimaNLqFfesJbw=="
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz",
|
||||
@ -1346,6 +1127,11 @@
|
||||
"integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
|
||||
"dev": true
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.48",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
||||
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
|
||||
},
|
||||
"big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
@ -3250,6 +3036,11 @@
|
||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"immer": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.2.tgz",
|
||||
"integrity": "sha512-mkcmzLtIfSp40vAqteRr1MbWNSoI7JE+/PB36FNPoSfJ9RQRmNKuTYCjKkyXyuq3Dgn07HuJBrwJd4ZSk2yUbw=="
|
||||
},
|
||||
"import-local": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
|
||||
@ -3753,8 +3544,7 @@
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"loglevel": {
|
||||
"version": "1.7.1",
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { isBrowser, isNode } from 'browser-or-node';
|
||||
import { Action, Scry, Thread } from '@urbit/api';
|
||||
import { fetchEventSource, EventSourceMessage } from '@microsoft/fetch-event-source';
|
||||
import { fetchEventSource, EventSourceMessage, EventStreamContentType } from '@microsoft/fetch-event-source';
|
||||
|
||||
import { AuthenticationInterface, SubscriptionInterface, CustomEventHandler, PokeInterface, SubscriptionRequestInterface, headers, UrbitInterface, SSEOptions, PokeHandlers } from './types';
|
||||
import { AuthenticationInterface, SubscriptionInterface, CustomEventHandler, PokeInterface, SubscriptionRequestInterface, headers, UrbitInterface, SSEOptions, PokeHandlers, Message } from './types';
|
||||
import { uncamelize, hexString } from './utils';
|
||||
|
||||
/**
|
||||
@ -52,7 +52,7 @@ export class Urbit implements UrbitInterface {
|
||||
* disconnect function may be called exactly once.
|
||||
*/
|
||||
|
||||
outstandingSubscriptions: Map<number, SubscriptionInterface> = new Map();
|
||||
outstandingSubscriptions: Map<number, SubscriptionRequestInterface> = new Map();
|
||||
|
||||
/**
|
||||
* Ship can be set, in which case we can do some magic stuff like send chats
|
||||
@ -64,6 +64,8 @@ export class Urbit implements UrbitInterface {
|
||||
*/
|
||||
verbose?: boolean;
|
||||
|
||||
onError?: (error: any) => void = null;
|
||||
|
||||
/** This is basic interpolation to get the channel URL of an instantiated Urbit connection. */
|
||||
get channelUrl(): string {
|
||||
return `${this.url}/~/channel/${this.uid}`;
|
||||
@ -78,6 +80,7 @@ export class Urbit implements UrbitInterface {
|
||||
}
|
||||
return {
|
||||
credentials: 'include',
|
||||
accept: '*',
|
||||
headers
|
||||
};
|
||||
}
|
||||
@ -90,8 +93,11 @@ export class Urbit implements UrbitInterface {
|
||||
*/
|
||||
constructor(
|
||||
public url: string,
|
||||
public code: string
|
||||
public code?: string
|
||||
) {
|
||||
if (isBrowser) {
|
||||
window.addEventListener('beforeunload', this.delete);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -144,7 +150,8 @@ export class Urbit implements UrbitInterface {
|
||||
/**
|
||||
* Initializes the SSE pipe for the appropriate channel.
|
||||
*/
|
||||
eventSource(): void{
|
||||
eventSource(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.sseClientInitialized) {
|
||||
const sseOptions: SSEOptions = {
|
||||
headers: {}
|
||||
@ -154,15 +161,50 @@ export class Urbit implements UrbitInterface {
|
||||
} else if (isNode) {
|
||||
sseOptions.headers.Cookie = this.cookie;
|
||||
}
|
||||
if (this.lastEventId === 0) {
|
||||
// Can't receive events until the channel is open
|
||||
this.poke({ app: 'hood', mark: 'helm-hi', json: 'Opening API channel' });
|
||||
}
|
||||
fetchEventSource(this.channelUrl, {
|
||||
// withCredentials: true,
|
||||
...this.fetchOptions,
|
||||
openWhenHidden: true,
|
||||
onopen: async (response) => {
|
||||
if (this.verbose) {
|
||||
console.log('Opened eventsource', response);
|
||||
}
|
||||
if (response.ok) {
|
||||
resolve();
|
||||
return; // everything's good
|
||||
} else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
||||
reject();
|
||||
if (this.onError) {
|
||||
this.onError(response.statusText);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
} else {
|
||||
reject();
|
||||
if (this.onError) {
|
||||
this.onError(response.statusText);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
},
|
||||
onmessage: (event: EventSourceMessage) => {
|
||||
if (this.verbose) {
|
||||
console.log('Received SSE: ', event);
|
||||
}
|
||||
if (!event.id) return;
|
||||
this.ack(Number(event.id));
|
||||
if (event.data && JSON.parse(event.data)) {
|
||||
|
||||
const data: any = JSON.parse(event.data);
|
||||
|
||||
if (data.response === 'diff') {
|
||||
this.clearQueue();
|
||||
}
|
||||
|
||||
if (data.response === 'poke' && this.outstandingPokes.has(data.id)) {
|
||||
const funcs = this.outstandingPokes.get(data.id);
|
||||
if (data.hasOwnProperty('ok')) {
|
||||
@ -193,12 +235,17 @@ export class Urbit implements UrbitInterface {
|
||||
}
|
||||
},
|
||||
onerror: (error) => {
|
||||
console.error('pipe error', error);
|
||||
if (this.onError) {
|
||||
this.onError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.sseClientInitialized = true;
|
||||
}
|
||||
return;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,8 +261,13 @@ export class Urbit implements UrbitInterface {
|
||||
*
|
||||
* @param eventId The event to acknowledge.
|
||||
*/
|
||||
ack(eventId: number): Promise<void | number> {
|
||||
return this.sendMessage('ack', { 'event-id': eventId });
|
||||
async ack(eventId: number): Promise<number | void> {
|
||||
const message: Message = {
|
||||
action: 'ack',
|
||||
'event-id': eventId
|
||||
};
|
||||
await this.sendJSONtoChannel(message);
|
||||
return eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -229,31 +281,88 @@ export class Urbit implements UrbitInterface {
|
||||
*
|
||||
* @returns void | number If successful, returns the number of the message that was sent
|
||||
*/
|
||||
async sendMessage(action: Action, data?: object): Promise<void | number> {
|
||||
|
||||
const id = this.getEventId();
|
||||
if (this.verbose) {
|
||||
console.log(`Sending message ${id}:`, action, data,);
|
||||
}
|
||||
let response: Response | undefined;
|
||||
try {
|
||||
response = await fetch(this.channelUrl, {
|
||||
...this.fetchOptions,
|
||||
method: 'put',
|
||||
body: JSON.stringify([{
|
||||
id,
|
||||
action,
|
||||
...data,
|
||||
}]),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('message error', error);
|
||||
response = undefined;
|
||||
}
|
||||
if (this.verbose) {
|
||||
console.log(`Received from message ${id}: `, response);
|
||||
}
|
||||
return id;
|
||||
// async sendMessage(action: Action, data?: object): Promise<number | void> {
|
||||
// const id = this.getEventId();
|
||||
// if (this.verbose) {
|
||||
// console.log(`Sending message ${id}:`, action, data,);
|
||||
// }
|
||||
// const message: Message = { id, action, ...data };
|
||||
// await this.sendJSONtoChannel(message);
|
||||
// return id;
|
||||
// }
|
||||
|
||||
outstandingJSON: Message[] = [];
|
||||
|
||||
debounceTimer: any = null;
|
||||
debounceInterval = 500;
|
||||
calm = true;
|
||||
|
||||
sendJSONtoChannel(json: Message): Promise<boolean | void> {
|
||||
this.outstandingJSON.push(json);
|
||||
return this.processQueue();
|
||||
}
|
||||
|
||||
processQueue(): Promise<boolean | void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const process = async () => {
|
||||
if (this.calm) {
|
||||
if (this.outstandingJSON.length === 0) resolve(true);
|
||||
this.calm = false; // We are now occupied
|
||||
const json = this.outstandingJSON;
|
||||
const body = JSON.stringify(json);
|
||||
this.outstandingJSON = [];
|
||||
if (body === '[]') {
|
||||
this.calm = true;
|
||||
return resolve(false);
|
||||
}
|
||||
try {
|
||||
await fetch(this.channelUrl, {
|
||||
...this.fetchOptions,
|
||||
method: 'PUT',
|
||||
body
|
||||
});
|
||||
} catch (error) {
|
||||
json.forEach(failed => this.outstandingJSON.push(failed));
|
||||
if (this.onError) {
|
||||
this.onError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
this.calm = true;
|
||||
if (!this.sseClientInitialized) {
|
||||
this.eventSource(); // We can open the channel for subscriptions once we've sent data over it
|
||||
}
|
||||
resolve(true);
|
||||
} else {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = setTimeout(process, this.debounceInterval);
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
this.debounceTimer = setTimeout(process, this.debounceInterval);
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// resetDebounceTimer() {
|
||||
// if (this.debounceTimer) {
|
||||
// clearTimeout(this.debounceTimer);
|
||||
// this.debounceTimer = null;
|
||||
// }
|
||||
// this.calm = false;
|
||||
// this.debounceTimer = setTimeout(() => {
|
||||
// this.calm = true;
|
||||
// }, this.debounceInterval);
|
||||
// }
|
||||
|
||||
clearQueue() {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -263,29 +372,42 @@ export class Urbit implements UrbitInterface {
|
||||
* @param mark The mark of the data being sent
|
||||
* @param json The data to send
|
||||
*/
|
||||
poke<T>(params: PokeInterface<T>): Promise<void | number> {
|
||||
const { app, mark, json, onSuccess, onError } = { onSuccess: () => {}, onError: () => {}, ...params };
|
||||
poke<T>(params: PokeInterface<T>): Promise<number> {
|
||||
const {
|
||||
app,
|
||||
mark,
|
||||
json,
|
||||
ship,
|
||||
onSuccess,
|
||||
onError
|
||||
} = {
|
||||
onSuccess: () => { },
|
||||
onError: () => { },
|
||||
ship: this.ship,
|
||||
...params
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
this
|
||||
.sendMessage('poke', { ship: this.ship, app, mark, json })
|
||||
.then(pokeId => {
|
||||
if (!pokeId) {
|
||||
return reject('Poke failed');
|
||||
}
|
||||
if (!this.sseClientInitialized) resolve(pokeId); // A poke may occur before a listener has been opened
|
||||
this.outstandingPokes.set(pokeId, {
|
||||
onSuccess: () => {
|
||||
onSuccess();
|
||||
resolve(pokeId);
|
||||
},
|
||||
onError: (event) => {
|
||||
onError(event);
|
||||
reject(event.err);
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
const message: Message = {
|
||||
id: this.getEventId(),
|
||||
action: 'poke',
|
||||
ship,
|
||||
app,
|
||||
mark,
|
||||
json
|
||||
};
|
||||
this.outstandingPokes.set(message.id, {
|
||||
onSuccess: () => {
|
||||
onSuccess();
|
||||
resolve(message.id);
|
||||
},
|
||||
onError: (event) => {
|
||||
onError(event);
|
||||
reject(event.err);
|
||||
}
|
||||
});
|
||||
this.sendJSONtoChannel(message).then(() => {
|
||||
resolve(message.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -296,18 +418,37 @@ export class Urbit implements UrbitInterface {
|
||||
* @param path The path to which to subscribe
|
||||
* @param handlers Handlers to deal with various events of the subscription
|
||||
*/
|
||||
async subscribe(params: SubscriptionRequestInterface): Promise<void | number> {
|
||||
const { app, path, err, event, quit } = { err: () => {}, event: () => {}, quit: () => {}, ...params };
|
||||
async subscribe(params: SubscriptionRequestInterface): Promise<number> {
|
||||
const {
|
||||
app,
|
||||
path,
|
||||
ship,
|
||||
err,
|
||||
event,
|
||||
quit
|
||||
} = {
|
||||
err: () => { },
|
||||
event: () => { },
|
||||
quit: () => { },
|
||||
ship: this.ship,
|
||||
...params
|
||||
};
|
||||
|
||||
const subscriptionId = await this.sendMessage('subscribe', { ship: this.ship, app, path });
|
||||
const message: Message = {
|
||||
id: this.getEventId(),
|
||||
action: 'subscribe',
|
||||
ship,
|
||||
app,
|
||||
path
|
||||
};
|
||||
|
||||
if (!subscriptionId) return;
|
||||
|
||||
this.outstandingSubscriptions.set(subscriptionId, {
|
||||
err, event, quit
|
||||
this.outstandingSubscriptions.set(message.id, {
|
||||
app, path, err, event, quit
|
||||
});
|
||||
|
||||
return subscriptionId;
|
||||
await this.sendJSONtoChannel(message);
|
||||
|
||||
return message.id;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,15 +456,28 @@ export class Urbit implements UrbitInterface {
|
||||
*
|
||||
* @param subscription
|
||||
*/
|
||||
unsubscribe(subscription: string): Promise<void | number> {
|
||||
return this.sendMessage('unsubscribe', { subscription });
|
||||
async unsubscribe(subscription: number) {
|
||||
return this.sendJSONtoChannel({
|
||||
id: this.getEventId(),
|
||||
action: 'unsubscribe',
|
||||
subscription
|
||||
}).then(() => {
|
||||
this.outstandingSubscriptions.delete(subscription);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the connection to a channel.
|
||||
*/
|
||||
delete(): Promise<void | number> {
|
||||
return this.sendMessage('delete');
|
||||
delete() {
|
||||
if (isBrowser) {
|
||||
navigator.sendBeacon(this.channelUrl, JSON.stringify([{
|
||||
action: 'delete'
|
||||
}]));
|
||||
} else {
|
||||
// TODO
|
||||
// this.sendMessage('delete');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -333,7 +487,7 @@ export class Urbit implements UrbitInterface {
|
||||
*/
|
||||
async scry(params: Scry): Promise<void | any> {
|
||||
const { app, path } = params;
|
||||
const response = await fetch(`/~/scry/${app}${path}.json`, this.fetchOptions);
|
||||
const response = await fetch(`${this.url}/~/scry/${app}${path}.json`, this.fetchOptions);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
@ -346,7 +500,7 @@ export class Urbit implements UrbitInterface {
|
||||
*/
|
||||
async thread<T>(params: Thread<T>): Promise<T> {
|
||||
const { inputMark, outputMark, threadName, body } = params;
|
||||
const res = await fetch(`/spider/${inputMark}/${threadName}/${outputMark}.json`, {
|
||||
const res = await fetch(`${this.url}/spider/${inputMark}/${threadName}/${outputMark}.json`, {
|
||||
...this.fetchOptions,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body)
|
||||
|
@ -37,19 +37,20 @@ export interface UrbitInterface {
|
||||
sseClientInitialized: boolean;
|
||||
cookie?: string | undefined;
|
||||
outstandingPokes: Map<number, PokeHandlers>;
|
||||
outstandingSubscriptions: Map<number, SubscriptionInterface>;
|
||||
outstandingSubscriptions: Map<number, SubscriptionRequestInterface>;
|
||||
verbose?: boolean;
|
||||
ship?: string | null;
|
||||
onError?: (error: any) => void;
|
||||
connect(): void;
|
||||
connect(): Promise<void>;
|
||||
eventSource(): void;
|
||||
getEventId(): number;
|
||||
ack(eventId: number): Promise<void | number>;
|
||||
sendMessage(action: Action, data?: object): Promise<void | number>;
|
||||
poke<T>(params: PokeInterface<T>): Promise<void | number>;
|
||||
subscribe(params: SubscriptionRequestInterface): Promise<void | number>;
|
||||
unsubscribe(subscription: string): Promise<void | number>;
|
||||
delete(): Promise<void | number>;
|
||||
// sendMessage(action: Action, data?: object): Promise<void | number>;
|
||||
poke<T>(params: PokeInterface<T>): Promise<number>;
|
||||
subscribe(params: SubscriptionRequestInterface): Promise<number>;
|
||||
unsubscribe(subscription: number): Promise<boolean | void>;
|
||||
delete(): void;
|
||||
scry(params: Scry): Promise<void | any>;
|
||||
thread<T>(params: Thread<T>): Promise<T>;
|
||||
}
|
||||
@ -64,3 +65,8 @@ export interface SSEOptions {
|
||||
};
|
||||
withCredentials?: boolean;
|
||||
}
|
||||
|
||||
export interface Message extends Record<string, any> {
|
||||
action: Action;
|
||||
id?: number;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"include": ["*.ts"],
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "@types"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
@ -20,4 +20,4 @@
|
||||
"*" : ["./node_modules/@types/*", "*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,72 +0,0 @@
|
||||
import { Action, Poke, Scry, Thread } from '@urbit/api';
|
||||
|
||||
export interface PokeHandlers {
|
||||
onSuccess?: () => void;
|
||||
onError?: (e: any) => void;
|
||||
}
|
||||
|
||||
export type PokeInterface<T> = PokeHandlers & Poke<T>;
|
||||
|
||||
export interface AuthenticationInterface {
|
||||
ship: string;
|
||||
url: string;
|
||||
code: string;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
export interface SubscriptionInterface {
|
||||
err?(error: any): void;
|
||||
event?(data: any): void;
|
||||
quit?(data: any): void;
|
||||
}
|
||||
|
||||
export type SubscriptionRequestInterface = SubscriptionInterface & {
|
||||
app: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface headers {
|
||||
'Content-Type': string;
|
||||
Cookie?: string;
|
||||
}
|
||||
|
||||
export interface UrbitInterface {
|
||||
uid: string;
|
||||
lastEventId: number;
|
||||
lastAcknowledgedEventId: number;
|
||||
sseClientInitialized: boolean;
|
||||
cookie?: string | undefined;
|
||||
outstandingPokes: Map<number, PokeHandlers>;
|
||||
outstandingSubscriptions: Map<number, SubscriptionRequestInterface>;
|
||||
verbose?: boolean;
|
||||
ship?: string | null;
|
||||
onError?: (error: any) => void;
|
||||
connect(): void;
|
||||
connect(): Promise<void>;
|
||||
eventSource(): void;
|
||||
getEventId(): number;
|
||||
ack(eventId: number): Promise<void | number>;
|
||||
// sendMessage(action: Action, data?: object): Promise<void | number>;
|
||||
poke<T>(params: PokeInterface<T>): Promise<number>;
|
||||
subscribe(params: SubscriptionRequestInterface): Promise<number>;
|
||||
unsubscribe(subscription: number): Promise<boolean | void>;
|
||||
delete(): void;
|
||||
scry(params: Scry): Promise<void | any>;
|
||||
thread<T>(params: Thread<T>): Promise<T>;
|
||||
}
|
||||
|
||||
export interface CustomEventHandler {
|
||||
(data: any, response: string): void;
|
||||
}
|
||||
|
||||
export interface SSEOptions {
|
||||
headers?: {
|
||||
Cookie?: string
|
||||
};
|
||||
withCredentials?: boolean;
|
||||
}
|
||||
|
||||
export interface Message extends Record<string, any> {
|
||||
action: Action;
|
||||
id?: number;
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
import * as http from 'http';
|
||||
|
||||
interface HttpResponse {
|
||||
req: http.ClientRequest;
|
||||
res: http.IncomingMessage;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export function request(
|
||||
url: string,
|
||||
options: http.ClientRequestArgs,
|
||||
body?: string
|
||||
): Promise<HttpResponse> {
|
||||
return new Promise<HttpResponse>((resolve, reject) => {
|
||||
const req = http.request(url, options, res => {
|
||||
let data = "";
|
||||
res.on("data", chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
res.on("end", () => {
|
||||
resolve({ req, res, data });
|
||||
});
|
||||
res.on("error", e => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
if (body) {
|
||||
req.write(body);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
export function camelize(str: string) {
|
||||
return str
|
||||
.replace(/\s(.)/g, function($1: string) { return $1.toUpperCase(); })
|
||||
.replace(/\s/g, '')
|
||||
.replace(/^(.)/, function($1: string) { return $1.toLowerCase(); });
|
||||
}
|
||||
|
||||
export function uncamelize(str: string, separator = '-') {
|
||||
// Replace all capital letters by separator followed by lowercase one
|
||||
var str = str.replace(/[A-Z]/g, function (letter: string) {
|
||||
return separator + letter.toLowerCase();
|
||||
});
|
||||
return str.replace(new RegExp('^' + separator), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hex string of given length.
|
||||
*
|
||||
* Poached from StackOverflow.
|
||||
*
|
||||
* @param len Length of hex string to return.
|
||||
*/
|
||||
export function hexString(len: number): string {
|
||||
const maxlen = 8;
|
||||
const min = Math.pow(16, Math.min(len, maxlen) - 1);
|
||||
const max = Math.pow(16, Math.min(len, maxlen)) - 1;
|
||||
const n = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
let r = n.toString(16);
|
||||
while (r.length < len) {
|
||||
r = r + hexString(len - maxlen);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random UID.
|
||||
*
|
||||
* Copied from https://github.com/urbit/urbit/blob/137e4428f617c13f28ed31e520eff98d251ed3e9/pkg/interface/src/lib/util.js#L3
|
||||
*/
|
||||
export function uid(): string {
|
||||
let str = '0v';
|
||||
str += Math.ceil(Math.random() * 8) + '.';
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let _str = Math.ceil(Math.random() * 10000000).toString(32);
|
||||
_str = ('00000' + _str).substr(-5, 5);
|
||||
str += _str + '.';
|
||||
}
|
||||
return str.slice(0, -1);
|
||||
}
|
Loading…
Reference in New Issue
Block a user