@1hive/connect-core
Version:
Access and interact with Aragon Organizations and their apps.
109 lines • 4.55 kB
JavaScript
import { isSubscriptionHandler, isSubscriptionStart, } from '../types';
import { ErrorInvalidConnector, ErrorInvalidApp } from '../errors';
function normalizeConnectorConfig(connector) {
var _a;
if (Array.isArray(connector)) {
// Returning `{} as Config` shouldn’t be an issue, because the config type
// of an app connector should have all its properties declared as optional.
return [connector[0], (_a = connector[1]) !== null && _a !== void 0 ? _a : {}];
}
if (typeof connector === 'string') {
return [connector, {}];
}
throw new ErrorInvalidConnector('The connector should be passed as a string or an array.');
}
// Check if an app is valid. We are not using instanceof here, because the
// passed app might come from the final app dependency, while @connect-core
// might come from the app connector they are using, with two different
// versions. It also makes it easier to work with linked dependencies, as it
// creates the same kind of issues.
function isApp(app) {
return (typeof app === 'object' &&
'name' in app &&
'address' in app &&
'appId' in app &&
'version' in app);
}
async function getProxiedApp(app, connector, callback) {
app = (await app);
if (!isApp(app)) {
throw new ErrorInvalidApp(`App connector: the passed value doesn’t appear to be an App.`);
}
const { connection } = app.organization;
const { orgConnector } = connection;
// App connector config.
const [connectorName, connectorConfig] = normalizeConnectorConfig(
// Contrary to the main connect() function, app connectors don’t require
// the connector to be passed. In this case, the name of the org
// connector (e.g. `name`) is used instead.
connector || orgConnector.name);
const connectedApp = await callback({
app,
config: connectorConfig,
connector: connectorName,
ipfs: connection.ipfs,
network: orgConnector.network,
orgConnector,
verbose: connection.verbose,
});
const boundMethods = new WeakMap();
const proxiedApp = new Proxy(connectedApp, {
get: (target, key) => {
const isAppProperty = connectedApp[key] === undefined;
// Pick properties from ConnectedApp first, then App
const instance = (isAppProperty ? app : connectedApp);
// Bind methods as they get accessed (so `this` works as expected).
// This is done once for reference comparisons to work as expected.
if (typeof instance[key] === 'function' &&
!boundMethods.has(instance[key])) {
instance[key] = instance[key].bind(instance);
boundMethods.set(instance[key], true);
}
return instance[key];
},
});
// Useful for inspection
proxiedApp._app = app;
proxiedApp._connectedApp = connectedApp;
return proxiedApp;
}
export function createAppConnector(callback) {
return function connect(appOrSubscribe, connector) {
// If app is a SubscriptionStart, assume it is onApp():
// start listening, and return the handler.
if (isSubscriptionStart(appOrSubscribe)) {
return (subscriptionCb) => {
let cancelled = false;
const subscriptionHandler = appOrSubscribe((error, app) => {
if (error !== null) {
subscriptionCb(error);
return;
}
getProxiedApp(app, connector, callback)
.then((app) => {
if (cancelled) {
return;
}
subscriptionCb(null, app);
})
.catch((err) => {
if (!cancelled) {
subscriptionCb(err);
}
});
});
return {
unsubscribe() {
cancelled = true;
subscriptionHandler.unsubscribe();
},
};
};
}
if (isSubscriptionHandler(appOrSubscribe)) {
throw new ErrorInvalidApp(`App connector: the value doesn’t appear to be an App or an app subscription.`);
}
return getProxiedApp(appOrSubscribe, connector, callback);
};
}
//# sourceMappingURL=app-connectors.js.map