@feelback/js
Version:
Client side js integration for Feelback service
219 lines (215 loc) • 6.29 kB
JavaScript
// src/content.ts
function calculateContentKey(keyOrMode, url) {
if (!keyOrMode || keyOrMode === "$auto") {
return url?.toString() || (typeof window !== "undefined" ? window.location.href : "/");
}
if (keyOrMode === "$path") {
if (typeof url === "string")
url = new URL(url);
const loc = url || (typeof window !== "undefined" ? window.location : void 0);
if (!loc)
return "/";
return `${loc.origin}${loc.pathname}`;
}
return keyOrMode;
}
// src/store.ts
var STORAGE_KEY = "fbs-store";
function calculateLocalKey(target) {
return "contentId" in target ? target.contentId : `${target.contentSetId}/${calculateContentKey(target.key)}`;
}
var FeelbackStore = class {
constructor(type) {
this.feelbacks = void 0;
type ??= "local";
if (typeof window === "undefined") {
type = "memory";
}
if (type === "local") {
this.storage = window.localStorage;
} else if (type === "session") {
this.storage = window.sessionStorage;
} else {
const NOOP = () => {
};
this.storage = {
getItem: NOOP,
setItem: NOOP,
removeItem: NOOP,
clear: NOOP,
key: NOOP,
length: 0
};
}
this.load();
}
add(data) {
const key = calculateLocalKey(data.target);
const idx = (this.feelbacks ??= []).findIndex((x) => x.key === key);
if (idx >= 0)
this.feelbacks.splice(idx, 1);
this.feelbacks.push({
key,
value: data.value,
expire: data.expireIn && data.expireIn > 0 ? Math.floor(Date.now() / 1e3) + data.expireIn : void 0,
feelbackId: data.feelbackId,
revokeToken: data.revokable?.token,
revokeExpire: data.revokable?.expireAt && Math.floor(new Date(data.revokable.expireAt).getTime() / 1e3) || void 0
});
this.save();
}
clear() {
this.feelbacks?.splice(0, this.feelbacks.length);
this.storage.removeItem(STORAGE_KEY);
}
remove(key) {
const idx = typeof key === "string" ? this.feelbacks?.findIndex((x) => x.feelbackId === key) : (key = calculateLocalKey(key), this.feelbacks?.findIndex((x) => x.key === key));
if (idx !== void 0 && idx >= 0) {
this.feelbacks.splice(idx, 1);
this.save();
}
}
getValue(target) {
return this.getFeelback(target)?.value;
}
isRevokable(target) {
return !!this.getRevocable(target);
}
getRevocable(key) {
const entry = this.getFeelback(key);
if (!entry)
return;
if (!entry.revokeToken)
return;
if (entry.revokeExpire && entry.revokeExpire < Date.now() / 1e3)
return;
return { feelbackId: entry.feelbackId, revokeToken: entry.revokeToken };
}
load(refresh) {
if (this.feelbacks && !refresh) {
return;
}
let entries;
try {
entries = JSON.parse(this.storage.getItem(STORAGE_KEY)) || [];
} catch {
entries = [];
}
this.feelbacks = entries.filter((x) => !x.expire || x.expire > Date.now() / 1e3);
if (entries.length !== this.feelbacks.length) {
this.save();
}
}
save() {
try {
this.storage.setItem(STORAGE_KEY, JSON.stringify(this.feelbacks));
} catch {
}
}
getFeelback(key) {
const item = typeof key === "string" ? this.feelbacks?.find((x) => x.feelbackId === key) : (key = calculateLocalKey(key), this.feelbacks?.find((x) => x.key === key));
if (item && item.expire && item.expire < Date.now() / 1e3) {
this.remove(item.feelbackId);
return;
}
return item;
}
};
var store;
var storeType;
function getFeelbackStore(type) {
type ??= storeType || "local";
if (store && storeType === type) {
return store;
}
storeType = type;
return store = new FeelbackStore(type);
}
function getLocalFeelbackValue(target, store2) {
return getFeelbackStore(store2).getValue(target);
}
// src/http.ts
async function get(url, ...params) {
if (params.length > 0) {
url = `${url}?$p=${JSON.stringify(params)}`;
}
return await getResponse(fetch(url, {
method: "GET"
}));
}
async function post(url, ...params) {
return await getResponse(fetch(url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(params)
}));
}
async function getResponse(response) {
response = await response;
if (response.status >= 400) {
throw new Error("[feelback] API error");
}
if (response.status === 204) {
return;
}
return await response.json();
}
var http_default = {
get,
post
};
// src/feelback.ts
var ENDPOINT = "https://api.feelback.dev/v0";
async function sendFeelback(params) {
const {
endpoint = ENDPOINT,
store: store2 = "local",
revokable = true,
value,
metadata,
expireIn = 3600
// 1h
} = params;
const target = "contentId" in params ? { contentId: params.contentId } : { contentSetId: params.contentSetId, key: calculateContentKey(params.key) };
const storage = store2 && store2 !== "none" && getFeelbackStore(store2) || void 0;
const revoke = revokable && storage?.getRevocable(target) || void 0;
const result = revoke ? await http_default.post(`${endpoint}/feelbacks/edit`, { ...revoke, value }) : await http_default.post(`${endpoint}/feelbacks/create`, { ...target, value, context: metadata, revokable });
storage?.add({
...result,
target,
value,
expireIn
});
}
async function removeFeelback(options) {
const {
endpoint = ENDPOINT,
feelbackId
} = options;
let revokeToken = options.revokeToken;
const storage = getFeelbackStore();
if (!revokeToken) {
const revoke = storage.getRevocable(feelbackId);
if (!revoke) {
throw new Error("Cannot revoke feelback");
}
revokeToken = revoke.revokeToken;
}
await http_default.post(`${endpoint}/feelbacks/remove`, { feelbackId, revokeToken });
storage.remove(feelbackId);
}
async function getFeelbackAggregates(params) {
const {
endpoint = ENDPOINT
} = params;
const target = "contentId" in params ? params.contentId : { contentSetId: params.contentSetId, key: calculateContentKey(params.key) };
return await http_default.get(`${endpoint}/feelbacks/getAggregates`, target);
}
export {
calculateContentKey,
getFeelbackAggregates,
getFeelbackStore,
getLocalFeelbackValue,
removeFeelback,
sendFeelback
};