Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(open-payments): adding open-payments package #669

Merged
merged 18 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
'pkg: auth': packages/auth/**/*
'pkg: openapi': packages/openapi/**/*
'pkg: map': packages/mock-account-provider/**/*
'pkg: open-payments': packages/open-payments/**/*

# Add 'type: documentation' label to any change to *.md files
'type: ci':
Expand All @@ -22,6 +23,7 @@
- packages/frontend/src/**/*
- packages/auth/src/**/*
- packages/openapi/src/**/*
- packages/open-payments/src/**/*

# Add 'type: tests' label to any change to *.test.ts files
'type: tests': packages/**/*.test.ts
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/lint_test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ jobs:
- uses: ./.github/workflows/rafiki/env-setup
- run: pnpm --filter openapi test

open-payments:
runs-on: ubuntu-latest
needs: checkout
timeout-minutes: 5
steps:
- uses: actions/checkout@v2
- uses: ./.github/workflows/rafiki/env-setup
- run: pnpm --filter open-payments test

build:
runs-on: ubuntu-latest
timeout-minutes: 5
Expand All @@ -65,6 +74,7 @@ jobs:
- frontend
- auth
- openapi
- open-payments
steps:
- uses: actions/checkout@v2
- uses: ./.github/workflows/rafiki/env-setup
Expand Down
17 changes: 17 additions & 0 deletions packages/open-payments/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const baseConfig = require('../../jest.config.base.js')
// eslint-disable-next-line @typescript-eslint/no-var-requires
const packageName = require('./package.json').name

module.exports = {
...baseConfig,
clearMocks: true,
roots: [`<rootDir>/packages/${packageName}`],
testRegex: `(packages/${packageName}/.*/__tests__/.*|\\.(test|spec))\\.tsx?$`,
moduleDirectories: [`node_modules`, `packages/${packageName}/node_modules`],
modulePaths: [`<rootDir>/packages/${packageName}/src/`],
id: packageName,
displayName: packageName,
rootDir: '../..'
}
26 changes: 26 additions & 0 deletions packages/open-payments/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "open-payments",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist/**/*"
],
"scripts": {
"build": "pnpm clean && tsc --build tsconfig.json",
"clean": "rm -fr dist/",
"generate:types": "npx ts-node scripts/generate-types.ts",
"prepack": "pnpm build",
"test": "jest --passWithNoTests"
},
"devDependencies": {
"@types/node": "^18.7.12",
"nock": "^13.2.9",
"openapi-typescript": "^4.5.0",
Copy link
Contributor Author

@mkurapov mkurapov Oct 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrested quite a bit with this, but I wasn't able to use the newest version of this library. Using the programmatic way of generating types via openapiTS anything above v5 does not work and result in ERR_REQUIRE_ESM errors. Versions 5 and above do not support backwards compatibility with CommonJS, especially given the fact that having type: module in package.json breaks using this package as a workspace in the /backend project.

one two three are the related issues in the openapi-typescript repo.

"ts-node": "^10.7.0",
"typescript": "^4.3.0"
},
"dependencies": {
"axios": "^1.1.2"
}
}
19 changes: 19 additions & 0 deletions packages/open-payments/scripts/generate-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import fs from 'fs'
import openapiTS from 'openapi-typescript'
import config from '../src/config'
;(async () => {
try {
const output = await openapiTS(config.OPEN_PAYMENTS_OPEN_API_URL)
const fileName = 'src/generated/types.ts'

fs.writeFile(fileName, output, (error) => {
if (error) {
console.log(`Error when writing types to ${fileName}`, { error })
}
})
} catch (error) {
console.log('Error when generating types', {
error
})
}
})()
67 changes: 67 additions & 0 deletions packages/open-payments/src/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { createAxiosInstance, get } from './client'
import nock from 'nock'

describe('open-payments', (): void => {
describe('createAxiosInstance', (): void => {
test('sets timeout properly', async (): Promise<void> => {
expect(createAxiosInstance({ timeout: 1000 }).defaults.timeout).toBe(1000)
})
test('sets Content-Type header properly', async (): Promise<void> => {
expect(
createAxiosInstance().defaults.headers.common['Content-Type']
).toBe('application/json')
})
})

describe('get', (): void => {
const axiosInstance = createAxiosInstance()
const baseUrl = 'http://localhost:1000'

beforeEach(() => {
jest.spyOn(axiosInstance, 'get')
})

test('sets headers properly if accessToken provided', async (): Promise<void> => {
nock(baseUrl)
.get('/incoming-payment')
.reply(200, () => ({
validReceiver: 0
}))

await get(axiosInstance, {
url: `${baseUrl}/incoming-payment`,
accessToken: 'accessToken'

Check failure

Code scanning / CodeQL

Hard-coded credentials

The hard-coded value "accessToken" is used as [authorization header](1).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this is a test file)

})

expect(axiosInstance.get).toHaveBeenCalledWith(
`${baseUrl}/incoming-payment`,
{
headers: {
Authorization: 'GNAP accessToken',

Check failure

Code scanning / CodeQL

Hard-coded credentials

The hard-coded value "GNAP accessToken" is used as [authorization header](1).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this is a test file)

Signature: 'TODO',
'Signature-Input': 'TODO'
}
}
)
})

test('sets headers properly if accessToken is not provided', async (): Promise<void> => {
nock(baseUrl)
.get('/incoming-payment')
.reply(200, () => ({
validReceiver: 0
}))

await get(axiosInstance, {
url: `${baseUrl}/incoming-payment`
})

expect(axiosInstance.get).toHaveBeenCalledWith(
`${baseUrl}/incoming-payment`,
{
headers: {}
}
)
})
})
})
67 changes: 67 additions & 0 deletions packages/open-payments/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ILPStreamConnection, IncomingPayment } from './types'
import axios, { AxiosInstance } from 'axios'
import config from './config'

interface CreateOpenPaymentClientArgs {
timeout?: number
}

interface GetArgs {
url: string
accessToken?: string
}

export interface OpenPaymentsClient {
incomingPayment: {
get(args: GetArgs): Promise<IncomingPayment>
}
ilpStreamConnection: {
get(args: GetArgs): Promise<ILPStreamConnection>
}
}

export const get = async <T>(
axios: AxiosInstance,
args: GetArgs
): Promise<T> => {
const { url, accessToken } = args

const { data } = await axios.get(url, {
headers: accessToken
? {
Authorization: `GNAP ${accessToken}`,
Signature: 'TODO',
'Signature-Input': 'TODO'
}
: {}
})

return data
}

export const createAxiosInstance = (
args?: CreateOpenPaymentClientArgs
): AxiosInstance => {
const axiosInstance = axios.create({
timeout: args?.timeout ?? config.DEFAULT_REQUEST_TIMEOUT
})

axiosInstance.defaults.headers.common['Content-Type'] = 'application/json'

return axiosInstance
}

export const createClient = (
args?: CreateOpenPaymentClientArgs
): OpenPaymentsClient => {
const axios = createAxiosInstance(args)

return {
incomingPayment: {
get: (args: GetArgs) => get<IncomingPayment>(axios, args)
},
ilpStreamConnection: {
get: (args: GetArgs) => get<ILPStreamConnection>(axios, args)
}
}
}
Comment on lines +54 to +67
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wanted to keep very simple for the first PR, will be adding open-api validation next.

5 changes: 5 additions & 0 deletions packages/open-payments/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
OPEN_PAYMENTS_OPEN_API_URL:
Copy link
Contributor Author

@mkurapov mkurapov Oct 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

want to make sure the same URL is used for type generation and (soon to be added) open API response validation

'https://raw.githubusercontent.com/interledger/open-payments/4c873dba89164decffbe84905d12f1d4ec045389/open-api-spec.yaml',
DEFAULT_REQUEST_TIMEOUT: 3_000
}
Loading