UNPKG

@solana/rpc-subscriptions-spec

Version:

A generic implementation of JSON RPC Subscriptions using proxies

255 lines (249 loc) • 8.33 kB
'use strict'; var errors = require('@solana/errors'); var subscribable = require('@solana/subscribable'); var promises = require('@solana/promises'); var rpcSpecTypes = require('@solana/rpc-spec-types'); // src/rpc-subscriptions.ts function createSubscriptionRpc(rpcConfig) { return new Proxy(rpcConfig.api, { defineProperty() { return false; }, deleteProperty() { return false; }, get(target, p, receiver) { return function(...rawParams) { const notificationName = p.toString(); const createRpcSubscriptionPlan = Reflect.get(target, notificationName, receiver); if (!createRpcSubscriptionPlan) { throw new errors.SolanaError(errors.SOLANA_ERROR__RPC_SUBSCRIPTIONS__CANNOT_CREATE_SUBSCRIPTION_PLAN, { notificationName }); } const subscriptionPlan = createRpcSubscriptionPlan(...rawParams); return createPendingRpcSubscription(rpcConfig.transport, subscriptionPlan); }; } }); } function createPendingRpcSubscription(transport, subscriptionsPlan) { return { async subscribe({ abortSignal }) { const notificationsDataPublisher = await transport({ signal: abortSignal, ...subscriptionsPlan }); return subscribable.createAsyncIterableFromDataPublisher({ abortSignal, dataChannelName: "notification", dataPublisher: notificationsDataPublisher, errorChannelName: "error" }); } }; } // src/rpc-subscriptions-api.ts function createRpcSubscriptionsApi(config) { return new Proxy({}, { defineProperty() { return false; }, deleteProperty() { return false; }, get(...args) { const [_, p] = args; const methodName = p.toString(); return function(...params) { const rawRequest = { methodName, params }; const request = config.requestTransformer ? config.requestTransformer(rawRequest) : rawRequest; return { execute(planConfig) { return config.planExecutor({ ...planConfig, request }); }, request }; }; } }); } // src/rpc-subscriptions-channel.ts function transformChannelInboundMessages(channel, transform) { return Object.freeze({ ...channel, on(type, subscriber, options) { if (type !== "message") { return channel.on( type, subscriber, options ); } return channel.on( "message", (message) => subscriber(transform(message)), options ); } }); } function transformChannelOutboundMessages(channel, transform) { return Object.freeze({ ...channel, send: (message) => channel.send(transform(message)) }); } // ../event-target-impl/dist/index.browser.mjs var o = globalThis.AbortController; var subscriberCountBySubscriptionIdByChannel = /* @__PURE__ */ new WeakMap(); function decrementSubscriberCountAndReturnNewCount(channel, subscriptionId) { return augmentSubscriberCountAndReturnNewCount(-1, channel, subscriptionId); } function incrementSubscriberCount(channel, subscriptionId) { augmentSubscriberCountAndReturnNewCount(1, channel, subscriptionId); } function augmentSubscriberCountAndReturnNewCount(amount, channel, subscriptionId) { if (subscriptionId === void 0) { return; } let subscriberCountBySubscriptionId = subscriberCountBySubscriptionIdByChannel.get(channel); if (!subscriberCountBySubscriptionId && amount > 0) { subscriberCountBySubscriptionIdByChannel.set( channel, subscriberCountBySubscriptionId = { [subscriptionId]: 0 } ); } if (subscriberCountBySubscriptionId?.[subscriptionId] !== void 0) { return subscriberCountBySubscriptionId[subscriptionId] = amount + subscriberCountBySubscriptionId[subscriptionId]; } } var cache = /* @__PURE__ */ new WeakMap(); function getMemoizedDemultiplexedNotificationPublisherFromChannelAndResponseTransformer(channel, subscribeRequest, responseTransformer) { let publisherByResponseTransformer = cache.get(channel); if (!publisherByResponseTransformer) { cache.set(channel, publisherByResponseTransformer = /* @__PURE__ */ new WeakMap()); } const responseTransformerKey = responseTransformer ?? channel; let publisher = publisherByResponseTransformer.get(responseTransformerKey); if (!publisher) { publisherByResponseTransformer.set( responseTransformerKey, publisher = subscribable.demultiplexDataPublisher(channel, "message", (rawMessage) => { const message = rawMessage; if (!("method" in message)) { return; } const transformedNotification = responseTransformer ? responseTransformer(message.params.result, subscribeRequest) : message.params.result; return [`notification:${message.params.subscription}`, transformedNotification]; }) ); } return publisher; } async function executeRpcPubSubSubscriptionPlan({ channel, responseTransformer, signal, subscribeRequest, unsubscribeMethodName }) { let subscriptionId; channel.on( "error", () => { subscriptionId = void 0; subscriberCountBySubscriptionIdByChannel.delete(channel); }, { signal } ); const abortPromise = new Promise((_, reject) => { function handleAbort() { if (decrementSubscriberCountAndReturnNewCount(channel, subscriptionId) === 0) { const unsubscribePayload = rpcSpecTypes.createRpcMessage({ methodName: unsubscribeMethodName, params: [subscriptionId] }); subscriptionId = void 0; channel.send(unsubscribePayload).catch(() => { }); } reject(this.reason); } if (signal.aborted) { handleAbort.call(signal); } else { signal.addEventListener("abort", handleAbort); } }); const subscribePayload = rpcSpecTypes.createRpcMessage(subscribeRequest); await channel.send(subscribePayload); const subscriptionIdPromise = new Promise((resolve, reject) => { const abortController = new o(); signal.addEventListener("abort", abortController.abort.bind(abortController)); const options = { signal: abortController.signal }; channel.on( "error", (err) => { abortController.abort(); reject(err); }, options ); channel.on( "message", (message) => { if (message && typeof message === "object" && "id" in message && message.id === subscribePayload.id) { abortController.abort(); if ("error" in message) { reject(errors.getSolanaErrorFromJsonRpcError(message.error)); } else { resolve(message.result); } } }, options ); }); subscriptionId = await promises.safeRace([abortPromise, subscriptionIdPromise]); if (subscriptionId == null) { throw new errors.SolanaError(errors.SOLANA_ERROR__RPC_SUBSCRIPTIONS__EXPECTED_SERVER_SUBSCRIPTION_ID); } incrementSubscriberCount(channel, subscriptionId); const notificationPublisher = getMemoizedDemultiplexedNotificationPublisherFromChannelAndResponseTransformer( channel, subscribeRequest, responseTransformer ); const notificationKey = `notification:${subscriptionId}`; return { on(type, listener, options) { switch (type) { case "notification": return notificationPublisher.on( notificationKey, listener, options ); case "error": return channel.on( "error", listener, options ); default: throw new errors.SolanaError(errors.SOLANA_ERROR__INVARIANT_VIOLATION__DATA_PUBLISHER_CHANNEL_UNIMPLEMENTED, { channelName: type, supportedChannelNames: ["notification", "error"] }); } } }; } exports.createRpcSubscriptionsApi = createRpcSubscriptionsApi; exports.createSubscriptionRpc = createSubscriptionRpc; exports.executeRpcPubSubSubscriptionPlan = executeRpcPubSubSubscriptionPlan; exports.transformChannelInboundMessages = transformChannelInboundMessages; exports.transformChannelOutboundMessages = transformChannelOutboundMessages; //# sourceMappingURL=index.browser.cjs.map //# sourceMappingURL=index.browser.cjs.map