Skip to content

Commit

Permalink
feat: add fetch domains api
Browse files Browse the repository at this point in the history
  • Loading branch information
CCharlieLi committed Feb 16, 2021
1 parent 9d124e7 commit d61ea86
Show file tree
Hide file tree
Showing 19 changed files with 397 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .coveralls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
service_name: travis-pro
repo_token: FbUTUBuan5AgSUOEJU4YoEAlUyhW4orTR
Empty file removed .env
Empty file.
27 changes: 27 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
sudo: required
language: node_js
cache:
yarn: true
notifications:
email: false
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
branches:
only:
- master
env:
global:
- CXX=g++-4.8
- NODE_ENV="test"
node_js:
- "8"
- "10"
- "12"
- "14"
script:
- yarn test
- cat ./coverage/lcov.info | ./node_modules/.bin/coveralls --verbose
7 changes: 7 additions & 0 deletions License
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright 2021 ccharlieli@live.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# alicloud-apsara

This is a library for integrating with AliCloud live-streaming platform - [Apsara](https://www.alibabacloud.com/help/doc-detail/29951.htm?spm=a2c63.p38356.b99.2.2c0d56a2C7EHql).

Integration progress:
- [x] [get domains](https://www.alibabacloud.com/help/doc-detail/88332.htm?spm=a2c63.p38356.b99.143.17872c80zDTOBs)
- [ ] TBD

### How to use
```js
import { Apsara, ApsaraDomainsData } from 'alicloud-apsara'

const apsara = new Apsara({
accessKeyId: 'keyId',
accessKeySecret: 'keySecret'
})

const domainsData: ApsaraDomainsData = await apsara.getDomains()
// Domains: {
// PageData: [
// {
// Description: '',
// LiveDomainStatus: 'online',
// DomainName: 'live-ingest.upstra-china.cc',
// LiveDomainType: 'liveEdge',
// RegionName: 'ap-southeast-1',
// GmtModified: '2020-09-14T14:31:23Z',
// GmtCreated: '2020-09-14T14:27:06Z',
// Cname: 'live-ingest.upstra-china.cc.w.alikunlun.com'
// }
// ]
// },
// TotalCount: 8,
// RequestId: '0500D05A-505D-49B1-B989-BDC9FDAB9BAD',
// PageSize: 20,
// PageNumber: 1
```

### Specs

#### Apsara(options [,logger])
- **options**, required
- `accessKeyId`: string, required
- `accessKeySecret`: string, required
- `baseUrl`?: string, by default `'https://live.aliyuncs.com'`
- `timeout`?: number, by default `3000`
- `version`?: string, by default `'2016-11-01'`
- `signatureMethod`?: string, by default `'HMAC-SHA1'`
- `signatureVersion`?: string, by default `'1.0'`
- `format`?: string, by default `'json'`
- **logger**, optional, WinstonLogger/BunyanLogger/Console

#### [Apsara.getDomains()](https://www.alibabacloud.com/help/doc-detail/88332.htm?spm=a2c63.p38356.b99.143.17872c80zDTOBs)

### How to test

```sh
yarn
yarn test
```

### [MIT License](https://github.com/CCharlieLi/alicloud-apsara/blob/master/License)
33 changes: 23 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
{
"name": "service-template",
"name": "alicloud-apsara",
"version": "0.0.1",
"private": true,
"description": "",
"keywords": [],
"description": "A library for AliCloud live-streaming platform - Apsara",
"keywords": [
"AliCloud",
"Aliyun",
"Live",
"Streaming",
"Apsara",
"Video"
],
"author": "Charlie Li <ccharlieli@live.com>",
"license": "ISC",
"publishConfig": {
"registry": "https://xxx/api/npm/npm-local"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/CCharlieLi/service-template.git"
Expand Down Expand Up @@ -45,8 +49,15 @@
"./dist"
]
},
"dependencies": {},
"dependencies": {
"axios": "^0.21.1",
"crypto": "^1.0.1",
"http-errors": "^1.8.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-angular": "^11.0.0",
"@types/accept-language-parser": "1.5.1",
"@types/bluebird": "3.5.33",
"@types/body-parser": "1.19.0",
Expand All @@ -55,6 +66,7 @@
"@types/express": "4.17.3",
"@types/fs-extra": "8.0.1",
"@types/helmet": "0.0.45",
"@types/http-errors": "^1.8.0",
"@types/intl": "1.2.0",
"@types/ioredis": "4.16.4",
"@types/jest": "~25.1.4",
Expand All @@ -70,6 +82,7 @@
"@types/xml2js": "0.4.5",
"@typescript-eslint/eslint-plugin": "~2.23.0",
"@typescript-eslint/parser": "~2.23.0",
"coveralls": "^3.1.0",
"dotenv": "~8.2.0",
"eslint": "~6.8.0",
"eslint-config-prettier": "~6.10.0",
Expand All @@ -78,9 +91,9 @@
"husky": "~4.2.3",
"jest": "~25.1.0",
"lint-staged": "10.0.8",
"nock": "~11.7.0",
"nock": "^13.0.7",
"nockback-harder": "~4.0.1",
"prettier": "~1.19.1",
"prettier": "^2.2.1",
"supertest": "~4.0.2",
"ts-jest": "~25.2.1",
"ts-mockery": "^1.2.0",
Expand Down
Empty file removed postman/README.md
Empty file.
Empty file removed resources/README.md
Empty file.
118 changes: 118 additions & 0 deletions src/Apsara.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import axios, { AxiosInstance, AxiosResponse } from 'axios'
import { createHmac } from 'crypto'
import createHttpError from 'http-errors'
import { escape } from 'querystring'
import { v4 as uuidv4 } from 'uuid'

import { API_VERSION, BASE_URL, FORMAT, SIGNATURE_METHOD, SIGNATURE_VERSION, TIMEOUT } from './constants'
import { ApsaraDomainsData } from './data/ApsaraDomainsData'
import { ApsaraErrorData } from './data/ApsaraErrorData'
import { AsparaOptions } from './data/ApsaraOptions'
import { ApsaraParams } from './data/ApsaraParams'
import { Logger } from './data/Logger'

export class Apsara {
private options: AsparaOptions
private axios: AxiosInstance
private logger?: Logger

constructor(options: AsparaOptions, logger?: Logger) {
this.options = {
version: API_VERSION,
signatureMethod: SIGNATURE_METHOD,
signatureVersion: SIGNATURE_VERSION,
format: FORMAT,
...options
}
this.logger = logger
this.axios = axios.create({
baseURL: options.baseUrl || BASE_URL,
timeout: options.timeout || TIMEOUT
})
}

/**
* Fetch all domains from current Apsara account
* See https://www.alibabacloud.com/help/zh/doc-detail/88332.htm?spm=a2c63.p38356.b99.153.6e601545HUfO2U
*/
async getDomains(): Promise<ApsaraDomainsData> {
this.logger?.info(`Get domains of current Apsara account`)
return this.request<ApsaraDomainsData>({ Action: 'DescribeLiveUserDomains' })
}

// Private

/**
* Make HTTP request to Apsara open API
* @param {object} params
*/
private async request<T>(params: Record<string, string>): Promise<T> {
this.logger?.info(`Send request to Apsara with payload ${JSON.stringify(params)}`)

// create payload
const method = 'get'
const commonParams: ApsaraParams = {
Format: this.options.format as string,
Version: this.options.version as string,
SignatureMethod: this.options.signatureMethod as string,
SignatureVersion: this.options.signatureVersion as string,
AccessKeyId: this.options.accessKeyId,
SignatureNonce: uuidv4(),
Timestamp: new Date().toISOString()
}
const signature: string = this.generateSignature({ ...commonParams, ...params }, method)

// init request
let response: AxiosResponse<T>
try {
response = await this.axios({
method,
params: {
...commonParams,
...params,
Signature: signature
}
})

const { status, data } = response
this.logger?.info(`Request to Apsara succeed with status ${status} and data ${JSON.stringify(data)}`)
return data as T
} catch (err) {
this.logger?.error(`Request to Apsara failed with response ${JSON.stringify(err.message)}`)

// Apsara business error
if (err?.response?.data?.RequestId) {
const data: ApsaraErrorData = err.response.data
const status = err.response.status
throw createHttpError(status, data.Message, data)
}

// Service error
throw createHttpError(err)
}
}

/**
* Generate signature for Apsara open API
* See https://www.alibabacloud.com/help/zh/doc-detail/50286.htm?spm=a2c63.p38356.b99.145.2b434281pZhqqP
* @param {object} payload
* @param {string} method
*/
private generateSignature(payload: any, method: string): string {
// sort params
const paramsStr = Object.keys(payload)
.map(key => (key === 'Timestamp' ? `${key}=${encodeURIComponent(payload[key])}` : `${key}=${payload[key]}`))
.sort()
.join('&')

// sign
return createHmac('sha1', `${this.options.accessKeySecret}&`)
.update(
`${method.toUpperCase()}&${encodeURIComponent('/')}&${escape(paramsStr)
.replace('+', '%20')
.replace('*', '%2A')
.replace('%7E', '~')}`
)
.digest('base64')
}
}
8 changes: 8 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// https://www.alibabacloud.com/help/zh/doc-detail/50284.htm?spm=a2c63.p38356.b99.144.1cc659c6QMTj5N
export const BASE_URL = 'https://live.aliyuncs.com'
export const TIMEOUT = 3000 // 3s
export const API_VERSION = '2016-11-01'
export const SIGNATURE_METHOD = 'HMAC-SHA1'
export const SIGNATURE_VERSION = '1.0'
export const FORMAT = 'json'
// ResourceOwnerAccount?
22 changes: 22 additions & 0 deletions src/data/ApsaraDomainsData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export interface ApsaraDomainsData {
Domains: PageData
TotalCount: number
RequestId: string
PageSize: number
PageNumber: number
}

interface PageData {
PageData: Domain[]
}

interface Domain {
Description: string
LiveDomainStatus: string
DomainName: string
LiveDomainType: string
RegionName: string
GmtModified: string
GmtCreated: string
Cname: string
}
7 changes: 7 additions & 0 deletions src/data/ApsaraErrorData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface ApsaraErrorData {
RequestId: string
Message: string
Recommend: string
HostId: string
Code: string
}
10 changes: 10 additions & 0 deletions src/data/ApsaraOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface AsparaOptions {
baseUrl?: string
timeout?: number
version?: string
signatureMethod?: string
signatureVersion?: string
accessKeyId: string
accessKeySecret: string
format?: string
}
10 changes: 10 additions & 0 deletions src/data/ApsaraParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface ApsaraParams {
Format: string
Version: string
SignatureMethod: string
SignatureVersion: string
AccessKeyId: string
Signature?: string
SignatureNonce: string
Timestamp: string
}
5 changes: 5 additions & 0 deletions src/data/Logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export declare class Logger {
info(...meta: any[]): void
warn(...meta: any[]): void
error(...meta: any[]): void
}
5 changes: 5 additions & 0 deletions src/data/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './ApsaraDomainsData'
export * from './ApsaraErrorData'
export * from './ApsaraOptions'
export * from './ApsaraParams'
export * from './Logger'
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './Apsara'
export * from './constants'
export * from './data'

0 comments on commit d61ea86

Please sign in to comment.