Commit bf5e1a4d authored by Laurent Wouters's avatar Laurent Wouters
Browse files

Transform into a web extension

parent a1a97f319ada
......@@ -3,7 +3,7 @@
"version": "0.1.0",
"description": "A browser for the web of data.",
"scripts": {
"build": "bsb -clean-world && bsb -make-world && webpack",
"build": "webpack",
"start": "bsb -make-world -w",
"clean": "bsb -clean-world",
"test": "echo \"Error: no test specified\" && exit 1",
......
......@@ -18,25 +18,19 @@
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { NO_DATA, MIME, LinkedData, Tag, Link } from "../common/api";
import {
NO_DATA,
MIME,
LinkedData,
Tag,
Link,
TabData,
Page
} from "../common/api";
/// <reference path="./fallback.d.ts"/>
let F = require("./register");
import "chrome";
/**
* Data about a tab
*/
class TabData {
/**
* Whether the user activated the extension for this tab
*/
isActive: boolean;
/**
* The data associated to this tab
*/
linkedData: LinkedData;
}
/**
* The data about the tabs
*/
......@@ -152,11 +146,8 @@ function parseLinks(content: string): Link[] {
);
let match;
while ((match = regexp.exec(content)) !== null) {
var link: Link = { url: match[1] };
var tags = parseLinkTags(match[0]);
for (var i = 0; i != tags.length; i++) {
link[tags[i].name] = tags[i].value;
}
let tags = parseLinkTags(match[0]);
let link = new Link(match[1], tags);
links.push(link);
}
return links;
......@@ -185,12 +176,15 @@ function getHeader(
*/
function doDetectDataOnLinks(links: Link[]): LinkedData {
for (var j = 0; j != links.length; j++) {
if (links[j].rel == "alternate" && MIME.hasOwnProperty(links[j].type)) {
return {
badge: MIME[links[j].type],
syntax: links[j].type,
target: links[j]
};
if (
links[j].tags.rel == "alternate" &&
MIME.hasOwnProperty(links[j].tags.type)
) {
return new LinkedData(
MIME[links[j].tags.type],
links[j].tags.type,
links[j]
);
}
}
return NO_DATA;
......@@ -211,22 +205,22 @@ function detectDataOnLinks(
/**
* Detects linked data in page content
* @param tabId The tab identifier
* @param details The details
* @param headers The HTTP headers
*/
function detectDataOnContent(
tabId: number,
details: chrome.webRequest.WebResponseHeadersDetails,
headers: chrome.webRequest.HttpHeader[]
): LinkedData {
var contentType = getHeader(headers, "Content-Type");
if (contentType == null) return NO_DATA;
contentType = contentType.split(";")[0];
if (MIME.hasOwnProperty(contentType)) {
return {
badge: MIME[contentType],
syntax: contentType,
target: "content"
};
return new LinkedData(
MIME[contentType],
contentType,
new Page(details.url, contentType)
);
}
return NO_DATA;
}
......@@ -243,7 +237,7 @@ function onHeadersReceived(
if (data.linkedData != NO_DATA)
// already detected something
return;
data.linkedData = detectDataOnContent(details.tabId, details.responseHeaders);
data.linkedData = detectDataOnContent(details, details.responseHeaders);
if (data.linkedData == NO_DATA)
data.linkedData = detectDataOnLinks(details.responseHeaders);
updateIcon(details.tabId);
......
import { link } from "fs";
/*******************************************************************************
* Copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
......@@ -18,6 +20,51 @@
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
/**
* A typed raw content
*/
export class RawContent {
/**
* The MIME type
*/
contentType: string;
/**
* The raw content
*/
content: string;
}
/**
* The data about a page
*/
export class Page {
constructor(url: string, contentType: string) {
this.url = url;
this.contentType = contentType;
}
/**
* The page url
*/
url: string;
/**
* MIME type for the page's content
*/
contentType: string;
}
/**
* Get a promise to fetch the content of the current document
*/
function doFetchPage(page: Page): Promise<RawContent> {
return new Promise(function(resolve, reject) {
resolve({
contentType: page.contentType,
content: document.body.innerText
});
});
}
/**
* A tag for a link
*/
......@@ -30,14 +77,58 @@ export class Tag {
* A link for a related resource
*/
export class Link {
constructor(url: string, tags: Tag[]) {
this.url = url;
this.tags = {};
for (var i = 0; i != tags.length; i++) {
this.tags[tags[i].name] = tags[i].value;
}
}
/**
* The URL of the link
*/
url: string;
[tag: string]: string;
/**
* The various tags for this link
*/
tags: { [tag: string]: string };
}
/**
* Get a promise to fetch content at a URI
*/
function doFetchLink(link: Link): Promise<RawContent> {
return new Promise(function(resolve, reject) {
let xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4) {
if (xmlHttp.status != 200) {
reject("HTTP error: " + xmlHttp.status);
}
let ct = xmlHttp.getResponseHeader("Content-Type");
resolve({
contentType: ct,
content: xmlHttp.responseText
});
}
};
xmlHttp.open("GET", link.url, true);
xmlHttp.setRequestHeader("Accept", link.tags.type);
xmlHttp.send();
});
}
/**
* Linked data resource
*/
export class LinkedData {
constructor(badge: string, syntax: string, target: Link | Page) {
this.badge = badge;
this.syntax = syntax;
this.target = target;
}
/**
* Display name
*/
......@@ -49,31 +140,41 @@ export class LinkedData {
/**
* Target url to fetch the content
*/
target: Link | string;
target: Link | Page;
}
/**
* A typed raw content
* Fetches this data
*/
export class RawContent {
/**
* The MIME type
*/
contentType: string;
/**
* The raw content
*/
content: string;
export function fetchLinkedData(linkedData: LinkedData): Promise<RawContent> {
if (linkedData.target == null) {
return new Promise((resolve, reject) => reject("No data"));
} else if (linkedData.target.hasOwnProperty("contentType")) {
return doFetchPage(linkedData.target as Page);
} else if (linkedData.target.hasOwnProperty("tags")) {
return doFetchLink(linkedData.target as Link);
}
return new Promise((resolve, reject) => reject("Invalid data"));
}
/**
* Constant when no data are available
*/
export const NO_DATA: LinkedData = {
badge: "",
syntax: null,
target: null
};
export const NO_DATA = new LinkedData("", null, null);
/**
* Data about a tab
*/
export class TabData {
/**
* Whether the user activated the extension for this tab
*/
isActive: boolean;
/**
* The data associated to this tab
*/
linkedData: LinkedData;
}
/**
* Map of known MIME types to badge names
......
/*******************************************************************************
* Copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
*
* This file is part of CubicWeb.
*
* CubicWeb is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 2.1 of the License, or (at your option)
* any later version.
*
* CubicWeb is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { RawContent, TabData, fetchLinkedData } from "./api";
/// <reference path="./rdflib-interface.d.ts"/>
let $rdf = require("rdflib");
export const RDF = $rdf;
export function loadRdfStore(tabData: TabData): Promise<any> {
if (!tabData.isActive) {
return new Promise((resolve, reject) => {
reject("no data");
});
}
return new Promise((resolve, reject) => {
let p1 = fetchLinkedData(tabData.linkedData);
p1.then((value: RawContent) => {
let store = $rdf.graph();
try {
$rdf.parse(
value.content,
store,
tabData.linkedData.target.url,
value.contentType
);
resolve(store);
} catch (err) {
reject(err);
}
}).catch(reason => {
reject(reason);
});
});
}
/*******************************************************************************
* Copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
*
* This file is part of CubicWeb.
*
* CubicWeb is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 2.1 of the License, or (at your option)
* any later version.
*
* CubicWeb is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
/**
* RDF stubs for Typescript
*/
declare module "rdflib" {
export interface Store {}
let $rdf: any;
export default $rdf;
}
......@@ -18,48 +18,11 @@
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { RawContent, Link, LinkedData } from "../common/api";
import { Link, TabData, Tag } from "../common/api";
import { loadRdfStore } from "../common/rdf";
import * as App from "./app/index";
import "chrome";
/**
* Get a promise to fetch the content of the current document
* @param contentType The MIME type for the page's content
*/
function doFetchContent(contentType: string): Promise<RawContent> {
return new Promise(function(resolve, reject) {
resolve({
contentType: contentType,
content: document.body.innerText
});
});
}
/**
* Get a promise to fetch content at a URI
* @param link The link to use
*/
function doFetchLink(link: Link): Promise<RawContent> {
return new Promise(function(resolve, reject) {
let xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4) {
if (xmlHttp.status != 200) {
reject("HTTP error: " + xmlHttp.status);
}
let ct = xmlHttp.getResponseHeader("Content-Type");
resolve({
contentType: ct,
content: xmlHttp.responseText
});
}
};
xmlHttp.open("GET", link.url, true);
xmlHttp.setRequestHeader("Accept", link.type);
xmlHttp.send();
});
}
/**
* Reacts to the deactivation
*/
......@@ -69,31 +32,28 @@ function onUpdateDeactivate(): void {
/**
* Reacts to the activation
* @param linkedData The linked data to use
* @param tabData The current data
*/
function onUpdateActivate(linkedData: LinkedData): void {
function onUpdateActivate(tabData: TabData): void {
let app = App.injectApplication();
let fetch = null;
if (linkedData.target == "content") {
fetch = doFetchContent(linkedData.syntax);
} else {
fetch = doFetchLink(linkedData.target as Link);
}
fetch
.then(function(result) {
app.onContent(result);
})
.catch(function(reason) {
app.onError(reason);
});
let p = loadRdfStore(tabData);
p.then((store: any) => {
app.onContent(store);
}).catch(reason => {
app.onError(reason.toString());
});
}
/**
* Listen to messages from the background
*/
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
chrome.runtime.onMessage.addListener(function(
request: TabData,
sender,
sendResponse
) {
let activate = request.isActive && request.linkedData.syntax != "";
if (activate) onUpdateActivate(request.linkedData);
if (activate) onUpdateActivate(request);
else onUpdateDeactivate();
});
......@@ -115,11 +75,12 @@ function sendHead(): void {
let type = child.getAttribute("type");
let href = child.getAttribute("href");
if (rel == "meta") {
links.push({
rel: "alternate",
type: type,
url: href
});
links.push(
new Link(href, [
{ name: "type", value: type },
{ name: "rel", value: "alternate" }
])
);
}
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment