@joyid/common
Version:
Shared code for JoyID SDK
643 lines (628 loc) • 18.7 kB
JavaScript
// src/utils/errors.ts
var DappErrorName = /* @__PURE__ */ ((DappErrorName2) => {
DappErrorName2["DecodeError"] = "Decode Error";
DappErrorName2["InvalidParams"] = "Invalid Params";
DappErrorName2["UserRejected"] = "User Rejected";
DappErrorName2["NotAllowed"] = "Not Allowed";
return DappErrorName2;
})(DappErrorName || {});
var DappError = class extends Error {
constructor(message, name = "Invalid Params" /* InvalidParams */, rawError = void 0) {
super(message);
this.name = message === "User Rejected" /* UserRejected */ ? message : name;
this.rawError = rawError;
}
};
// src/utils/qss.ts
function encode(obj, pfx) {
let k;
let i;
let tmp;
let str = "";
for (k in obj) {
if ((tmp = obj[k]) !== void 0) {
if (Array.isArray(tmp)) {
for (i = 0; i < tmp.length; i++) {
str && (str += "&");
str += `${encodeURIComponent(k)}=${encodeURIComponent(tmp[i])}`;
}
} else {
str && (str += "&");
str += `${encodeURIComponent(k)}=${encodeURIComponent(tmp)}`;
}
}
}
return (pfx || "") + str;
}
function toValue(mix) {
if (!mix)
return "";
const str = decodeURIComponent(mix);
if (str === "false")
return false;
if (str === "true")
return true;
return +str * 0 === 0 && `${+str}` === str ? +str : str;
}
function decode(str) {
let tmp;
let k;
const out = {};
const arr = str.split("&");
while (tmp = arr.shift()) {
tmp = tmp.split("=");
k = tmp.shift();
if (out[k] !== void 0) {
out[k] = [].concat(out[k], toValue(tmp.shift()));
} else {
out[k] = toValue(tmp.shift());
}
}
return out;
}
// src/utils/search-params.ts
function parseSearchWith(parser) {
return (searchStr) => {
if (searchStr.substring(0, 1) === "?") {
searchStr = searchStr.substring(1);
}
const query = decode(searchStr);
for (const key in query) {
const value = query[key];
if (typeof value === "string") {
try {
query[key] = parser(value);
} catch (err) {
}
}
}
if (Object.keys(query).length === 0) {
throw new DappError("Invalid request", "Invalid Params" /* InvalidParams */);
}
return query;
};
}
function stringifySearchWith(stringify, parser) {
function stringifyValue(val) {
if (typeof val === "object" && val !== null) {
try {
return stringify(val);
} catch (err) {
}
} else if (typeof val === "string" && typeof parser === "function") {
try {
parser(val);
return stringify(val);
} catch (err) {
}
}
return val;
}
return (search) => {
search = { ...search };
if (search) {
Object.keys(search).forEach((key) => {
const val = search[key];
if (typeof val === "undefined" || val === void 0) {
delete search[key];
} else {
search[key] = stringifyValue(val);
}
});
}
const searchStr = encode(search).toString();
return searchStr ? `?${searchStr}` : "";
};
}
var decodeSearch = parseSearchWith(JSON.parse);
var encodeSearch = stringifySearchWith(JSON.stringify, JSON.parse);
// src/utils/func.ts
var safeExec = (fn) => {
try {
return fn();
} catch (error) {
return null;
}
};
// src/utils/buffer.ts
function base64URLStringToBuffer(base64URLString) {
const base64 = base64URLString.replace(/-/g, "+").replace(/_/g, "/");
const padLength = (4 - base64.length % 4) % 4;
const padded = base64.padEnd(base64.length + padLength, "=");
const binary = atob(padded);
const buffer = new ArrayBuffer(binary.length);
const bytes = new Uint8Array(buffer);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return buffer;
}
function bufferToBase64URLString(buffer) {
const bytes = new Uint8Array(buffer);
let str = "";
for (let i = 0; i < bytes.length; i++) {
const charCode = bytes[i];
if (charCode != null) {
str += String.fromCharCode(charCode);
}
}
const base64String = btoa(str);
return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
function hexToArrayBuffer(input) {
const view = new Uint8Array(input.length / 2);
for (let i = 0; i < input.length; i += 2) {
view[i / 2] = Number.parseInt(input.substring(i, i + 2), 16);
}
return view.buffer;
}
function bufferToHex(buffer) {
return [...new Uint8Array(buffer)].map((b) => b.toString(16).padStart(2, "0")).join("");
}
function appendBuffer(buffer1, buffer2) {
const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
}
function bufferToUTF8String(value) {
return new TextDecoder("utf-8").decode(value);
}
function utf8StringToBuffer(value) {
return new TextEncoder().encode(value);
}
function hexToUTF8String(value) {
return bufferToUTF8String(hexToArrayBuffer(value));
}
function remove0x(hex) {
return hex.startsWith("0x") ? hex.slice(2) : hex;
}
function append0x(hex) {
return hex.startsWith("0x") ? hex : `0x${hex}`;
}
function hexToString(hex) {
let str = "";
for (let i = 0; i < hex.length; i += 2)
str += String.fromCharCode(Number.parseInt(hex.substr(i, 2), 16));
return str;
}
function base64urlToHex(s) {
return bufferToHex(base64URLStringToBuffer(s));
}
// src/utils/browser.ts
function isStandaloneBrowser() {
return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone;
}
// src/types/dapp.ts
var SigningAlg = /* @__PURE__ */ ((SigningAlg2) => {
SigningAlg2[SigningAlg2["RS256"] = -257] = "RS256";
SigningAlg2[SigningAlg2["ES256"] = -7] = "ES256";
return SigningAlg2;
})(SigningAlg || {});
var DappRequestType = /* @__PURE__ */ ((DappRequestType2) => {
DappRequestType2["Auth"] = "Auth";
DappRequestType2["SignMessage"] = "SignMessage";
DappRequestType2["SignEvm"] = "SignEvm";
DappRequestType2["SignPsbt"] = "SignPsbt";
DappRequestType2["BatchSignPsbt"] = "BatchSignPsbt";
DappRequestType2["SignCkbTx"] = "SignCkbTx";
DappRequestType2["SignCotaNFT"] = "SignCotaNFT";
DappRequestType2["SignCkbRawTx"] = "SignCkbRawTx";
DappRequestType2["SignNostrEvent"] = "SignNostrEvent";
DappRequestType2["EncryptNostrMessage"] = "EncryptNostrMessage";
DappRequestType2["EvmWeb2Login"] = "EvmWeb2Login";
DappRequestType2["DecryptNostrMessage"] = "DecryptNostrMessage";
DappRequestType2["AuthMiniApp"] = "AuthMiniApp";
DappRequestType2["SignMiniAppMessage"] = "SignMiniAppMessage";
DappRequestType2["SignMiniAppEvm"] = "SignMiniAppEvm";
return DappRequestType2;
})(DappRequestType || {});
var DappCommunicationType = /* @__PURE__ */ ((DappCommunicationType2) => {
DappCommunicationType2["Popup"] = "popup";
DappCommunicationType2["Redirect"] = "redirect";
return DappCommunicationType2;
})(DappCommunicationType || {});
var SESSION_KEY_VER = "00";
// src/types/nostr.ts
var EventKind = /* @__PURE__ */ ((EventKind2) => {
EventKind2[EventKind2["Metadata"] = 0] = "Metadata";
EventKind2[EventKind2["Text"] = 1] = "Text";
EventKind2[EventKind2["RecommendRelay"] = 2] = "RecommendRelay";
EventKind2[EventKind2["Contacts"] = 3] = "Contacts";
EventKind2[EventKind2["EncryptedDirectMessage"] = 4] = "EncryptedDirectMessage";
EventKind2[EventKind2["EventDeletion"] = 5] = "EventDeletion";
EventKind2[EventKind2["Repost"] = 6] = "Repost";
EventKind2[EventKind2["Reaction"] = 7] = "Reaction";
EventKind2[EventKind2["BadgeAward"] = 8] = "BadgeAward";
EventKind2[EventKind2["ChannelCreation"] = 40] = "ChannelCreation";
EventKind2[EventKind2["ChannelMetadata"] = 41] = "ChannelMetadata";
EventKind2[EventKind2["ChannelMessage"] = 42] = "ChannelMessage";
EventKind2[EventKind2["ChannelHideMessage"] = 43] = "ChannelHideMessage";
EventKind2[EventKind2["ChannelMuteUser"] = 44] = "ChannelMuteUser";
EventKind2[EventKind2["Blank"] = 255] = "Blank";
EventKind2[EventKind2["Report"] = 1984] = "Report";
EventKind2[EventKind2["ZapRequest"] = 9734] = "ZapRequest";
EventKind2[EventKind2["Zap"] = 9735] = "Zap";
EventKind2[EventKind2["RelayList"] = 10002] = "RelayList";
EventKind2[EventKind2["ClientAuth"] = 22242] = "ClientAuth";
EventKind2[EventKind2["HttpAuth"] = 27235] = "HttpAuth";
EventKind2[EventKind2["ProfileBadge"] = 30008] = "ProfileBadge";
EventKind2[EventKind2["BadgeDefinition"] = 30009] = "BadgeDefinition";
EventKind2[EventKind2["Article"] = 30023] = "Article";
return EventKind2;
})(EventKind || {});
// src/sdk/config.ts
var internalConfig = {
joyidAppURL: "https://testnet.joyid.dev"
};
var initConfig = (config) => {
Object.assign(internalConfig, config);
return internalConfig;
};
var getConfig = () => internalConfig;
// src/sdk/errors.ts
var GenericError = class _GenericError extends Error {
constructor(error, error_description) {
super(error_description);
this.error = error;
this.error_description = error_description;
Object.setPrototypeOf(this, _GenericError.prototype);
}
};
var TimeoutError = class _TimeoutError extends GenericError {
constructor() {
super("timeout", "Timeout");
Object.setPrototypeOf(this, _TimeoutError.prototype);
}
};
var PopupTimeoutError = class _PopupTimeoutError extends TimeoutError {
constructor(popup) {
super();
this.popup = popup;
Object.setPrototypeOf(this, _PopupTimeoutError.prototype);
}
};
var PopupCancelledError = class _PopupCancelledError extends GenericError {
constructor(popup) {
super("cancelled", "Popup closed");
this.popup = popup;
Object.setPrototypeOf(this, _PopupCancelledError.prototype);
}
};
var PopupNotSupportedError = class extends GenericError {
constructor(popup) {
super(
"NotSupported",
"Popup window is blocked by browser. see: https://docs.joy.id/guide/best-practice#popup-window-blocked"
);
this.popup = popup;
Object.setPrototypeOf(this, PopupCancelledError.prototype);
}
};
var RedirectErrorWithState = class extends Error {
constructor(message, state) {
super(message);
this.message = message;
this.state = state;
this.state = state;
}
};
// src/sdk/url.ts
var JOYID_REDIRECT = "joyid-redirect";
var getRedirectResponse = (uri) => {
const url = new URL(uri ?? window.location.href);
const data = url.searchParams.get("_data_");
if (data == null) {
throw new Error("No data found");
}
const res = decodeSearch(data);
if (res.error != null) {
throw new RedirectErrorWithState(res.error, res.state);
}
return res.data;
};
var buildJoyIDURL = (request, type, path) => {
const joyidURL = request.joyidAppURL ?? getConfig().joyidAppURL;
const url = new URL(`${joyidURL}`);
url.pathname = path;
let redirectTo = request.redirectURL;
if (type === "redirect") {
const redirectURL = new URL(redirectTo);
redirectURL.searchParams.set(JOYID_REDIRECT, "true");
redirectTo = redirectURL.href;
}
url.searchParams.set("type", type);
const data = encodeSearch({
...request,
redirectURL: redirectTo
});
url.searchParams.set("_data_", data);
return url.href;
};
var isRedirectFromJoyID = (uri) => {
try {
const url = new URL(uri ?? window.location.href);
return url.searchParams.has(JOYID_REDIRECT);
} catch (error) {
return false;
}
};
// src/sdk/popup.ts
var DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS = 3e3;
var openPopup = (url = "") => {
const width = 400;
const height = 600;
const left = window.screenX + (window.innerWidth - width) / 2;
const top = window.screenY + (window.innerHeight - height) / 2;
return window.open(
url,
"joyid:authorize:popup",
`left=${left},top=${top},width=${width},height=${height},resizable,scrollbars=yes,status=1`
);
};
var runPopup = async (config) => new Promise((resolve, reject) => {
if (isStandaloneBrowser()) {
reject(new PopupNotSupportedError(config.popup));
}
let popupEventListener;
let timeoutId;
const popupTimer = setInterval(() => {
if (config.popup?.closed) {
clearInterval(popupTimer);
clearTimeout(timeoutId);
window.removeEventListener("message", popupEventListener, false);
reject(new PopupCancelledError(config.popup));
}
}, 1e3);
timeoutId = setTimeout(
() => {
clearInterval(popupTimer);
reject(new PopupTimeoutError(config.popup));
window.removeEventListener("message", popupEventListener, false);
},
(config.timeoutInSeconds ?? DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS) * 1e3
);
popupEventListener = (e) => {
const joyidAppURL = config.joyidAppURL ?? getConfig().joyidAppURL;
if (joyidAppURL == null) {
throw new Error("joyidAppURL is not set in the config");
}
const appURL = new URL(joyidAppURL);
if (e.origin !== appURL.origin) {
return;
}
if (!e.data || e.data?.type !== config.type) {
return;
}
clearTimeout(timeoutId);
clearInterval(popupTimer);
window.removeEventListener("message", popupEventListener, false);
config.popup.close();
if (e.data.error) {
reject(new Error(e.data.error));
}
resolve(e.data.data);
};
window.addEventListener("message", popupEventListener);
});
// src/sdk/block-dialog.ts
var styleId = "joyid-block-dialog-style";
var approveId = "joyid-block-dialog-approve";
var rejectId = "joyid-block-dialog-reject";
var styleSheet = `
.joyid-block-dialog {
position: fixed;
top: 32px;
left: 50%;
width: 340px;
margin-left: -170px;
background: white;
color: #333;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
height: 110px;
z-index: 100002;
box-sizing: border-box;
border: 1px solid #ffffff;
border-radius: 8px;
padding: 16px 20px;
}
.joyid-block-dialog-bg {
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
position: fixed;
top: 0;
left: 0;
display: none;
z-index: 100001;
display: block;
}
.joyid-block-dialog-title {
font-weight: bold;
font-size: 14px;
margin-bottom: 8px;
}
.joyid-block-dialog-tip {
font-size: 12px;
color: #777;
}
.joyid-block-dialog-btn {
width: 90px;
height: 35px;
font-size: 12px;
text-align: center;
border-radius: 6px;
cursor: pointer;
}
.joyid-block-dialog-action {
text-align: right;
}
#${approveId} {
border: 1px solid #333;
color: #333;
background: #D2FF00;
margin-bottom: 8px;
}
#${rejectId} {
background: transparent;
}
`;
var dialogInnerHtml = `
<div class="joyid-block-dialog">
<div class="joyid-block-dialog-content">
<div class="joyid-block-dialog-title">
Request Pop-up
</div>
<div class="joyid-block-dialog-tip">
Click Approve to complete creating or using wallet
</div>
</div>
<div class="joyid-block-dialog-action">
<button class="joyid-block-dialog-btn" id="${approveId}">Approve</button>
<button class="joyid-block-dialog-btn" id="${rejectId}">Reject</button>
</div>
</div>
`;
var appendStyle = () => {
const _style = document.getElementById(styleId);
if (_style != null) {
return;
}
const style = document.createElement("style");
style.appendChild(document.createTextNode(styleSheet));
const head = document.head ?? document.getElementsByTagName("head")[0];
head.appendChild(style);
};
var createBlockDialog = async (cb) => {
appendStyle();
const dialog = document.createElement("div");
dialog.innerHTML = dialogInnerHtml;
document.body.appendChild(dialog);
const dialogBg = document.createElement("div");
dialogBg.className = "joyid-block-dialog-bg";
document.body.appendChild(dialogBg);
const approveBtn = document.getElementById(approveId);
const rejectBtn = document.getElementById(rejectId);
const closeDialog = () => {
document.body.removeChild(dialog);
document.body.removeChild(dialogBg);
};
return new Promise((resolve, reject) => {
approveBtn?.addEventListener("click", async () => {
try {
const data = await cb();
closeDialog();
resolve(data);
} catch (error) {
closeDialog();
reject(error);
}
});
rejectBtn?.addEventListener("click", () => {
closeDialog();
reject(new Error("User Rejected"));
});
});
};
// src/sdk/auth.ts
var buildJoyIDAuthURL = (request, type) => buildJoyIDURL(request, type, "/auth");
var authWithRedirect = (request) => {
window.location.assign(buildJoyIDAuthURL(request, "redirect"));
};
var authWithPopup = async (request, config) => {
config = config ?? {};
if (config.popup == null) {
config.popup = openPopup("");
if (config.popup == null) {
return createBlockDialog(async () => authWithPopup(request, config));
}
}
config.popup.location.href = buildJoyIDAuthURL(
request,
"popup" /* Popup */
);
return runPopup({
...request,
...config,
type: "Auth" /* Auth */
});
};
var authCallback = (uri) => getRedirectResponse(uri);
// src/sdk/sign-messge.ts
var buildJoyIDSignMessageURL = (request, type) => buildJoyIDURL(request, type, "/sign-message");
var signMessageWithRedirect = (request) => {
window.location.assign(buildJoyIDSignMessageURL(request, "redirect"));
};
var signMessageWithPopup = async (request, config) => {
config = config ?? {};
if (config.popup == null) {
config.popup = openPopup("");
if (config.popup == null) {
return createBlockDialog(
async () => signMessageWithPopup(request, config)
);
}
}
config.popup.location.href = buildJoyIDSignMessageURL(request, "popup");
return runPopup({
...request,
...config,
type: "SignMessage" /* SignMessage */
});
};
var signMessageCallback = (uri) => getRedirectResponse(uri);
export {
DappCommunicationType,
DappError,
DappErrorName,
DappRequestType,
EventKind,
GenericError,
PopupCancelledError,
PopupNotSupportedError,
PopupTimeoutError,
RedirectErrorWithState,
SESSION_KEY_VER,
SigningAlg,
TimeoutError,
append0x,
appendBuffer,
appendStyle,
authCallback,
authWithPopup,
authWithRedirect,
base64URLStringToBuffer,
base64urlToHex,
bufferToBase64URLString,
bufferToHex,
bufferToUTF8String,
buildJoyIDAuthURL,
buildJoyIDSignMessageURL,
buildJoyIDURL,
createBlockDialog,
decodeSearch,
encodeSearch,
getConfig,
getRedirectResponse,
hexToArrayBuffer,
hexToString,
hexToUTF8String,
initConfig,
internalConfig,
isRedirectFromJoyID,
isStandaloneBrowser,
openPopup,
parseSearchWith,
remove0x,
runPopup,
safeExec,
signMessageCallback,
signMessageWithPopup,
signMessageWithRedirect,
stringifySearchWith,
utf8StringToBuffer
};
//# sourceMappingURL=index.js.map