splitwise-ts
Version:
A typed, fast, flexible SDK for Splitwise written in TypeScript
313 lines (312 loc) • 10.7 kB
JavaScript
import * as __WEBPACK_EXTERNAL_MODULE_es_toolkit_82663681__ from "es-toolkit";
import * as __WEBPACK_EXTERNAL_MODULE_ofetch__ from "ofetch";
import * as __WEBPACK_EXTERNAL_MODULE_oauth4webapi__ from "oauth4webapi";
class SplitwiseError extends Error {
static __splitwise_error__ = true;
code;
cause;
message;
constructor({ message, code, cause }){
super(message);
this.name = 'SplitwiseTSError';
this.message = message || '';
this.code = code || 500;
this.cause = cause || 'splitwise';
}
}
function isError(input) {
return input?.constructor?.__splitwise_error__ === true;
}
function createError(input) {
if ('string' == typeof input) return new SplitwiseError({
message: input
});
if (isError(input)) return input;
const statusMessage = input.message ?? input.statusMessage ?? input.statusText;
const err = new SplitwiseError({
message: statusMessage ?? '',
cause: input.cause || 'splitwise',
code: input.statusCode ?? input?.code
});
return err;
}
const flatten = (payload, prefix = '', delimitter = '_')=>{
const result = {};
const keys = Object.keys(payload);
for(let idx = 0; idx < keys.length; idx++){
const key = keys[idx];
const value = payload[key];
const prefixedKey = prefix ? `${prefix}${delimitter}${key}` : key;
if ((0, __WEBPACK_EXTERNAL_MODULE_es_toolkit_82663681__.isPlainObject)(value) && Object.keys(value).length > 0) {
Object.assign(result, flatten(value, prefixedKey));
continue;
}
if (Array.isArray(value)) {
Object.assign(result, flatten(value, prefixedKey));
continue;
}
result[prefixedKey] = value;
}
return result;
};
const splitwisify = (input)=>{
const flattened = flatten(input);
const mapped = (0, __WEBPACK_EXTERNAL_MODULE_es_toolkit_82663681__.mapValues)(flattened, (value)=>(0, __WEBPACK_EXTERNAL_MODULE_es_toolkit_82663681__.isPrimitive)(value) ? value : '');
return mapped;
};
const makeResponse = async (promise)=>{
try {
return await promise;
} catch (error) {
throw createError(error);
}
};
const getFetcher = ()=>__WEBPACK_EXTERNAL_MODULE_ofetch__.ofetch.create({
retry: 3,
retryDelay: 500
});
async function rest({ fetcher, auth, endpoint, params, requestBody, method, baseUrl }) {
const accessToken = auth?.accessToken;
if (!accessToken) throw createError({
cause: 'auth',
code: 402,
message: 'Access token is missing'
});
const includeBody = [
'post',
'put'
].includes(method.toLowerCase()) && !!requestBody;
const promise = fetcher(endpoint, {
baseURL: baseUrl,
method,
headers: {
authorization: `Bearer ${accessToken}`
},
query: params,
body: includeBody ? requestBody : void 0
});
return makeResponse(promise);
}
const config = {
issuer: 'https://secure.splitwise.com',
authorization_endpoint: 'https://secure.splitwise.com/oauth/authorize',
token_endpoint: 'https://secure.splitwise.com/oauth/token',
api_url: 'https://secure.splitwise.com/api/v3.0'
};
class Client {
#auth;
#defaultRequestOptions;
constructor(auth){
this.#auth = auth;
this.#defaultRequestOptions = {
auth: this.#auth,
fetcher: getFetcher(),
endpoint: '',
method: 'GET',
baseUrl: config.api_url
};
}
users = {
getCurrentUser: ()=>rest({
...this.#defaultRequestOptions,
endpoint: "/get_current_user",
method: 'get'
}),
getUser: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/get_user/${id}`,
method: 'get'
}),
updateUser: (id, request_body)=>rest({
...this.#defaultRequestOptions,
endpoint: `/update_user/${id}`,
requestBody: request_body,
method: 'post'
})
};
groups = {
getGroups: ()=>rest({
...this.#defaultRequestOptions,
endpoint: "/get_groups",
method: 'get'
}),
getGroup: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/get_group/${id}`,
method: 'get'
}),
createGroup: (request_body)=>rest({
...this.#defaultRequestOptions,
endpoint: "/create_group",
requestBody: request_body,
method: 'post'
}),
deleteGroup: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/delete_group/${id}`,
method: 'post'
}),
unDeleteGroup: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/undelete_group/${id}`,
method: 'post'
}),
addUserToGroup: (request_body)=>rest({
...this.#defaultRequestOptions,
endpoint: "/add_user_to_group",
requestBody: request_body,
method: 'post'
}),
removeUserFromGroup: (request_body)=>rest({
...this.#defaultRequestOptions,
endpoint: "/remove_user_from_group",
requestBody: request_body,
method: 'post'
})
};
friends = {
getFriends: ()=>rest({
...this.#defaultRequestOptions,
endpoint: "/get_friends",
method: 'get'
}),
getFriend: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/get_friend/${id}`,
method: 'get'
}),
createFriend: (request_body)=>rest({
...this.#defaultRequestOptions,
endpoint: "/create_friend",
requestBody: request_body,
method: 'post'
}),
createFriends: (request_body)=>rest({
...this.#defaultRequestOptions,
endpoint: "/create_friends",
requestBody: request_body,
method: 'post'
}),
deleteFriend: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/delete_friend/${id}`,
method: 'post'
})
};
expenses = {
getExpense: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/get_expense/${id}`,
method: 'get'
}),
getExpenses: (params)=>rest({
...this.#defaultRequestOptions,
endpoint: "/get_expenses",
params: params,
method: 'get'
}),
createExpense: (request_body)=>rest({
...this.#defaultRequestOptions,
endpoint: "/create_expense",
requestBody: request_body,
method: 'post'
}),
updateExpense: (id, request_body)=>rest({
...this.#defaultRequestOptions,
endpoint: `/update_expense/${id}`,
requestBody: request_body,
method: 'post'
}),
deleteExpense: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/delete_expense/${id}`,
method: 'post'
}),
unDeleteExpense: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/undelete_expense/${id}`,
method: 'post'
})
};
comments = {
getComments: (params)=>rest({
...this.#defaultRequestOptions,
endpoint: "/get_comments",
params: params,
method: 'get'
}),
createComment: (request_body)=>rest({
...this.#defaultRequestOptions,
endpoint: "/create_comment",
requestBody: request_body,
method: 'post'
}),
deleteComment: (id)=>rest({
...this.#defaultRequestOptions,
endpoint: `/delete_comment/${id}`,
method: 'post'
})
};
notifications = {
getNotifications: (params)=>rest({
...this.#defaultRequestOptions,
endpoint: "/get_notifications",
params: params,
method: 'get'
})
};
other = {
getCurrencies: ()=>rest({
...this.#defaultRequestOptions,
endpoint: "/get_currencies",
method: 'get'
}),
getCategories: ()=>rest({
...this.#defaultRequestOptions,
endpoint: "/get_categories",
method: 'get'
})
};
}
class OAuth2User {
token;
#options;
constructor(credentials){
this.#options = credentials;
}
get accessToken() {
return this.token ?? null;
}
async requestAccessToken() {
if (!this.#options?.clientId || !this.#options?.clientSecret) throw createError({
cause: 'auth',
message: 'Both clientId and clientSecret is required',
code: 401
});
const { clientId, clientSecret } = this.#options;
const client = {
client_id: clientId
};
const as = config;
const params = (0, __WEBPACK_EXTERNAL_MODULE_oauth4webapi__.validateAuthResponse)(as, client, new URLSearchParams());
const clientAuth = (0, __WEBPACK_EXTERNAL_MODULE_oauth4webapi__.ClientSecretPost)(clientSecret);
try {
const response = await (0, __WEBPACK_EXTERNAL_MODULE_oauth4webapi__.clientCredentialsGrantRequest)(as, client, clientAuth, params);
const token = await (0, __WEBPACK_EXTERNAL_MODULE_oauth4webapi__.processClientCredentialsResponse)(as, client, response);
this.token = token.access_token;
return {
access_token: this.token
};
} catch (e) {
const responseError = e;
const cause = responseError?.error;
const code = responseError?.status;
throw createError({
cause: 'auth',
code,
message: cause
});
}
}
}
export { Client, OAuth2User, splitwisify };