ac-useractionlog-connector 4.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.acsemver.js +9 -0
- package/.github/CODEOWNERS +9 -0
- package/.github/workflows/node.js.yml +33 -0
- package/.ncurc.js +8 -0
- package/CHANGELOG.md +624 -0
- package/Makefile +22 -0
- package/README.md +190 -0
- package/debug.js +43 -0
- package/eslint.config.js +34 -0
- package/index.js +167 -0
- package/package.json +35 -0
- package/test/test.js +255 -0
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# AC UserActionLog Connector
|
|
2
|
+
Connect an app to our userActionLog service with this module.
|
|
3
|
+
|
|
4
|
+
The module receives some log payload from backend applications, sends them to AWS firehose for processing/converting and stores the data (as original as well as converted) to S3.
|
|
5
|
+
|
|
6
|
+
[](https://github.com/AdmiralCloud/c-useractionlog-connector/actions/workflows/node.js.yml)
|
|
7
|
+
|
|
8
|
+
# Breaking change in version 4 / Upgrading from version 3
|
|
9
|
+
+ improved compatibility with AWS SDK
|
|
10
|
+
+ use AWS_PROFILE instead of custom profile
|
|
11
|
+
+ let SDK handle session management (refresh tokens)
|
|
12
|
+
+ function is now a class - so initializing slightly changes
|
|
13
|
+
|
|
14
|
+
To upgrade (your local or non EC2 machines), please read the section "Local development/non EC2 instances"
|
|
15
|
+
|
|
16
|
+
# Breaking change in version 3
|
|
17
|
+
+ works with Node16 or higher
|
|
18
|
+
+ async/await - no callback!
|
|
19
|
+
+ uses AWS IAM roles or AWS IAM profiles instead of IAM credentials
|
|
20
|
+
|
|
21
|
+
# Upgrading from version 1 to 2
|
|
22
|
+
Please note that version 1 is no longer supported. Version 2 uses AWS Kinesis Firehose, AWS Glue and AWS Athena.
|
|
23
|
+
|
|
24
|
+
The data is stored in S3 and you can export/download it from there any time.
|
|
25
|
+
|
|
26
|
+
# Usage
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
// Init during bootstrap
|
|
30
|
+
|
|
31
|
+
const ualConnector = require('ac-useractionlog-connector')
|
|
32
|
+
|
|
33
|
+
ualConnector = new ual({
|
|
34
|
+
// optional config parameters (see below)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// now send logs like this
|
|
38
|
+
await ualConnector.log({ ip, processingTime, statusCode, logMeta: { body: req.body } }, req)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Configuration options
|
|
42
|
+
|Parameter|Default value|Description|
|
|
43
|
+
|-----|----|----|
|
|
44
|
+
|region|eu-central-1|Region for Firehose|
|
|
45
|
+
|deliveryStreamName|UserActionLogs|Name of the stream in Firehose|
|
|
46
|
+
|applicationName|AC-UAL-AppNameNotSet|Name of the logging application|
|
|
47
|
+
|applicationVersion|AppVersionNotSet|Version of the logging application|
|
|
48
|
+
|
|
49
|
+
# AWS Credentials
|
|
50
|
+
Credentials are fetched using the approach described here:
|
|
51
|
+
https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html
|
|
52
|
+
|
|
53
|
+
## Local development/non EC2 instances
|
|
54
|
+
AWS SDK automatically handles expired session tokens if you follow these instructions:
|
|
55
|
+
|
|
56
|
+
1 Download this script -> https://github.com/AdmiralCloud/bashHelper/blob/master/awsConnect/getRoleSession.sh
|
|
57
|
+
2 Prepare the file ~/.aws/config and add a configuration for the role (e.g. ac-api-instance-role) you want to use:
|
|
58
|
+
```
|
|
59
|
+
// dev is the profile name of the IAM user (your IAM user) that is allowed to assume the role.
|
|
60
|
+
// The profile must be defined in ~/.aws/credentials
|
|
61
|
+
|
|
62
|
+
[profile ac-api-instance-role]
|
|
63
|
+
credential_process = /ABSOLUTE_PATH/BashHelpers/awsConnect/getRoleSession.sh "ac-api-instance-role" "dev"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
3 export AWS_PROFILE=role in the project where this connector is used. You can also add it to pm2 config json.
|
|
67
|
+
4 Run the script in your project that uses this connector. The SDK will handle everything else for you!
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Preparation
|
|
71
|
+
## Glue
|
|
72
|
+
Create a manual table in Glue:
|
|
73
|
+
|
|
74
|
+
Name it "UAL-Default", select the database "useractionlog" (or create it if not existing), choose Type "Kinesis" with Location "UserActionLog" and the Kinesis URL "https://glue.eu-central-1.amazonaws.com". Select data format JSON. And then use the following fields:
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
| Pos | Field | Type |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
1 | ip | string |
|
|
80
|
+
2 | method | string |
|
|
81
|
+
3 | controller | string |
|
|
82
|
+
4 | action | string |
|
|
83
|
+
5 | statuscode | int |
|
|
84
|
+
6 | processingtime | int |
|
|
85
|
+
7 | userId | int |
|
|
86
|
+
8 | customerId | int |
|
|
87
|
+
9 | mediaContainerId | int |
|
|
88
|
+
10 | created | bigint |
|
|
89
|
+
11 | createdAt | timestamp |
|
|
90
|
+
12 | platform | string |
|
|
91
|
+
13 | platformVersion | string |
|
|
92
|
+
14 | clientVersion | string |
|
|
93
|
+
15 | token | string |
|
|
94
|
+
16 | clientid | string |
|
|
95
|
+
17 | deviceidentifier | string |
|
|
96
|
+
18 | useragent | string |
|
|
97
|
+
19 | payload | string |
|
|
98
|
+
20 | truncated | boolean |
|
|
99
|
+
21 | response | string |
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## AWS Firehose
|
|
103
|
+
First you have to create a delivery stream (Firehose) in AWS Kinesis. Name it "UserActionLogs", select "Direct PUT or other sources".
|
|
104
|
+
|
|
105
|
+
On the next page, choose "Convert record format" and select "Apache Parquet" format. Use the table definition from Glue (above).
|
|
106
|
+
|
|
107
|
+
The destination should be S3 logbucket with prefix "ual/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/". The error prefixx can be "ual_errors/".
|
|
108
|
+
|
|
109
|
+
Also select S3 backup and use the logbucket and prefix "userActionLogs.
|
|
110
|
+
|
|
111
|
+
Finally, create the delivery stream.
|
|
112
|
+
|
|
113
|
+
# Analysis
|
|
114
|
+
To analyze your data you have to create a Athena database and query it.
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
### Create the table
|
|
118
|
+
Before running the query below, consider the following:
|
|
119
|
+
+ If you don't need the whole month, but only a day, remove "day" from partition and update the location with the day (day=xx)
|
|
120
|
+
+ The fewer data is indexed, the faster the process.
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
// Create the table in database useractionlog
|
|
124
|
+
CREATE EXTERNAL TABLE IF NOT EXISTS ual_202011 (
|
|
125
|
+
ip string,
|
|
126
|
+
method string,
|
|
127
|
+
controller string,
|
|
128
|
+
action string,
|
|
129
|
+
statusCode int,
|
|
130
|
+
processingtime int,
|
|
131
|
+
userId int,
|
|
132
|
+
customerId int,
|
|
133
|
+
mediaContainerId int,
|
|
134
|
+
createdAt timestamp,
|
|
135
|
+
platform string,
|
|
136
|
+
clientVersion string,
|
|
137
|
+
payload string,
|
|
138
|
+
token string,
|
|
139
|
+
clientId string,
|
|
140
|
+
deviceIdentifier string,
|
|
141
|
+
response boolean,
|
|
142
|
+
response string
|
|
143
|
+
userAgent string,
|
|
144
|
+
created bigint,
|
|
145
|
+
)
|
|
146
|
+
PARTITIONED BY (
|
|
147
|
+
day string,
|
|
148
|
+
hour string
|
|
149
|
+
)
|
|
150
|
+
STORED AS PARQUET
|
|
151
|
+
LOCATION 's3://logs.admiralcloud.live.fra/ual/year=2020/month=11/'
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
After creation and before querying, you need to load the partition using the following command:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
MSCK REPAIR TABLE ual_202011;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
When querying data, please consider reducing the data to parse using the partions created above
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
// Example Query using partitions
|
|
164
|
+
|
|
165
|
+
SELECT * FROM "useractionlog"."ual_202011"
|
|
166
|
+
where hour >= '14' AND hour < '15'
|
|
167
|
+
limit 10;
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
# Error handling
|
|
172
|
+
If creating a Glue table is not working via AWS console, you can use CLI but have to edit/add field definitions etc later. So use this only as fallback!
|
|
173
|
+
```
|
|
174
|
+
// FALLBACK
|
|
175
|
+
aws glue create-table \
|
|
176
|
+
--database-name useractionlog \
|
|
177
|
+
--table-input '{
|
|
178
|
+
"Name":"ual-default", "StorageDescriptor":{}}' \
|
|
179
|
+
--endpoint https://glue.eu-central-1.amazonaws.com
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
## Links
|
|
185
|
+
- [Website](https://www.admiralcloud.com/)
|
|
186
|
+
- [Twitter (@admiralcloud)](https://twitter.com/admiralcloud)
|
|
187
|
+
- [Facebook](https://www.facebook.com/MediaAssetManagement/)
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
Copyright mmpro
|
package/debug.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const ual = require('./index')
|
|
2
|
+
|
|
3
|
+
const connector = new ual({})
|
|
4
|
+
|
|
5
|
+
const params = {
|
|
6
|
+
ip: '8.8.8.8',
|
|
7
|
+
statusCode: 200,
|
|
8
|
+
processingTime: 51,
|
|
9
|
+
userId: 123,
|
|
10
|
+
customerId: 147,
|
|
11
|
+
platform: 'APIv5',
|
|
12
|
+
requestPayload: {
|
|
13
|
+
type: 'video'
|
|
14
|
+
},
|
|
15
|
+
responsePayload: {
|
|
16
|
+
id: '123',
|
|
17
|
+
type: 'video'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const req = {
|
|
22
|
+
authInfo: {
|
|
23
|
+
token: 'token-123-abc',
|
|
24
|
+
clientId: 'clientId-456-111',
|
|
25
|
+
deviceIdentifier: 'myDevice',
|
|
26
|
+
clientVersion: '11.04.34455'
|
|
27
|
+
},
|
|
28
|
+
method: 'post',
|
|
29
|
+
options: {
|
|
30
|
+
controller: 'mediacontainer',
|
|
31
|
+
action: 'create'
|
|
32
|
+
},
|
|
33
|
+
headers: {
|
|
34
|
+
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36'
|
|
35
|
+
},
|
|
36
|
+
allParams: () => {
|
|
37
|
+
return params.requestPayload
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setInterval(async() => {
|
|
42
|
+
await connector.log(params, req)
|
|
43
|
+
}, 15000)
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const globals = require('globals')
|
|
2
|
+
|
|
3
|
+
module.exports = [
|
|
4
|
+
{
|
|
5
|
+
files: ['index.js', 'test/test.js'],
|
|
6
|
+
languageOptions: {
|
|
7
|
+
ecmaVersion: 2022,
|
|
8
|
+
sourceType: 'module',
|
|
9
|
+
globals: {
|
|
10
|
+
...globals.commonjs,
|
|
11
|
+
...globals.es2015,
|
|
12
|
+
...globals.node,
|
|
13
|
+
expect: 'readonly',
|
|
14
|
+
describe: 'readonly',
|
|
15
|
+
it: 'readonly'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
rules: {
|
|
19
|
+
'no-const-assign': 'error',
|
|
20
|
+
'space-before-function-paren': 'off',
|
|
21
|
+
'no-extra-semi': 'off',
|
|
22
|
+
'object-curly-spacing': ['error', 'always'],
|
|
23
|
+
'brace-style': ['error', 'stroustrup', { allowSingleLine: true }],
|
|
24
|
+
'block-spacing': 'error',
|
|
25
|
+
'no-useless-escape': 'off',
|
|
26
|
+
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
|
27
|
+
'no-unused-vars': 'error',
|
|
28
|
+
'eqeqeq': 'error',
|
|
29
|
+
'no-var': 'error',
|
|
30
|
+
'curly': 'error',
|
|
31
|
+
'prefer-const': ['error', { ignoreReadBeforeAssign: true }]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
]
|
package/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const _ = require('lodash')
|
|
2
|
+
|
|
3
|
+
const { FirehoseClient, PutRecordCommand } = require("@aws-sdk/client-firehose")
|
|
4
|
+
const https = require('https')
|
|
5
|
+
const agent = new https.Agent({
|
|
6
|
+
keepAlive: true
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
// ATTN: export AWS_PROFILE=ac-api-instance-role (or whatever role is used)
|
|
10
|
+
|
|
11
|
+
class FirehoseManager {
|
|
12
|
+
constructor({ region = 'eu-central-1', deliveryStreamName = 'UserActionLogs', applicationName= 'AC-UAL-AppNameNotSet', applicationVersion = 'AppVersionNotSet', debug }) {
|
|
13
|
+
this.debug = debug
|
|
14
|
+
const awsParams = {
|
|
15
|
+
region,
|
|
16
|
+
httpOptions: { agent }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.firehose = {}
|
|
20
|
+
this.firehose.deliveryStreamName = deliveryStreamName
|
|
21
|
+
this.firehose.applicationName = applicationName
|
|
22
|
+
this.firehose.applicationVersion = applicationVersion
|
|
23
|
+
this.firehose.client = new FirehoseClient(awsParams)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async microTimeString () {
|
|
27
|
+
const time = new Date().getTime()
|
|
28
|
+
const hrtime = process.hrtime()
|
|
29
|
+
const id = time + '.' + (hrtime[0] * 1e9 + hrtime[1])
|
|
30
|
+
return id.toString().substring(0, 20)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Trim response object without deep recursion:
|
|
34
|
+
// - keeps all top-level keys
|
|
35
|
+
// - truncates arrays at top level and one level deep to max 3 items
|
|
36
|
+
trimData(data) {
|
|
37
|
+
try {
|
|
38
|
+
const obj = JSON.parse(data)
|
|
39
|
+
for (const key of Object.keys(obj)) {
|
|
40
|
+
if (Array.isArray(obj[key])) {
|
|
41
|
+
obj[key] = obj[key].slice(0, 3).map(item => {
|
|
42
|
+
if (item && typeof item === 'object') {
|
|
43
|
+
for (const k of Object.keys(item)) {
|
|
44
|
+
if (typeof item[k] === 'string' && item[k].length > 500) {
|
|
45
|
+
item[k] = item[k].slice(0, 500) + '...'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return item
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
else if (obj[key] && typeof obj[key] === 'object') {
|
|
53
|
+
for (const subKey of Object.keys(obj[key])) {
|
|
54
|
+
if (Array.isArray(obj[key][subKey])) {
|
|
55
|
+
obj[key][subKey] = obj[key][subKey].slice(0, 3)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (typeof obj[key] === 'string' && obj[key].length > 500) {
|
|
60
|
+
obj[key] = obj[key].slice(0, 500) + '...'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return JSON.stringify(obj)
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
console.error('AC-UserActionLog-Connector | Failed to parse JSON for trimming')
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async log(params, req) {
|
|
73
|
+
const created = parseInt(this.microTimeString())
|
|
74
|
+
const platform = _.toLower(_.get(params, 'platform', this.firehose.applicationName))
|
|
75
|
+
const platformVersion = _.toLower(_.get(params, 'platformVersion', this.firehose.applicationVersion))
|
|
76
|
+
|
|
77
|
+
const requestParams = _.get(params, 'requestPayload', req && req.allParams())
|
|
78
|
+
|
|
79
|
+
const obscureFields = [
|
|
80
|
+
{ field: 'password', search: /.+/g, replace: 'XXXX-XXXX' }
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
_.forEach(obscureFields, field => {
|
|
84
|
+
if (_.get(requestParams, field.field)) { _.set(requestParams, field.field, _.get(requestParams, field.field).replace(field.search, field.replace)) }
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
let mediaContainerId = _.get(params, 'mediaContainerId') || _.get(requestParams, 'mediaContainerId') || _.get(params, 'responsePayload.mediaContainerId')
|
|
88
|
+
if (!mediaContainerId && _.get(req, 'options.controller') === 'mediacontainer' && _.get(req, 'options.action') === 'create') { mediaContainerId = _.get(params, 'responsePayload.id') }
|
|
89
|
+
|
|
90
|
+
const clientId = _.get(req, 'authInfo.clientId', _.get(requestParams, 'client_id'))
|
|
91
|
+
const deviceIdentifier = _.get(req, 'authInfo.deviceIdentifier', _.get(requestParams, 'device'))
|
|
92
|
+
const clientVersion = _.get(req, 'authInfo.clientVersion', _.get(requestParams, 'clientVersion'))
|
|
93
|
+
const loginuid = _.get(params, 'loginuid')
|
|
94
|
+
|
|
95
|
+
const now = new Date()
|
|
96
|
+
const ualPayload = {
|
|
97
|
+
ip: _.get(params, 'ip'),
|
|
98
|
+
iso2: _.get(params, 'iso2'),
|
|
99
|
+
method: _.get(req, 'method'),
|
|
100
|
+
controller: _.get(req, 'options.controller'),
|
|
101
|
+
action: _.get(req, 'options.action'),
|
|
102
|
+
statusCode: _.get(params, 'statusCode'),
|
|
103
|
+
processingTime: _.get(params, 'processingTime'),
|
|
104
|
+
userId: _.get(req, 'user.id', _.get(params, 'userId')),
|
|
105
|
+
customerId: _.get(req, 'user.customerId', _.get(params, 'customerId')),
|
|
106
|
+
accessKey: _.get(params, 'accessKey'),
|
|
107
|
+
mediaContainerId,
|
|
108
|
+
created,
|
|
109
|
+
createdAt: now.toISOString().slice(0,10) + ' ' + now.toISOString().slice(11,23),
|
|
110
|
+
platform,
|
|
111
|
+
platformVersion,
|
|
112
|
+
clientVersion,
|
|
113
|
+
loginuid,
|
|
114
|
+
clientId,
|
|
115
|
+
deviceIdentifier,
|
|
116
|
+
userAgent: _.get(req, 'headers.user-agent'),
|
|
117
|
+
responseLength: 0,
|
|
118
|
+
payload: JSON.stringify(requestParams),
|
|
119
|
+
response: JSON.stringify(_.get(params, 'responsePayload'))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const recordLength = Buffer.byteLength(JSON.stringify(ualPayload), 'utf8')
|
|
123
|
+
|
|
124
|
+
if (recordLength > 1024000) {
|
|
125
|
+
const t0 = performance.now()
|
|
126
|
+
ualPayload.truncatedResponse = true
|
|
127
|
+
ualPayload.response = this.trimData(ualPayload.response)
|
|
128
|
+
|
|
129
|
+
// reduce request payload if still too large after trimming response
|
|
130
|
+
if (Buffer.byteLength(JSON.stringify(ualPayload), 'utf8') > 1024000) {
|
|
131
|
+
ualPayload.payload = this.trimData(ualPayload.payload)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// final fallback if still too large - drop response entirely but keep request payload
|
|
135
|
+
if (Buffer.byteLength(JSON.stringify(ualPayload), 'utf8') > 1024000) {
|
|
136
|
+
ualPayload.response = null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.warn('AC-UserActionLog-Connector | Took %sms | Record truncated from %s to %s bytes', (performance.now() - t0).toFixed(2), recordLength, Buffer.byteLength(JSON.stringify(ualPayload), 'utf8'))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (process.env.NODE_ENV === 'test') {
|
|
143
|
+
return ualPayload
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const awsParams = {
|
|
147
|
+
DeliveryStreamName: this.firehose.deliveryStreamName,
|
|
148
|
+
Record: {
|
|
149
|
+
Data: Buffer.from(JSON.stringify(ualPayload))
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const command = new PutRecordCommand(awsParams)
|
|
153
|
+
try {
|
|
154
|
+
const r = await this.firehose.client.send(command)
|
|
155
|
+
if (this.debug) {
|
|
156
|
+
const c = await this.firehose.client.config.credentials()
|
|
157
|
+
console.warn('UAL | FIREHOSE', c?.accessKeyId, c?.expiration, r?.$metadata?.httpStatusCode)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch(e) {
|
|
161
|
+
console.error('AC-UserActionLog-Connector | Failed %j', e?.message)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
module.exports = FirehoseManager
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ac-useractionlog-connector",
|
|
3
|
+
"author": "Mark Poepping (https://www.admiralcloud.com)",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": "admiralcloud/ac-useractionlog-connector",
|
|
6
|
+
"version": "4.0.25",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@aws-sdk/client-firehose": "^3.1014.0",
|
|
9
|
+
"@eslint/js": "^10.0.1",
|
|
10
|
+
"globals": "^17.4.0",
|
|
11
|
+
"install": "^0.13.0",
|
|
12
|
+
"lodash": "^4.17.23"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"ac-jenkins": "git+ssh://git@ac-jenkins.github.com/admiralcloud/ac-jenkins#v1.2.10",
|
|
16
|
+
"ac-semantic-release": "^0.4.10",
|
|
17
|
+
"chai": "^4.5.0",
|
|
18
|
+
"eslint": "^10.1.0",
|
|
19
|
+
"mocha": "^11.7.5"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "NODE_ENV=test mocha --reporter spec"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20.0.0"
|
|
26
|
+
},
|
|
27
|
+
"resolutions": {
|
|
28
|
+
"mocha/chokidar/braces": "^3.0.3",
|
|
29
|
+
"mocha/diff": "^8.0.3",
|
|
30
|
+
"minimatch": "^10.2.1",
|
|
31
|
+
"fast-xml-parser": "^5.3.8",
|
|
32
|
+
"mocha/serialize-javascript": "^7.0.3"
|
|
33
|
+
},
|
|
34
|
+
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
35
|
+
}
|