Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

apollo-link-webextensions-messaging

Package Overview
Dependencies
Maintainers
8
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

apollo-link-webextensions-messaging - npm Package Compare versions

Comparing version 1.0.0-rc.0 to 1.0.0-rc.1

.vscode/launch.json

3

.eslintrc.js

@@ -12,2 +12,5 @@ module.exports = {

],
rules: {
'@typescript-eslint/no-use-before-define': ['error', { 'variables': false }]
}
};
import { ApolloLink, GraphQLRequest } from 'apollo-link';
import { MessagingPort } from './types';
declare type CreateWebExtensionMessagingExecutorListenerOptions = {
/**
* The Apollo link to execute the received GraphQL request upon.
*/
link: ApolloLink;
};
/**
* Create a [`onConnect`][onConnect] listener that'll execute received
* GraphQL request against the given [Apollo Link `link`][apollo-link].
*
* @param param0
* Operations passed to link will get a `context` that has key `port`
* with the requesting `Port`.
*
* [onConnect]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onConnect
* [apollo-link]: https://www.apollographql.com/docs/link/
*/

@@ -13,5 +22,12 @@ export declare function createWebExtensionMessagingExecutorListener<T extends MessagingPort>({ link }: CreateWebExtensionMessagingExecutorListenerOptions): ((port: T) => void);

/**
* Create an [Apollo Link][apollo-link] that'll transfer the GraphQL request
* over the _Port_ `port`.
*
* [apollo-link]: https://www.apollographql.com/docs/link/
*/
export declare function createWebExtensionsMessagingLink<T extends MessagingPort>(portFn: PortOrPortFn<T>): ApolloLink;
export declare function createWebExtensionsMessagingLink<T extends MessagingPort>(
/**
* The `Port` or a function that should return a `Port`.
*/
portFn: PortOrPortFn<T>): ApolloLink;
export {};

@@ -18,4 +18,10 @@ "use strict";

/**
* Create a [`onConnect`][onConnect] listener that'll execute received
* GraphQL request against the given [Apollo Link `link`][apollo-link].
*
* @param param0
* Operations passed to link will get a `context` that has key `port`
* with the requesting `Port`.
*
* [onConnect]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onConnect
* [apollo-link]: https://www.apollographql.com/docs/link/
*/

@@ -37,7 +43,20 @@ function createWebExtensionMessagingExecutorListener(_a) {

};
// todo: unsubscribe
// todo: unsubscribe on disconnect
var unsubscribe = apollo_link_1.execute(link, request).subscribe({
var operationOnMessageListener_1 = function (message) {
if (rpcs_1.isOperationUnsubscribeRPC(message, operationId_1)) {
close_1();
}
};
port.onMessage.addListener(operationOnMessageListener_1);
var portDisconnectListener_1 = function () {
close_1();
};
port.onDisconnect.addListener(portDisconnectListener_1);
var close_1 = function () {
subscription_1.unsubscribe();
port.onMessage.removeListener(operationOnMessageListener_1);
port.onDisconnect.removeListener(portDisconnectListener_1);
};
var subscription_1 = apollo_link_1.execute(link, request).subscribe({
next: function (res) { return port.postMessage(rpcs_1.operationResultRPC(operationId_1, res)); },
// todo error:
error: function (error) { return port.postMessage(rpcs_1.operationErrorRPC(operationId_1, error)); },
complete: function () { return port.postMessage(rpcs_1.operationCompleteRPC(operationId_1)); },

@@ -51,5 +70,12 @@ });

/**
* Create an [Apollo Link][apollo-link] that'll transfer the GraphQL request
* over the _Port_ `port`.
*
* [apollo-link]: https://www.apollographql.com/docs/link/
*/
function createWebExtensionsMessagingLink(portFn) {
function createWebExtensionsMessagingLink(
/**
* The `Port` or a function that should return a `Port`.
*/
portFn) {
return new apollo_link_1.ApolloLink(function (operation) {

@@ -67,4 +93,6 @@ var port = typeof portFn === 'function' ? portFn(operation) : portFn;

}
if (rpcs_1.isOperationErrorRPC(message, operationId)) {
observer.error(new Error(message.params.errorMessage));
}
};
// todo: on error
port.onMessage.addListener(onMessageListener);

@@ -71,0 +99,0 @@ return function () {

@@ -48,2 +48,15 @@ import { Operation } from 'apollo-link';

}>;
export declare type OperationErrorRPC = RPCNotificationMessage<{
operationId: string;
errorMessage: string;
}>;
export declare const OPERATION_ERROR_METHOD = "operation-error";
export declare const operationErrorRPC: (operationId: string, errorValue: unknown) => RPCNotificationMessage<{
operationId: string;
errorMessage: string;
}>;
export declare const isOperationErrorRPC: (message: Record<string, unknown>, operationId: string) => message is RPCNotificationMessage<{
operationId: string;
errorMessage: string;
}>;
export declare type OperationCompleteRPC = RPCNotificationMessage<{

@@ -66,1 +79,4 @@ operationId: string;

}>;
export declare const isOperationUnsubscribeRPC: (message: Record<string, unknown>, operationId: string) => message is RPCNotificationMessage<{
operationId: string;
}>;

@@ -37,2 +37,22 @@ "use strict";

};
exports.OPERATION_ERROR_METHOD = 'operation-error';
exports.operationErrorRPC = function (operationId, errorValue) {
var errorMessage = '<unknow error>';
if (errorValue instanceof Error) {
errorMessage = errorValue.message;
}
return {
jsonrpc: '2.0',
method: exports.OPERATION_ERROR_METHOD,
params: {
operationId: operationId,
errorMessage: errorMessage,
}
};
};
exports.isOperationErrorRPC = function (message, operationId) {
return exports.isRPCNotificationMessage(message) &&
(message.method === exports.OPERATION_ERROR_METHOD) &&
(isRecord(message.params) && message.params.operationId === operationId);
};
exports.OPERATION_COMPLETE_METHOD = 'operation-complete';

@@ -59,1 +79,6 @@ exports.operationCompleteRPC = function (operationId) { return ({

}); };
exports.isOperationUnsubscribeRPC = function (message, operationId) {
return exports.isRPCNotificationMessage(message) &&
(message.method === exports.OPERATION_UNSUBSCRIBE_METHOD) &&
(isRecord(message.params) && message.params.operationId === operationId);
};

@@ -19,2 +19,5 @@ /// <reference types="node" />

}
/**
* Will create 2 MessagingPort that communicate with one another.
*/
export declare function createMessagingPorts(): [MockPort, MockPort];

12

lib/test-utils/createMessagingPorts.js

@@ -23,2 +23,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/ban-ts-ignore */
var events_1 = require("events");

@@ -56,3 +57,3 @@ var ID = 0;

MockPort.prototype.disconnect = function () {
this.emit('disconnect');
this.emit('do-disconnect');
};

@@ -62,3 +63,5 @@ return MockPort;

exports.MockPort = MockPort;
;
/**
* Will create 2 MessagingPort that communicate with one another.
*/
function createMessagingPorts() {

@@ -81,3 +84,3 @@ var port1 = new MockPort();

});
port1.on('disconnect', function () {
port1.on('do-disconnect', function () {
var args = [];

@@ -89,3 +92,3 @@ for (var _i = 0; _i < arguments.length; _i++) {

});
port2.on('disconnect', function () {
port2.on('do-disconnect', function () {
var args = [];

@@ -100,2 +103,1 @@ for (var _i = 0; _i < arguments.length; _i++) {

exports.createMessagingPorts = createMessagingPorts;
;
import { ApolloLink, Observable, Operation, RequestHandler, NextLink, FetchResult } from 'apollo-link';
export default class MockLink extends ApolloLink {
constructor(handleRequest?: RequestHandler);
constructor(handleRequest: RequestHandler);
request(_operation: Operation, _forward?: NextLink): Observable<FetchResult> | null;
}

@@ -20,3 +20,2 @@ "use strict";

function MockLink(handleRequest) {
if (handleRequest === void 0) { handleRequest = function () { return null; }; }
var _this = _super.call(this) || this;

@@ -26,3 +25,7 @@ _this.request = handleRequest;

}
MockLink.prototype.request = function (_operation, _forward) {
MockLink.prototype.request = function (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_operation,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_forward) {
throw Error('should be overridden');

@@ -29,0 +32,0 @@ };

{
"name": "apollo-link-webextensions-messaging",
"version": "1.0.0-rc.0",
"version": "1.0.0-rc.1",
"description": "",

@@ -12,3 +12,3 @@ "main": "lib/index.js",

"prepare": "npm run build",
"prepublishOnly": "npm test"
"prepublishOnly": "npm test && npm run lint"
},

@@ -19,2 +19,3 @@ "keywords": [],

"devDependencies": {
"@types/delay": "^3.1.0",
"@types/jest": "^25.1.2",

@@ -25,2 +26,4 @@ "@typescript-eslint/eslint-plugin": "^2.20.0",

"apollo-client": "^2.6.8",
"apollo-link-error": "^1.1.12",
"delay": "^4.3.0",
"eslint": "^6.8.0",

@@ -27,0 +30,0 @@ "graphql": "^14.6.0",

# apollo-link-webextensions-messaging
> Apollo link that, in a WebExtension, forwards GraqhQL operations between processes
> Apollo link that, in a WebExtension, forwards GraphQL operations between processes

@@ -57,5 +57,9 @@ [![NPM Version][npm-image]][npm-url]

const client = new ApolloClient({
// can aslo be `createWebExtensionsMessagingLink((operation) => port)`
// can also be `createWebExtensionsMessagingLink((operation) => port)`
link: createWebExtensionsMessagingLink(port)),
cache: new InMemoryCache(),
// from experience, if `queryDeduplication` is true,
// `client.watchQuery` unsubscription will not be
// properly passed down to the `link`
queryDeduplication: false,
});

@@ -62,0 +66,0 @@

@@ -5,3 +5,5 @@ import { ApolloClient } from 'apollo-client';

import gql from 'graphql-tag';
import { Observable, FetchResult } from 'apollo-link';
import { Observable, FetchResult, ApolloLink } from 'apollo-link';
import { onError } from 'apollo-link-error';
import delay from 'delay';

@@ -13,10 +15,10 @@ import { createMessagingPorts, MockPort } from "../test-utils/createMessagingPorts";

import { Operation } from 'apollo-link';
import { ServerError, ServerParseError } from 'apollo-link-http-common';
const observableOfWithDelay = <T>(value: T, delayMs: number = 1000) => new Observable<T>(observer => {
let timer = setTimeout(() => {
const observableOfWithDelay = <T>(value: T, delayMs = 1000): Observable<T> => new Observable<T>(observer => {
const timer = setTimeout(() => {
observer.next(value);
observer.complete();
}, delayMs);
return () => clearTimeout(timer);
return (): void => clearTimeout(timer);
});

@@ -44,6 +46,11 @@

link: createWebExtensionsMessagingLink(requesterPort),
cache: new InMemoryCache()
cache: new InMemoryCache(),
// from experience, if `queryDeduplication` is true,
// `client.watchQuery` unsubscription will not be
// properly passed down to the `link`
queryDeduplication: false,
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
requestHandler = jest.fn((_operation: Operation) => Observable.of({ data: { foo: 'bar' } }));
createWebExtensionMessagingExecutorListener({

@@ -98,12 +105,113 @@ link: new MockLink(requestHandler)

});
it('should not leak listeners request after request', async () => {
const requesterPortMessageListeners = requesterPort.listenerCount('message');
const executorPortMessageListeners = executorPort.listenerCount('message');
const requesterPortDisconnectListeners = requesterPort.listenerCount('disconnect');
const executorPortDisconnectListeners = executorPort.listenerCount('disconnect');
// test streaming result
await client.query({ query });
// test error on executor
expect(requesterPort.listenerCount('message')).toEqual(requesterPortMessageListeners);
expect(executorPort.listenerCount('message')).toEqual(executorPortMessageListeners);
expect(requesterPort.listenerCount('disconnect')).toEqual(requesterPortDisconnectListeners);
expect(executorPort.listenerCount('disconnect')).toEqual(executorPortDisconnectListeners);
});
// test request unsubsribe should unsubscribe on executor
it('should be able to stream a response', () => {
requestHandler.mockImplementation(() => new Observable(observer => {
const timer = setTimeout(() => {
observer.next({ data: { foo: 'bar' } });
}, 500);
// test port disconect should usubscribe on executor
const timer2 = setTimeout((): void => {
observer.next({ data: { foo: 'foo' } });
observer.complete();
}, 1000);
return (): void => {
clearTimeout(timer);
clearTimeout(timer2);
};
}));
// test executor completion should complete on requester
return new Promise((resolve) => {
const fooValues: string[] = [];
client.watchQuery({ query })
.subscribe(res => {
fooValues.push(res.data.foo);
if (fooValues.length == 2) {
expect(fooValues[0]).toEqual('bar');
expect(fooValues[1]).toEqual('foo');
resolve();
}
});
})
});
it('should forward executor\'s errors', async () => {
const onNetworkError = jest.fn<unknown, [Error | ServerError | ServerParseError | undefined]>();
client = new ApolloClient({
link: ApolloLink.from([
onError(({ networkError }) => {
onNetworkError(networkError)
}),
createWebExtensionsMessagingLink(requesterPort),
]),
cache: new InMemoryCache(),
queryDeduplication: false,
});
requestHandler.mockImplementation(() => new Observable(observer => {
observer.error(new Error('An error'));
}));
// execute but ignore errors
try {
await client.query({ query });
} catch(e) {
// do nothing
}
expect(onNetworkError.mock.calls[0][0]).toBeDefined();
if (!onNetworkError.mock.calls[0][0]) throw new Error('no call');
expect(onNetworkError.mock.calls[0][0].message).toEqual('An error');
});
it('should unsubscribe on executor when unsubsribe on request', async () => {
const executorUnsubscribeSpy = jest.fn();
requestHandler.mockImplementation(() => new Observable(() => {
return (): void => {
executorUnsubscribeSpy();
}
}));
const subscription = client.watchQuery({ query })
.subscribe(() => {/* do nothing*/});
await delay(100);
subscription.unsubscribe();
expect(executorUnsubscribeSpy).toHaveBeenCalled();
});
it('should unsubscribe on executor when port is disconnected', async () => {
const executorUnsubscribeSpy = jest.fn();
requestHandler.mockImplementation(() => new Observable(() => {
return (): void => {
executorUnsubscribeSpy();
}
}));
client.watchQuery({ query })
.subscribe(() => {/* do nothing*/ });
await delay(100);
requesterPort.disconnect();
expect(executorUnsubscribeSpy).toHaveBeenCalled();
});
});

@@ -12,2 +12,5 @@ import { ApolloLink, GraphQLRequest, execute, Observable } from 'apollo-link';

isOperationRequestRPC,
operationErrorRPC,
isOperationErrorRPC,
isOperationUnsubscribeRPC,
} from './rpcs';

@@ -17,7 +20,17 @@ import { MessagingPort } from './types';

type CreateWebExtensionMessagingExecutorListenerOptions = {
/**
* The Apollo link to execute the received GraphQL request upon.
*/
link: ApolloLink;
};
/**
*
* @param param0
* Create a [`onConnect`][onConnect] listener that'll execute received
* GraphQL request against the given [Apollo Link `link`][apollo-link].
*
* Operations passed to link will get a `context` that has key `port`
* with the requesting `Port`.
*
* [onConnect]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onConnect
* [apollo-link]: https://www.apollographql.com/docs/link/
*/

@@ -42,7 +55,24 @@ export function createWebExtensionMessagingExecutorListener<T extends MessagingPort>(

};
// todo: unsubscribe
// todo: unsubscribe on disconnect
const unsubscribe = execute(link, request).subscribe({
const operationOnMessageListener = (message: Record<string, unknown>): void => {
if (isOperationUnsubscribeRPC(message, operationId)) {
close();
}
};
port.onMessage.addListener(operationOnMessageListener);
const portDisconnectListener = (): void => {
close();
};
port.onDisconnect.addListener(portDisconnectListener);
const close = (): void => {
subscription.unsubscribe();
port.onMessage.removeListener(operationOnMessageListener);
port.onDisconnect.removeListener(portDisconnectListener);
};
const subscription = execute(link, request).subscribe({
next: res => port.postMessage(operationResultRPC(operationId, res)),
// todo error:
error: error => port.postMessage(operationErrorRPC(operationId, error)),
complete: () => port.postMessage(operationCompleteRPC(operationId)),

@@ -58,5 +88,13 @@ })

/**
*
* Create an [Apollo Link][apollo-link] that'll transfer the GraphQL request
* over the _Port_ `port`.
*
* [apollo-link]: https://www.apollographql.com/docs/link/
*/
export function createWebExtensionsMessagingLink<T extends MessagingPort>(portFn: PortOrPortFn<T>): ApolloLink {
export function createWebExtensionsMessagingLink<T extends MessagingPort>(
/**
* The `Port` or a function that should return a `Port`.
*/
portFn: PortOrPortFn<T>
): ApolloLink {
return new ApolloLink((operation) => {

@@ -73,8 +111,10 @@ const port = typeof portFn === 'function' ? portFn(operation) : portFn;

}
if (isOperationCompleteRPC(message, operationId)){
if (isOperationCompleteRPC(message, operationId)) {
observer.complete();
}
if (isOperationErrorRPC(message, operationId)) {
observer.error(new Error(message.params.errorMessage));
}
};
// todo: on error
port.onMessage.addListener(onMessageListener);

@@ -81,0 +121,0 @@

@@ -60,3 +60,28 @@ import { Operation } from 'apollo-link';

// executor -> requester
export type OperationErrorRPC = RPCNotificationMessage<{
operationId: string;
errorMessage: string;
}>
export const OPERATION_ERROR_METHOD = 'operation-error';
export const operationErrorRPC = (operationId: string, errorValue: unknown): OperationErrorRPC => {
let errorMessage = '<unknow error>';
if (errorValue instanceof Error) {
errorMessage = errorValue.message;
}
return {
jsonrpc: '2.0',
method: OPERATION_ERROR_METHOD,
params: {
operationId,
errorMessage,
}
};
};
export const isOperationErrorRPC = (message: Record<string, unknown>, operationId: string): message is OperationErrorRPC =>
isRPCNotificationMessage(message) &&
(message.method === OPERATION_ERROR_METHOD) &&
(isRecord(message.params) && message.params.operationId === operationId);
// executor -> requester

@@ -89,1 +114,5 @@ export type OperationCompleteRPC = RPCNotificationMessage<{ operationId: string }>;

});
export const isOperationUnsubscribeRPC = (message: Record<string, unknown>, operationId: string): message is OperationUnsubscribeRPC =>
isRPCNotificationMessage(message) &&
(message.method === OPERATION_UNSUBSCRIBE_METHOD) &&
(isRecord(message.params) && message.params.operationId === operationId);

@@ -0,1 +1,2 @@

/* eslint-disable @typescript-eslint/ban-ts-ignore */
import { EventEmitter } from 'events';

@@ -14,3 +15,3 @@ import { MessagingPort, Event, PortDisconnectEvent, PortMessageEvent } from '../types';

addListener(listener: T) {
addListener(listener: T): void {
// @ts-ignore Function and (..args) => void

@@ -20,3 +21,3 @@ this.ee.addListener(this.eventName, listener);

removeListener(listener: T) {
removeListener(listener: T): void {
// @ts-ignore Function and (..args) => void

@@ -41,11 +42,14 @@ this.ee.removeListener(this.eventName, listener);

postMessage(message: Record<string, unknown>) {
postMessage(message: Record<string, unknown>): void {
this.emit('post-message', message);
}
disconnect() {
this.emit('disconnect');
disconnect(): void {
this.emit('do-disconnect');
}
};
}
/**
* Will create 2 MessagingPort that communicate with one another.
*/
export function createMessagingPorts(): [MockPort, MockPort] {

@@ -58,7 +62,7 @@ const port1 = new MockPort();

port1.on('disconnect', (...args) => port2.emit('disconnect', ...args));
port2.on('disconnect', (...args) => port1.emit('disconnect', ...args));
port1.on('do-disconnect', (...args) => port2.emit('disconnect', ...args));
port2.on('do-disconnect', (...args) => port1.emit('disconnect', ...args));
return [port1, port2];
};
}
import { ApolloLink, Observable, Operation, RequestHandler, NextLink, FetchResult } from 'apollo-link';
export default class MockLink extends ApolloLink {
constructor(handleRequest: RequestHandler = () => null) {
constructor(handleRequest: RequestHandler) {
super();

@@ -10,3 +10,5 @@ this.request = handleRequest;

public request(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_operation: Operation,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_forward?: NextLink,

@@ -13,0 +15,0 @@ ): Observable<FetchResult> | null {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc