AsyncIterator.return()
How do I get AsyncIterator.return() to get called onDisconnect and on unsubscribe? I'm looking through the code and it seems this will get bypassed whenever the web socket is disconnected or a subscription is unsubscribed...
I'm trying to publish a presence event to a room topic whenever a user goes offline, using this code I found on google:
const withCancel = (asyncIteratorFn, onCancel) => {
return async (rootValue, args, context, info) => {
const asyncIterator = await asyncIteratorFn(rootValue, args, context, info);
const asyncIteratorReturn = asyncIterator.return;
asyncIterator.return = () => {
onCancel();
return asyncIteratorReturn
? asyncIteratorReturn.call(asyncIterator)
: Promise.resolve({ value: undefined, done: true });
};
return asyncIterator;
};
};
But asyncIterator.return is never called, and so it doesn't invoke my onCancel event to publish.
Would be willing to submit a PR if you can point me in the right direction.
The async iterator is available only on subscribe because lambda is stateless so if you want to handle presence you should use onDisconnect event handler (see subscriptions.onDisconnect and subscriptions.onOperationComplete (unsubscribe), https://github.com/michalkvasnicak/aws-lambda-graphql/tree/master/packages/aws-lambda-graphql#options)
I managed to get this to work actually... even though lambda is stateless it's possible to call the return handler when a client disconnects by iterating through the subscribers returned from unsubscribeAllByConnectionId. Do you want me to submit a PR for your review?
@tbehrsin that'd be nice! Thank you.
By returning subscribers iterated from unsubscribeAllByConnectionId and then in turn connectionManager.unregisterConnection:
case '$disconnect': {
const { onDisconnect } = this.subscriptionOptions || {};
// this event is called eventually by AWS APIGateway v2
// we actualy don't care about a result of this operation because client is already
// disconnected, it is meant only for clean up purposes
// hydrate connection
const connection = await this.connectionManager.hydrateConnection(
event.requestContext.connectionId,
);
if (onDisconnect) {
onDisconnect(connection);
}
const subscribers = await this.connectionManager.unregisterConnection(
connection,
);
const promises = subscribers.map(async (subscriber) => {
const pubSub = new PubSub();
const options = await this.createGraphQLServerOptions(
event,
lambdaContext,
{
connection,
operation: subscriber.operation,
pubSub,
},
);
const iterable = await execute(
Object.assign(Object.assign({}, options), {
connection,
connectionManager: this.connectionManager,
event,
lambdaContext,
operation: subscriber.operation,
pubSub,
registerSubscriptions: false,
subscriptionManager: this.subscriptionManager,
}),
);
if (!isAsyncIterable(iterable)) {
return;
}
const iterator = getAsyncIterator(iterable);
await iterator.return?.();
});
await Promise.all(promises);
return {
body: '',
statusCode: 200,
};
}
I'll submit a PR.