Compare commits

..

20 Commits

Author SHA1 Message Date
d7ba62e767 3.0.0 2022-05-12 16:42:36 +02:00
019197e43c BREAKING CHANGE(new TinkUser().getTinkLink()): now expects a tinkLinkOptionsArg 2022-05-12 16:42:36 +02:00
596f4e5398 2.0.0 2022-04-18 18:54:53 +02:00
dca6f9ef0b BREAKING CHANGE(core): switch to esm and allow specification of redirect url 2022-04-18 18:54:53 +02:00
bdd7419d3d 1.0.17 2022-02-19 13:53:13 +01:00
0d99ee51d5 fix(core): update 2022-02-19 13:53:12 +01:00
0f80623111 1.0.16 2022-02-19 13:47:41 +01:00
6c02861a79 fix(core): update 2022-02-19 13:47:40 +01:00
a25e758faf 1.0.15 2022-02-19 13:46:05 +01:00
14f2ba0692 fix(core): update 2022-02-19 13:46:05 +01:00
0c391c1fd1 1.0.14 2022-02-19 13:32:57 +01:00
0c9decce3e fix(core): update 2022-02-19 13:32:57 +01:00
edb3160f35 1.0.13 2022-02-19 13:30:47 +01:00
0eb51cf3c5 fix(core): update 2022-02-19 13:30:46 +01:00
80373424b4 1.0.12 2022-02-19 13:29:08 +01:00
d108baf672 fix(core): update 2022-02-19 13:29:08 +01:00
031d140a44 1.0.11 2022-02-19 13:28:29 +01:00
c96ccf198c fix(core): update 2022-02-19 13:28:29 +01:00
18835fa5ae 1.0.10 2022-02-19 13:16:00 +01:00
c0e26cdc4b fix(core): update 2022-02-19 13:15:59 +01:00
11 changed files with 5736 additions and 13951 deletions

View File

@ -100,10 +100,9 @@ codequality:
only:
- tags
script:
- npmci command npm install -g tslint typescript
- npmci command npm install -g typescript
- npmci npm prepare
- npmci npm install
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
tags:
- lossless
- docker

19419
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,11 @@
{
"name": "@mojoio/tink",
"version": "1.0.9",
"version": "3.0.0",
"private": false,
"description": "an unofficial api abstraction for tink.com",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"type": "module",
"author": "Lossless GmbH",
"license": "MIT",
"scripts": {
@ -12,16 +13,17 @@
"build": "(tsbuild --web)"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.1.25",
"@gitzone/tsbundle": "^1.0.78",
"@gitzone/tstest": "^1.0.64",
"@gitzone/tsbuild": "^2.1.61",
"@gitzone/tsbundle": "^1.0.102",
"@gitzone/tstest": "^1.0.70",
"@pushrocks/qenv": "^4.0.10",
"@pushrocks/tapbundle": "^4.0.7",
"@types/node": "^17.0.18",
"@pushrocks/tapbundle": "^5.0.3",
"@types/node": "^17.0.25",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.15.0"
},
"dependencies": {
"@pushrocks/smartdelay": "^2.0.13",
"@pushrocks/smartrequest": "^1.1.56"
},
"browserslist": [

View File

@ -27,6 +27,30 @@ Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20W
Use TypeScript for best inclass intellisense
```typescript
// this example assumes toplevel await
import * as tink from '@mojoio/tink';
const tinkAccount = new TinkAccount('<clientId>', '<clientSecret>');
const tinkUser = await tinkAccount.createTinkUser('<YourOwnUniqueUserId/externalUserId>');
const tinkLinkUrl = await tinkUser.getTinkLink('<marketCode like DE>');
// present the link to your user to connect their bank accounts to the tink platform.
const tinkProviderConsents = await tinkUser.getProviderConsents();
for (const providerConsent of tinkProviderConsents) {
const bankAccounts = await providerConsent.getBankAccounts();
for (const bankAccount of bankAccounts) {
const transactions = bankAccount.getTransactions();
}
}
// additional stuff
const existingTinkUser = await tinkAccount.getUser('<YourOwnUniqueUserId/externalUserId>');
await existingTinkuser.delete(); // delete the user on the tink platform
```
## Contribution
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)

View File

@ -3,7 +3,7 @@ import * as qenv from '@pushrocks/qenv';
const testQenv = new qenv.Qenv('./', './.nogit/');
import * as tink from '../ts/index';
import * as tink from '../ts/index.js';
let tinkTestAccount: tink.TinkAccount;
@ -19,17 +19,21 @@ tap.test('should report tink as healthy', async () => {
await expectAsync(tinkTestAccount.getTinkHealthyBoolean()).toBeTrue();
});
tap.test('should create a valid request', async (toolsArg) => {
const tinkuser = await tinkTestAccount.createTinkUser("user_1234_abc");
console.log(await tinkuser.getTinkLink());
process.exit(0);
await toolsArg.delayFor(10000);
tap.test('should create a tink user', async (toolsArg) => {
await tinkTestAccount.createTinkUser('user_1234_abc');
})
tap.test('should create a valid request', async (toolsArg) => {
const tinkuser: tink.TinkUser = await tinkTestAccount.getTinkUser('user_1234_abc');
console.log(tinkuser);
console.log(await tinkuser.getTinkLinkForMarket()); // defaults to 'DE';
console.log(await tinkuser.getProviderConsents());
});
tap.test('should delete existing users', async () => {
expect(tinkTestAccount).toBeInstanceOf(tink.TinkAccount);
const tinkUser = new tink.TinkUser(tinkTestAccount, null, 'user_1234_abc');
await tinkUser.delete();
})
});
tap.start();

8
ts/00_commitinfo_data.ts Normal file
View File

@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
*/
export const commitinfo = {
name: '@mojoio/tink',
version: '3.0.0',
description: 'an unofficial api abstraction for tink.com'
}

View File

@ -1,2 +1,3 @@
export * from './tink.classes.tinkaccount';
export * from './tink.classes.tinkuser';
export * from './tink.classes.tinkaccount.js';
export * from './tink.classes.tinkuser.js';
export * from './tink.classes.tinkproviderconsent.js';

View File

@ -1,22 +1,24 @@
import * as plugins from './tink.plugins';
import * as plugins from './tink.plugins.js';
import { TinkUser } from './tink.classes.tinkuser';
import { TinkUser } from './tink.classes.tinkuser.js';
export class TinkAccount {
public clientId: string;
private clientSecret: string;
private _clientSecret: string;
private apiBaseUrl: string = 'https://api.tink.com';
private _apiBaseUrl: string = 'https://api.tink.com';
constructor(clientIdArg: string, clientSecretArg: string) {
this.clientId = clientIdArg;
this.clientSecret = clientSecretArg;
this._clientSecret = clientSecretArg;
}
public async getTinkHealthyBoolean(): Promise<boolean> {
const response = await plugins.smartrequest.request(
'https://api.tink.com/api/v1/monitoring/healthy',
{}
{
keepAlive: false,
}
);
return response.body === 'ok';
}
@ -24,8 +26,10 @@ export class TinkAccount {
public async getClientAccessTokenForScope(scopeArg: string): Promise<string> {
// lets get an accessToken for the request
const response = await plugins.smartrequest.postFormDataUrlEncoded(
`${this.apiBaseUrl}/api/v1/oauth/token`,
{},
`${this._apiBaseUrl}/api/v1/oauth/token`,
{
keepAlive: false,
},
[
{
key: 'client_id',
@ -33,7 +37,7 @@ export class TinkAccount {
},
{
key: 'client_secret',
content: this.clientSecret,
content: this._clientSecret,
},
{
key: 'grant_type',
@ -46,6 +50,8 @@ export class TinkAccount {
]
);
if (response.statusCode !== 200) {
console.log(response.statusCode);
console.log(response.body);
throw new Error('there was an error aquiring an access token.');
}
const clientAccessToken = response.body.access_token;
@ -59,8 +65,9 @@ export class TinkAccount {
) {
const accessToken = await this.getClientAccessTokenForScope('authorization:grant');
const response = await plugins.smartrequest.postFormDataUrlEncoded(
`${this.apiBaseUrl}/api/v1/oauth/authorization-grant/delegate`,
`${this._apiBaseUrl}/api/v1/oauth/authorization-grant/delegate`,
{
keepAlive: false,
headers: {
Authorization: `Bearer ${accessToken}`,
},
@ -100,8 +107,10 @@ export class TinkAccount {
public async getUserAccessToken(authorizationCode: string): Promise<string> {
const accessToken = await this.getClientAccessTokenForScope('authorization:grant');
const response = await plugins.smartrequest.postFormDataUrlEncoded(
`${this.apiBaseUrl}/api/v1/oauth/token`,
{},
`${this._apiBaseUrl}/api/v1/oauth/token`,
{
keepAlive: false,
},
[
{
key: 'code',
@ -113,7 +122,7 @@ export class TinkAccount {
},
{
key: 'client_secret',
content: this.clientSecret,
content: this._clientSecret,
},
{
key: 'grant_type',
@ -144,7 +153,8 @@ export class TinkAccount {
} else {
console.log('tink is healthy, continuing...');
}
const response = await plugins.smartrequest.request(`${this.apiBaseUrl}${optionsArg.urlArg}`, {
const response = await plugins.smartrequest.request(`${this._apiBaseUrl}${optionsArg.urlArg}`, {
keepAlive: false,
headers: {
Authorization: `Bearer ${optionsArg.accessToken}`,
'Content-Type': 'application/json',
@ -156,6 +166,11 @@ export class TinkAccount {
return response.body;
}
public async getTinkUser(externalUserIdArg: string) {
const tinkuser = await TinkUser.getTinkUser(this, externalUserIdArg);
return tinkuser;
}
public async createTinkUser(externalUserIdArg: string) {
const tinkuser = await TinkUser.createNewTinkUser(this, externalUserIdArg);
return tinkuser;

View File

@ -0,0 +1,30 @@
import * as plugins from './tink.plugins.js';
import { TinkUser } from './tink.classes.tinkuser.js';
/**
* a provider consent maps to tinks bank consents
*/
export class TinkProviderConsent {
// STATIC
public static async getProviderConsentsForUser(tinkUserRefArg: TinkUser) {
const returnProviderConsents: TinkProviderConsent[] = [];
const authorizationCode = await tinkUserRefArg.tinkAccountRef.getUserAuthorizationCode(
tinkUserRefArg.externalUserIdArg,
tinkUserRefArg.tinkAccountRef.clientId,
'accounts:read,balances:read,transactions:read,provider-consents:read'
);
const accessToken = await tinkUserRefArg.tinkAccountRef.getUserAccessToken(authorizationCode);
const responseData = await tinkUserRefArg.tinkAccountRef.request({
urlArg: '/api/v1/provider-consents',
accessToken,
methodArg: 'GET',
payloadArg: null,
});
console.log(responseData);
return returnProviderConsents;
}
// INSTANCE
constructor(tinkUserRefArg: TinkUser) {}
}

View File

@ -1,12 +1,16 @@
import * as plugins from './tink.plugins';
import * as plugins from './tink.plugins.js';
import { TinkAccount } from './tink.classes.tinkaccount';
import { TinkAccount } from './tink.classes.tinkaccount.js';
import { TinkProviderConsent } from './tink.classes.tinkproviderconsent.js';
export class TinkUser {
// STATIC
public static async createNewTinkUser(tinkaccountArg: TinkAccount, externalUserIdArg: string) {
const accessToken = await tinkaccountArg.getClientAccessTokenForScope('user:create');
const response = await tinkaccountArg.request({
public static async createNewTinkUser(tinkAccountArg: TinkAccount, externalUserIdArg: string) {
const accessToken = await tinkAccountArg.getClientAccessTokenForScope('user:create');
const responseData: {
external_user_id: string;
user_id: string;
} = await tinkAccountArg.request({
urlArg: '/api/v1/user/create',
accessToken,
methodArg: 'POST',
@ -17,7 +21,57 @@ export class TinkUser {
},
});
const newTinkUser = new TinkUser(tinkaccountArg, response.user_id, response.external_user_id);
const newTinkUser = await TinkUser.getTinkUser(tinkAccountArg, externalUserIdArg);
return newTinkUser;
}
public static async getTinkUser(tinkAccountArg: TinkAccount, externalUserIdArg: string) {
const authorizationCode = await tinkAccountArg.getUserAuthorizationCode(
externalUserIdArg,
tinkAccountArg.clientId,
'user:read'
);
const accessToken = await tinkAccountArg.getUserAccessToken(authorizationCode);
const responseData: {
appId: string;
created: string;
externalUserId: string;
flags: string[];
id: string;
nationalId: string;
profile: {
// cashbackEnabled: boolean; // deprecated
currency: string;
locale: string;
market: string;
notificationSettings: {
balance: boolean;
budget: boolean;
doubleCharge: boolean;
einvoices: boolean;
fraud: boolean;
income: boolean;
largeExpense: boolean;
leftToSpend: boolean;
loanUpdate: boolean;
summaryMonthly: boolean;
summaryWeekly: boolean;
transaction: boolean;
unusualAccount: boolean;
unusualCategory: boolean;
};
periodAdjustedDay: 25;
periodMode: 'MONTHLY_ADJUSTED' | 'MONTHLY';
timeZone: string;
};
// username: string; // not relevant
} = await tinkAccountArg.request({
urlArg: '/api/v1/user',
accessToken,
methodArg: 'GET',
payloadArg: null,
});
const newTinkUser = new TinkUser(tinkAccountArg, responseData.id, responseData.externalUserId);
return newTinkUser;
}
@ -26,8 +80,6 @@ export class TinkUser {
public tinkUserId: string;
public externalUserIdArg: string;
public authorizationToken: string;
constructor(tinkAccountrefArg: TinkAccount, tinkUserIdArg: string, externalUserIdArg: string) {
this.tinkAccountRef = tinkAccountrefArg;
this.tinkUserId = tinkUserIdArg;
@ -53,13 +105,34 @@ export class TinkUser {
console.log(`successfully deleted user with externalId ${this.externalUserIdArg}`);
}
public async getTinkLink(): Promise<string> {
/**
* gets a tink link that can be used by a user to connect accounts
* @returns
*/
public async getTinkLinkForMarket(linkOptionsArg: {
countryId: string;
redirectUrl: string;
testProviderBool?: boolean;
} = {
countryId: 'DE',
redirectUrl: 'https://console.tink.com/callback',
testProviderBool: true
}): Promise<string> {
if (typeof linkOptionsArg.testProviderBool !== 'boolean') {
linkOptionsArg.testProviderBool = false;
}
const authorizationCode = await this.tinkAccountRef.getUserAuthorizationCode(
this.externalUserIdArg,
'df05e4b379934cd09963197cc855bfe9', // this is a hardcoded app id for tink link, as recommended by tink.com
'authorization:read,authorization:grant,credentials:refresh,credentials:read,credentials:write,providers:read,user:read'
);
const tinkLinkUrl = `https://link.tink.com/1.0/business-transactions/connect-accounts?client_id=${'teststate'}&redirect_uri=https://console.tink.com/callback&authorization_code=${authorizationCode}&market=DE`;
const tinkLinkUrl = `https://link.tink.com/1.0/business-transactions/connect-accounts?client_id=${'teststate'}&redirect_uri=${linkOptionsArg.redirectUrl}&authorization_code=${authorizationCode}&market=${linkOptionsArg.countryId}&test=${linkOptionsArg.testProviderBool}`;
return tinkLinkUrl;
}
public async getProviderConsents(): Promise<TinkProviderConsent[]> {
const providerConsents: TinkProviderConsent[] =
await TinkProviderConsent.getProviderConsentsForUser(this);
return providerConsents;
}
}

View File

@ -1,6 +1,6 @@
// @pushrocks scope
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartrequest from '@pushrocks/smartrequest';
import * as smartpromise from '@pushrocks/smartpromise';
export {
smartrequest
}
export { smartdelay, smartrequest, smartpromise };