Coder Social home page Coder Social logo

otiai10 / chomex Goto Github PK

View Code? Open in Web Editor NEW
45.0 6.0 6.0 1.3 MB

Chrome Extension Messaging Routing Kit / Promisify Chrome Messaging / LocalStorage Object Mapper

Home Page: https://www.npmjs.com/package/chomex

License: MIT License

TypeScript 100.00%
chrome-extension localstorage router promise promisify chrome

chomex's Introduction

chomex

Latest Stable Version NPM Downloads Node.js CI codecov

Chrome Extension Messaging Routing Kit.

  • Router to handle onMessage with routes expression
  • Client to Promisify sendMessage
  • Model to access to localStorage like ActiveRecord
  • Types to define data schema of Model

Installation

npm install chomex

Why?

.onMessage like a server routing

๐Ÿ‘Ž Dispatching message inside addListener function makes my code messy and unreeadable.

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  switch(message.action) {
  case "/users/get":
    GetUser.apply(sender, [message, sendResponse]);
    break;
  default:
    NotFound.apply(sender, [message, sendResponse]);
  }
  return true;
});

๐Ÿ‘ chomex.Router makes it more claen and readable.

const router = new chomex.Router();
router.on("/users/get", GetUser);
chrome.runtime.onMessage.addListener(router.listener());

Happy ๐Ÿค—

.sendMessage like a fetch client

๐Ÿ‘Ž Handling the response of sendMessage by callback makes my code messy and unreadable.

chrome.runtime.sendMessage({action:"/users/get",id:123}, (response) => {
  if (response.status == 200) {
    alert("User: " + response.user.name);
  } else {
    console.log("Error:", response);
  }
});

๐Ÿ‘ chomex.Client makes it clean and readable by handling response with Promise.

const client = new chomex.Client(chrome.runtime);
const response = await client.message("/users/get", {id:123});
alert("User: " + response.data.user.name);

Happy ๐Ÿค—

Examples

NOTE: These examples are using async/await on top-level. I believe you are familiar with asyc/await.

background.js as a server

import {Router, Model, Types} from 'chomex';

// Define your model
class User extends Model {
  static schema = {
    name: Types.string.isRequired,
    age:  Types.number,
  }
}

const router = new Router();

// Define your routes
router.on("/users/create", message => {
  const obj = message.user;
  const user = User.new(obj).save();
  return user;
});

router.on("/users/get", message => {
  const userId = message.id;
  const user = User.find(userId);
  if (!user) {
    return {status:404,error:"not found"};
  }
  // You can also return async Promise
  return Promise.resolve(user);
});

// Of course, you can separate files
// in which controller functions are defined.
import {UserDelete} from "./Controllers/Users";
router.on("/users/delete", UserDelete);

// Don't forget to add listener to chrome modules.
chrome.runtime.onMessage.addListener(router.listener());

content_script.js as a client

import {Client} from 'chomex';

const client = new Client(chrome.runtime);

// it sends message to "/users/get" route.
const user = {name: 'otiai10', age: 30};
const response = await client.message('/users/create', {user});
console.log("Created!", response.data);

const {data: user} = await client.message('/users/get', {id: 12345});
console.log("Found:", res.data);

Customize Router for other listeners

You can also customize resolver for routing. It's helpful when you want to make routings for EventListener modules on chrome, such as chrome.notifications.onClicked, chrome.webRequest.onBeforeRequest or so.

// Resolver rule, which resolve given "id" to routing name.
const resolve = (id) => {
  const prefix = id.split(".")[0];
  return {name: prefix};
};

const router = new Router(resolve);
// You see, this controller is invoked when
// a notification with ID "quest.xxxx" is clicked.
router.on('quest', NotificaionOnClickController.Quest);

chrome.notifications.onClicked.addListener(router.listener());

For more information

Reference Projects

Projects using chomex

chomex's People

Contributors

dependabot[bot] avatar otiai10 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

chomex's Issues

Model.useStorage(Storage)

interface Sotrage {
    getItem(key: string): any
    setItem(key: string, value: any)
    removeItem(key: string)
}
Model.useStorage(Storage);

for example

const StoragePushEffort = {
    getItem: (key: string) => {
        fetch(`/data/${key}`).then(res => res.json()).then(json => {
            localStorage.setItem(key, json);
        });
        // it's available for next time
        return localStorage.getItem(key);
    },
    setItem: (key: string, value: string) => {
        localStorage.setItem(key, value);
        fetch(`/data/${key}`, {method:'POST', body: value})
    },
    removeItem: (key: string) => {
         localStorage.removeItem(key);
         fetch(`/data/${key}`, {method:'DELETE'})
    },
}
Model.useStorage(SotragePushEffort);

Model.schema

class User extends Model {
  static schema = {
    name: Model.string.isRequired
  }
}

transformer for Router

given

declare function OnTabUpdatedListenerFunction(tabId: number, info: TabInfo, tab: Tab): void;
chrome.tabs.onUpdated.addListener(fn);

want

const resolver = (tabId: number, info: TabInfo, tab: Tab): Routing => {
    return {name: "foobar"}
}
const transformer = (tabId: number, info: TabInfo, tab: Tab): Message => {
    // ...
}
const router = new Router(resolver, transformer);

so that

router.on("foobar", (message: Message) => {
    // do what I want
});
chrome.tab.onUpdated.addListener(router.listener());

includes

  • Refactor the types of Message and Routing as TypeScript

Model.list

Model.all() == dictionary
Model.list() == []

alias: Model.filter(() => true)

Model.sync

Set up logic to sync

import {Model} from "chomex";

// {{{ It really depends on you!!
const mySyncFunc = (_model) => {
    const dict = JSON.parse(window.localStorage.getItem(_model.name));
    // but I recommend to return Promise object ;)
    return new Promise(resolve => chrome.storage.sync.set({[_model.name]:dict}, resolve));
};
// }}}

Model.setSyncFunc(mySyncFunc);

Sync specific model

class User extends chomex.Model {}

document.querySelector("button#sync").addEventListener("onclick", () => {
    User.sync().then(() => alert("Synced!!"));
});

Types.date

stored as a string, decoded as Date class

on sending message to a runtime on which NO LISTENER added

Error in event handler for (unknown): TypeError: Cannot read property 'status' of undefined
    at TargettedClient../node_modules/chomex/lib/Client/index.js.Client._finally (chrome-extension://mbhfjahbokpmoabjfinclaingicjddpc/dest/js/background.js:246:40)
    at defaultCallback (chrome-extension://mbhfjahbokpmoabjfinclaingicjddpc/dest/js/background.js:223:23)

client raises error

Uncaught Error: Attempting to use a disconnected port object

Problem

It's known that this is caused by trying sendResponse to no-longer-exsting or explicitly-closed runtime port.

Idea

It's better Controller can specify that it doesn't want to sendResponse, explicitly.

for example,

const FooController = (message) => {
    // ...

    // return nothing
    // otherwise, `return;`
};

As is

Controllers with returning nothing are handled as error cases here

if (typeof response == 'undefined') {
    sendResponse(this._formatResponse({
        status: 500, // TODO: should it be 500?
        message: `\`${controllerFunc.name || '(anonymous controller)'}\`: Response should be defined. ex) return true;`,
    }));
    return true;
}

To be

if (typeof response == 'undefined') {
    /*
    sendResponse(this._formatResponse({
        status: 500, // TODO: should it be 500?
        message: `\`${controllerFunc.name || '(anonymous controller)'}\`: Response should be defined. ex) return true;`,
    }));
    */
    return true;
}

`Not Found` for simple GET request

contents_page

import {Client} from 'chomex';
const client = new Client(chrome.runtime, true);
client.message('/foo/bar').catch(err => {
    console.log(err);
});
  • AS IS
    • {status: 404, message: "routing not found for undefined"}
  • SHOULD BE
    • {status: 404, message: "routing not found for /foo/bar"}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.