Commit 78e629e5 authored by Laurent Wouters's avatar Laurent Wouters
Browse files

Cleanup API

parent 257a9f9e0239
......@@ -21,15 +21,17 @@
import {
NO_DATA,
MIME,
LinkedData,
Tag,
Link,
DataSourceLinked,
TabData,
Page
DataSourcePage,
Link,
DataSource
} from "../common/api";
/// <reference path="./fallback.d.ts"/>
let F = require("./register");
import "chrome";
import { linkSync, link } from "fs";
/**
* The data about the tabs
......@@ -42,10 +44,7 @@ let allTabs: { [index: number]: TabData } = {};
*/
function getTabData(id: number): TabData {
if (!allTabs.hasOwnProperty(id)) {
allTabs[id] = {
isActive: false,
linkedData: NO_DATA
};
allTabs[id] = new TabData();
}
return allTabs[id];
}
......@@ -80,7 +79,7 @@ function updateIcon(tabId: number): void {
tabId: tabId
});
chrome.browserAction.setBadgeText({
text: data.linkedData.badge,
text: data.mainSource.name,
tabId: tabId
});
}
......@@ -170,37 +169,23 @@ function getHeader(
return null;
}
/**
* Detect data on links from headers
* @param links The links
*/
function doDetectDataOnLinks(links: Link[]): LinkedData {
for (var j = 0; j != links.length; 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;
}
/**
* Detects linked data in HTTP headers
* @param headers he headers
*/
function detectDataOnLinks(
headers: chrome.webRequest.HttpHeader[]
): LinkedData {
): DataSourceLinked[] {
let linksContent = getHeader(headers, "Link");
if (linksContent == null) return NO_DATA;
var links = parseLinks(linksContent);
return doDetectDataOnLinks(links);
if (linksContent == null) return [];
let links = parseLinks(linksContent);
return links
.filter((link: Link) => {
return link.refersToData();
})
.map((link: Link) => {
return new DataSourceLinked(link);
});
}
/**
......@@ -211,18 +196,13 @@ function detectDataOnLinks(
function detectDataOnContent(
details: chrome.webRequest.WebResponseHeadersDetails,
headers: chrome.webRequest.HttpHeader[]
): LinkedData {
): DataSource {
var contentType = getHeader(headers, "Content-Type");
if (contentType == null) return NO_DATA;
contentType = contentType.split(";")[0];
if (MIME.hasOwnProperty(contentType)) {
return new LinkedData(
MIME[contentType],
contentType,
new Page(details.url, contentType)
);
}
return NO_DATA;
return MIME.hasOwnProperty(contentType)
? new DataSourcePage(details.url, contentType)
: NO_DATA;
}
/**
......@@ -234,12 +214,15 @@ function onHeadersReceived(
): void {
if (details.tabId == -1) return;
let data = getTabData(details.tabId);
if (data.linkedData != NO_DATA)
if (data.mainSource != NO_DATA)
// already detected something
return;
data.linkedData = detectDataOnContent(details, details.responseHeaders);
if (data.linkedData == NO_DATA)
data.linkedData = detectDataOnLinks(details.responseHeaders);
data.sources.push(detectDataOnContent(details, details.responseHeaders));
data.sources = data.sources.concat(
detectDataOnLinks(details.responseHeaders)
);
data.selectMain();
updateIcon(details.tabId);
}
......@@ -251,8 +234,7 @@ function onBeforeNavigate(
details: chrome.webNavigation.WebNavigationFramedCallbackDetails
): void {
if (details.tabId == -1 || details.frameId != 0) return;
let data = getTabData(details.tabId);
data.linkedData = NO_DATA;
getTabData(details.tabId).clear();
updateIcon(details.tabId);
}
......@@ -279,8 +261,16 @@ F.registerNavigations(onBeforeNavigate, onCompleted);
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
let data = getTabData(sender.tab.id);
if (request.reqType == "links") {
if (data.linkedData != NO_DATA) return;
data.linkedData = doDetectDataOnLinks(request.links);
data.sources = data.sources.concat(
request.links
.filter((link: Link) => {
return link.refersToData();
})
.map((link: Link) => {
return new DataSourceLinked(link);
})
);
data.selectMain();
updateIcon(sender.tab.id);
}
});
......@@ -18,6 +18,95 @@
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
/**
* Metadata about a MIME type
*/
export class MimeInfo {
/**
* The MIME type
*/
mime: string;
/**
* The user-readable name
*/
name: string;
/**
* The relative priority of using this MIME type (lower is better)
*/
priority: number;
}
/**
* A tag for a link
*/
export class Tag {
name: string;
value: string;
}
/**
* 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;
}
this.refersToData = this.refersToData.bind(this);
}
/**
* The link's target
*/
url: string;
/**
* The various tags for this link
*/
tags: { [tag: string]: string };
/**
* Determines whether this link is appropriate as a linked data source
*/
public refersToData(): boolean {
return (
this.tags.hasOwnProperty("rel") &&
this.tags.hasOwnProperty("type") &&
this.tags.rel == "alternate" &&
MIME.hasOwnProperty(this.tags.type)
);
}
}
/**
* Map of known MIME types to badge names
*/
export const MIME: { [mime: string]: MimeInfo } = {
"text/n3": { mime: "text/n3", name: "N3", priority: 5 },
"application/n-triples": {
mime: "application/n-triples",
name: "NT",
priority: 6
},
"application/n-quads": {
mime: "application/n-quads",
name: "NQ",
priority: 7
},
"text/turtle": { mime: "text/turtle", name: "TTL", priority: 8 },
"application/trig": { mime: "application/trig", name: "TRIG", priority: 9 },
"application/rdf+xml": {
mime: "application/rdf+xml",
name: "RDF",
priority: 10
},
"application/ld+json": {
mime: "application/ld+json",
name: "LD",
priority: 11
}
};
/**
* A typed raw content
*/
......@@ -32,29 +121,72 @@ export class RawContent {
content: string;
}
/**
* A source of data
*/
export interface DataSource {
/**
* A descriptor of the type of source
*/
sourceType: string;
/**
* The human-readable source of this data
*/
name: string;
/**
* The content's url
*/
url: string;
/**
* MIME type for the content
*/
contentType: string;
/**
* The relative priority of this source (lower is better)
*/
priority: number;
}
/**
* No data source
*/
export class DataSourceNone implements DataSource {
constructor() {
this.sourceType = "DataSourceNone";
this.name = "";
this.url = "";
this.contentType = "";
this.priority = Number.MAX_SAFE_INTEGER;
}
sourceType: string;
name: string;
url: string;
contentType: string;
priority: number;
}
/**
* The data about a page
*/
export class Page {
export class DataSourcePage implements DataSource {
constructor(url: string, contentType: string) {
this.sourceType = "DataSourcePage";
this.name = "*";
this.url = url;
this.contentType = contentType;
this.priority = 1;
}
/**
* The page url
*/
sourceType: string;
name: string;
url: string;
/**
* MIME type for the page's content
*/
contentType: string;
priority: number;
}
/**
* Get a promise to fetch the content of the current document
*/
function doFetchPage(page: Page): Promise<RawContent> {
function doFetchSourcePage(page: DataSourcePage): Promise<RawContent> {
return new Promise(function(resolve, reject) {
resolve({
contentType: page.contentType,
......@@ -63,40 +195,35 @@ function doFetchPage(page: Page): Promise<RawContent> {
});
}
/**
* A tag for a link
*/
export class Tag {
name: string;
value: string;
}
/**
* 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;
}
export class DataSourceLinked implements DataSource {
constructor(link: Link) {
let mime = MIME[link.tags.type];
if (mime == null || mime == undefined) throw "Unrecognized link type";
this.tags = link.tags;
this.sourceType = "DataSourceLinked";
this.name = mime.name;
this.url = link.url;
this.contentType = mime.mime;
this.priority = mime.priority;
}
/**
* The URL of the link
*/
url: string;
/**
* The various tags for this link
*/
tags: { [tag: string]: string };
sourceType: string;
name: string;
url: string;
contentType: string;
priority: number;
}
/**
* Get a promise to fetch content at a URI
*/
function doFetchLink(link: Link): Promise<RawContent> {
function doFetchSourceLink(link: DataSourceLinked): Promise<RawContent> {
return new Promise(function(resolve, reject) {
let xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
......@@ -112,58 +239,53 @@ function doFetchLink(link: Link): Promise<RawContent> {
}
};
xmlHttp.open("GET", link.url, true);
xmlHttp.setRequestHeader("Accept", link.tags.type);
xmlHttp.setRequestHeader("Accept", link.contentType);
xmlHttp.send();
});
}
/**
* Linked data resource
* Fetches this data
*/
export class LinkedData {
constructor(badge: string, syntax: string, target: Link | Page) {
this.badge = badge;
this.syntax = syntax;
this.target = target;
export function fetchSource(source: DataSource): Promise<RawContent> {
if (source == null || source == undefined) {
return new Promise((resolve, reject) => reject("No data"));
} else if (source.sourceType == "DataSourceNone") {
return new Promise((resolve, reject) => reject("No data"));
} else if (source.sourceType == "DataSourcePage") {
return doFetchSourcePage(source as DataSourcePage);
} else if (source.sourceType == "DataSourceLinked") {
return doFetchSourceLink(source as DataSourceLinked);
}
/**
* Display name
*/
badge: string;
/**
* MIME type for the resource
*/
syntax: string;
/**
* Target url to fetch the content
*/
target: Link | Page;
return new Promise((resolve, reject) => reject("Unknown data source"));
}
/**
* Fetches this data
* Compares two data sources
* @param x The first data source
* @param y The second data source
*/
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"));
function compareSources(x: DataSource, y: DataSource): number {
return x.priority - y.priority;
}
/**
* Constant when no data are available
*/
export const NO_DATA = new LinkedData("", null, null);
export const NO_DATA = new DataSourceNone();
/**
* Data about a tab
*/
export class TabData {
constructor() {
this.isActive = false;
this.mainSource = NO_DATA;
this.sources = [];
this.clear = this.clear.bind(this);
this.selectMain = this.selectMain.bind(this);
}
/**
* Whether the user activated the extension for this tab
*/
......@@ -171,18 +293,34 @@ export class TabData {
/**
* The data associated to this tab
*/
linkedData: LinkedData;
}
mainSource: DataSource;
/**
* All the alternative data sources
*/
sources: DataSource[];
/**
* Map of known MIME types to badge names
*/
export const MIME: { [mime: string]: string } = {
"text/n3": "N3",
"application/n-triples": "N3",
"application/n-quads": "N4",
"text/turtle": "TTL",
"application/rdf+xml": "RDF",
"application/ld+json": "JSON",
"application/trig": "TRIG"
};
/**
* Clears this data
*/
clear(): void {
this.mainSource = NO_DATA;
this.sources = [];
}
/**
* Selects the main resource among the provided one
*/
selectMain(): DataSource {
if (this.sources.length == 0) {
this.mainSource = NO_DATA;
} else {
this.sources = this.sources
.filter((source: DataSource) => {
return source != NO_DATA;
})
.sort(compareSources);
this.mainSource = this.sources.length == 0 ? NO_DATA : this.sources[0];
}
return this.mainSource;
}
}
......@@ -18,7 +18,7 @@
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { RawContent, TabData, fetchLinkedData } from "./api";
import { RawContent, TabData, fetchSource } from "./api";
/// <reference path="./rdflib-interface.d.ts"/>
import { RDF } from "rdflib";
......@@ -33,15 +33,15 @@ export function loadRdfStore(tabData: TabData): Promise<any> {
});
}
return new Promise((resolve, reject) => {
let p1 = fetchLinkedData(tabData.linkedData);
let p1 = fetchSource(tabData.mainSource);
p1.then((value: RawContent) => {
let store = $rdf.graph();
try {
$rdf.parse(
value.content,
store,
tabData.linkedData.target.url,
value.contentType
tabData.mainSource.url,
tabData.mainSource.contentType
);
resolve(store);
} catch (err) {
......
......@@ -18,7 +18,7 @@
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { Link, TabData } from "../common/api";
import { TabData, Link, NO_DATA } from "../common/api";
import { loadRdfStore } from "../common/rdf";
import * as App from "./app/index";
import "chrome";
......@@ -52,7 +52,7 @@ chrome.runtime.onMessage.addListener(function(
sender,
sendResponse
) {
let activate = request.isActive && request.linkedData.syntax != "";
let activate = request.isActive && request.mainSource != NO_DATA;
if (activate) onUpdateActivate(request);
else onUpdateDeactivate();
});
......
Supports Markdown
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