wikitree-js
Version:
Javascript library for the WikiTree API
277 lines • 10.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.login = exports.clientLogin = exports.navigateToLoginPage = exports.getLoggedInUserName = exports.getPeople = exports.getRelatives = exports.getDescendants = exports.getAncestors = exports.getPerson = exports.wikiTreeGet = exports.fetchWikiTree = exports.WikiTreeError = void 0;
const js_cookie_1 = __importDefault(require("js-cookie"));
const cross_fetch_1 = require("cross-fetch");
const form_data_1 = __importDefault(require("form-data"));
/** Default API URL if not explicitly specified. */
const WIKITREE_API_URL = 'https://api.wikitree.com/api.php';
/** Default appId sent if not explicitly specified. */
const WIKITREE_JS_APPID = 'wikitree-js';
/**
* Cookie where the logged in user name is stored. This cookie is shared
* between apps hosted on apps.wikitree.com. It is not used to authenticate
* requests but only to allow displaying what user is logged in.
* Authentication cookies are stored when the user logs in via
* https://api.wikitree.com/api.php
*/
const USER_NAME_COOKIE = 'wikidb_wtb_UserName';
/** Wraps an error returned from the WikiTree API. */
class WikiTreeError extends Error {
constructor(message) {
super(message);
this.name = 'WikiTreeError';
}
}
exports.WikiTreeError = WikiTreeError;
/** Sends a request to the WikiTree API. Returns the raw response. */
async function fetchWikiTree(request, options) {
const requestData = new form_data_1.default();
requestData.append('format', 'json');
requestData.append('appId', options?.appId ?? WIKITREE_JS_APPID);
for (const key in request) {
if (request[key]) {
requestData.append(key, request[key]);
}
}
const apiUrl = options?.apiUrl || WIKITREE_API_URL;
const postRequest = {
method: 'POST',
redirect: 'manual',
body: requestData,
credentials: isWikiTreeUrl(apiUrl) ? 'include' : undefined,
};
if (options?.auth) {
postRequest.headers = { Cookie: options.auth.cookies };
}
return await (0, cross_fetch_1.fetch)(apiUrl, postRequest);
}
exports.fetchWikiTree = fetchWikiTree;
/** Sends a request to the WikiTree API. Returns the parsed response JSON. */
async function wikiTreeGet(request, options) {
const response = await fetchWikiTree(request, options);
const result = await response.json();
if (result[0]?.status) {
throw new WikiTreeError(result[0].status);
}
return result;
}
exports.wikiTreeGet = wikiTreeGet;
/**
* Retrieves a single person record from WikiTree.
*
* See also: https://github.com/wikitree/wikitree-api/blob/main/getPerson.md
*/
async function getPerson(key, args, options) {
const request = {
action: 'getPerson',
key,
bioFormat: args?.bioFormat,
fields: args?.fields instanceof Array ? args.fields.join(',') : args?.fields,
resolveRedirect: args?.resolveRedirect ? '1' : undefined,
};
const response = await wikiTreeGet(request, options);
return response[0].person;
}
exports.getPerson = getPerson;
/**
* Retrieves ancestors from WikiTree for the given person ID.
*
* See also: https://github.com/wikitree/wikitree-api/blob/main/getAncestors.md
*/
async function getAncestors(key, args, options) {
const request = {
action: 'getAncestors',
key,
depth: args?.depth,
bioFormat: args?.bioFormat,
fields: args?.fields instanceof Array ? args.fields.join(',') : args?.fields,
resolveRedirect: args?.resolveRedirect ? '1' : undefined,
};
const response = await wikiTreeGet(request, options);
return response[0].ancestors;
}
exports.getAncestors = getAncestors;
/**
* Retrieves descendants from WikiTree for the given person ID.
*
* See also: https://github.com/wikitree/wikitree-api/blob/main/getDescendants.md
*/
async function getDescendants(key, args, options) {
const request = {
action: 'getDescendants',
key,
depth: args?.depth,
bioFormat: args?.bioFormat,
fields: args?.fields instanceof Array ? args.fields.join(',') : args?.fields,
resolveRedirect: args?.resolveRedirect ? '1' : undefined,
};
const response = await wikiTreeGet(request, options);
return response[0].descendants;
}
exports.getDescendants = getDescendants;
/**
* Retrieves relatives from WikiTree for the given array of person IDs.
* If a key does not exist or is inaccessible, it is omitted in the result.
*
* See also: https://github.com/wikitree/wikitree-api/blob/main/getRelatives.md
*/
async function getRelatives(keys, args, options) {
if (args?.bioFormat && !args?.fields?.includes('Bio')) {
console.warn('Setting bioFormat has no effect if the "Bio" field is not requested' +
' explicitly');
}
const request = {
action: 'getRelatives',
keys: keys.join(','),
getParents: args?.getParents ? 'true' : undefined,
getChildren: args?.getChildren ? 'true' : undefined,
getSpouses: args?.getSpouses ? 'true' : undefined,
getSiblings: args?.getSiblings ? 'true' : undefined,
bioFormat: args?.bioFormat,
fields: args?.fields instanceof Array ? args.fields.join(',') : args?.fields,
};
const response = await wikiTreeGet(request, options);
if (response[0].items === null) {
return [];
}
return response[0].items.map((item) => item.person);
}
exports.getRelatives = getRelatives;
/**
* Retrieves relatives from WikiTree for the given array of person IDs.
*
* See also: https://github.com/wikitree/wikitree-api/blob/main/getPeople.md
*/
async function getPeople(keys, args, options) {
if (args?.bioFormat && !args?.fields?.includes('Bio')) {
console.warn('Setting bioFormat has no effect if the "Bio" field is not requested' +
' explicitly');
}
const request = {
action: 'getPeople',
keys: keys.join(','),
siblings: args?.siblings ? '1' : undefined,
ancestors: args?.ancestors,
descendants: args?.descendants,
nuclear: args?.nuclear,
minGeneration: args?.minGeneration,
limit: args?.limit,
start: args?.start,
bioFormat: args?.bioFormat,
fields: args?.fields instanceof Array ? args.fields.join(',') : args?.fields,
};
const response = await wikiTreeGet(request, options);
console.log(JSON.stringify(response, null, 2));
if (response[0].people === null) {
return [];
}
return Object.values(response[0].people);
}
exports.getPeople = getPeople;
/**
* Returns the logged in user name or undefined if not logged in.
*
* In the browser, call this function without arguments.
* This is not an authoritative answer. The result of this function relies on
* the cookies set on the apps.wikitree.com domain under which this application
* is hosted. The authoritative source of login information is in cookies set on
* the api.wikitree.com domain.
*
* In Node.js, call this function with the auth parameter. This is an
* authoritative answer because the login flow is under control of the
* wikitree-js library.
*/
function getLoggedInUserName(auth) {
if (!auth) {
// Return user name stored in browser cookies.
return js_cookie_1.default.get(USER_NAME_COOKIE);
}
// Extract user name from cookies in WikiTreeAuthentication.
const regex = new RegExp(`${USER_NAME_COOKIE}=(.*?);`);
const match = auth.cookies.match(regex);
return match ? match[1] : undefined;
}
exports.getLoggedInUserName = getLoggedInUserName;
// === Browser-specific code ===
/**
* Navigates to WikiTree login screen at https://api.wikitree.com/api.php with
* the specified return URL.
*/
function navigateToLoginPage(returnUrl) {
if (!isWikiTreeUrl(returnUrl)) {
console.warn('Return URLs outside of the wikitree.com domain will not work with the' +
" WikiTree login flow because of WikiTree API's CORS settings.");
}
const form = document.createElement('form');
form.setAttribute('action', WIKITREE_API_URL);
form.setAttribute('method', 'POST');
form.setAttribute('hidden', 'true');
const actionInput = document.createElement('input');
actionInput.setAttribute('name', 'action');
actionInput.setAttribute('type', 'hidden');
actionInput.setAttribute('value', 'clientLogin');
const returnUrlInput = document.createElement('input');
returnUrlInput.setAttribute('name', 'returnURL');
returnUrlInput.setAttribute('type', 'hidden');
returnUrlInput.setAttribute('value', returnUrl);
form.appendChild(actionInput);
form.appendChild(returnUrlInput);
document.body.appendChild(form);
form.submit();
}
exports.navigateToLoginPage = navigateToLoginPage;
async function clientLogin(authcode, options) {
const response = await wikiTreeGet({
action: 'clientLogin',
authcode,
}, options);
const result = response.clientLogin;
if (result.result === 'Success') {
js_cookie_1.default.set(USER_NAME_COOKIE, result.username);
}
return result;
}
exports.clientLogin = clientLogin;
// === Node.js-specific code ===
/**
* Logs in to WikiTree returning authentication credentials.
* This function will not work in the browser because it handles cookies directly.
* Throws an exception if login fails.
*/
async function login(email, password, options) {
const authcode = await getAuthcode(email, password, options);
return { cookies: await getAuthCookies(authcode, options) };
}
exports.login = login;
async function getAuthcode(email, password, options) {
const response = await fetchWikiTree({
action: 'clientLogin',
doLogin: 1,
returnURL: 'https://x/',
wpEmail: email,
wpPassword: password,
}, options);
if (response.status !== 302) {
throw new WikiTreeError('Invalid login credentials');
}
return response.headers.get('location').replace('https://x/?authcode=', '');
}
async function getAuthCookies(authcode, options) {
const response = await fetchWikiTree({
action: 'clientLogin',
authcode,
}, options);
const result = await response.json();
if (result.clientLogin?.result !== 'Success') {
throw new WikiTreeError('Could not authorize authcode');
}
return response.headers.get('set-cookie');
}
function isWikiTreeUrl(url) {
return url.match(/^https:\/\/[^/]*wikitree.com\/.*/);
}
//# sourceMappingURL=wikitree_api.js.map