@rooted-software/piece-sitestacker-http-request 0.1.2

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.
@@ -0,0 +1,29 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Setup Node.js
15
+ uses: actions/setup-node@v4
16
+ with:
17
+ node-version: '20'
18
+ registry-url: 'https://registry.npmjs.org'
19
+
20
+ - name: Install dependencies
21
+ run: npm install
22
+
23
+ - name: Build
24
+ run: npm run build --if-present
25
+
26
+ - name: Publish to npm
27
+ run: npm publish --access public
28
+ env:
29
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_DEPLOY }}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@rooted-software/piece-sitestacker-http-request",
3
+ "version": "0.1.2",
4
+ "description": "HTTP request piece with SiteStacker HMAC authentication",
5
+ "main": "src/index.ts",
6
+ "keywords": [
7
+ "activepieces",
8
+ "piece",
9
+ "http",
10
+ "sitestacker",
11
+ "hmac",
12
+ "authentication",
13
+ "api"
14
+ ],
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/Rooted-Software/piece-sitestacker.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/Rooted-Software/piece-sitestacker/issues"
22
+ },
23
+ "homepage": "https://github.com/Rooted-Software/piece-sitestacker#readme",
24
+ "peerDependencies": {
25
+ "@activepieces/pieces-framework": ">=0.18.0",
26
+ "@activepieces/pieces-common": ">=0.6.0"
27
+ }
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { createPiece, PieceAuth, Property } from '@activepieces/pieces-framework';
2
+ import { sendRequest } from './lib/actions/send-request';
3
+
4
+ export const siteStackerAuth = PieceAuth.CustomAuth({
5
+ description: 'SiteStacker HMAC API authentication credentials',
6
+ required: true,
7
+ props: {
8
+ accessKeyId: Property.ShortText({
9
+ displayName: 'Access Key ID',
10
+ description: 'Found in Users component > right-click user > API Access',
11
+ required: true,
12
+ }),
13
+ secretAccessKey: PieceAuth.SecretText({
14
+ displayName: 'Secret Access Key',
15
+ description: 'The secret key generated alongside the Access Key ID',
16
+ required: true,
17
+ }),
18
+ },
19
+ });
20
+
21
+ export const siteStackerHttpRequest = createPiece({
22
+ displayName: 'SiteStacker HTTP Request',
23
+ description: 'Send HTTP requests with automatic SiteStacker HMAC authentication',
24
+ auth: siteStackerAuth,
25
+ minimumSupportedRelease: '0.20.3',
26
+ logoUrl: 'https://cdn.activepieces.com/pieces/http.png',
27
+ authors: [],
28
+ actions: [sendRequest],
29
+ triggers: [],
30
+ });
@@ -0,0 +1,162 @@
1
+ import { createAction, Property } from '@activepieces/pieces-framework';
2
+ import {
3
+ httpClient,
4
+ HttpMethod,
5
+ HttpRequest,
6
+ } from '@activepieces/pieces-common';
7
+ import { createHmac } from 'crypto';
8
+ import { siteStackerAuth } from '../../index';
9
+
10
+ function formatDateRFC2616(date: Date): string {
11
+ return date.toUTCString().replace('GMT', '+0000');
12
+ }
13
+
14
+ function computeHmacSignature(
15
+ secretKey: string,
16
+ method: string,
17
+ contentType: string,
18
+ dateStr: string
19
+ ): string {
20
+ const stringToSign = `${method}\n${contentType}\n${dateStr}`;
21
+ return createHmac('sha256', secretKey).update(stringToSign).digest('hex');
22
+ }
23
+
24
+ export const sendRequest = createAction({
25
+ auth: siteStackerAuth,
26
+ name: 'send_sitestacker_request',
27
+ displayName: 'Send Request',
28
+ description:
29
+ 'Send an HTTP request with automatic SiteStacker HMAC authentication',
30
+ props: {
31
+ method: Property.StaticDropdown({
32
+ displayName: 'Method',
33
+ required: true,
34
+ defaultValue: HttpMethod.GET,
35
+ options: {
36
+ disabled: false,
37
+ options: [
38
+ { label: 'GET', value: HttpMethod.GET },
39
+ { label: 'POST', value: HttpMethod.POST },
40
+ { label: 'PUT', value: HttpMethod.PUT },
41
+ { label: 'PATCH', value: HttpMethod.PATCH },
42
+ { label: 'DELETE', value: HttpMethod.DELETE },
43
+ ],
44
+ },
45
+ }),
46
+ url: Property.ShortText({
47
+ displayName: 'URL',
48
+ description: 'The full URL to send the request to',
49
+ required: true,
50
+ }),
51
+ headers: Property.Object({
52
+ displayName: 'Headers',
53
+ description:
54
+ 'Additional headers to include. Authorization, Date/ss-date are set automatically.',
55
+ required: false,
56
+ }),
57
+ body_type: Property.StaticDropdown({
58
+ displayName: 'Body Type',
59
+ required: true,
60
+ defaultValue: 'none',
61
+ options: {
62
+ disabled: false,
63
+ options: [
64
+ { label: 'None', value: 'none' },
65
+ { label: 'JSON', value: 'json' },
66
+ { label: 'Form Data', value: 'form_data' },
67
+ { label: 'Raw', value: 'raw' },
68
+ ],
69
+ },
70
+ }),
71
+ body: Property.Json({
72
+ displayName: 'Body (JSON)',
73
+ description: 'Request body when Body Type is JSON',
74
+ required: false,
75
+ }),
76
+ body_raw: Property.LongText({
77
+ displayName: 'Body (Raw)',
78
+ description: 'Request body when Body Type is Raw',
79
+ required: false,
80
+ }),
81
+ timeout: Property.Number({
82
+ displayName: 'Timeout (seconds)',
83
+ required: false,
84
+ defaultValue: 30,
85
+ }),
86
+ },
87
+ async run(context) {
88
+ const { method, url, headers, body_type, body, body_raw, timeout } =
89
+ context.propsValue;
90
+ const { accessKeyId, secretAccessKey } = context.auth;
91
+
92
+ const now = new Date();
93
+ const dateStr = formatDateRFC2616(now);
94
+
95
+ // Determine content type from body type
96
+ let contentType = '';
97
+ let requestBody: unknown = undefined;
98
+ switch (body_type) {
99
+ case 'json':
100
+ contentType = 'application/json';
101
+ requestBody = body;
102
+ break;
103
+ case 'form_data':
104
+ contentType = 'application/x-www-form-urlencoded';
105
+ requestBody = body;
106
+ break;
107
+ case 'raw':
108
+ contentType = 'text/plain';
109
+ requestBody = body_raw;
110
+ break;
111
+ default:
112
+ contentType = '';
113
+ requestBody = undefined;
114
+ }
115
+
116
+ // Compute HMAC signature
117
+ const signature = computeHmacSignature(
118
+ secretAccessKey,
119
+ method,
120
+ contentType,
121
+ dateStr
122
+ );
123
+ const authorization = `HMAC ${accessKeyId}:${signature}`;
124
+
125
+ // Build final headers
126
+ const userHeaders: Record<string, string> = (headers as Record<string, string>) ?? {};
127
+ const hasUserDateHeader =
128
+ Object.keys(userHeaders).some((k) => k.toLowerCase() === 'date');
129
+
130
+ const finalHeaders: Record<string, string> = {
131
+ ...userHeaders,
132
+ Authorization: authorization,
133
+ };
134
+
135
+ if (contentType) {
136
+ finalHeaders['Content-Type'] = contentType;
137
+ }
138
+
139
+ // Use ss-date if user already set a Date header, otherwise set Date
140
+ if (hasUserDateHeader) {
141
+ finalHeaders['ss-date'] = dateStr;
142
+ } else {
143
+ finalHeaders['Date'] = dateStr;
144
+ }
145
+
146
+ const request: HttpRequest = {
147
+ method,
148
+ url,
149
+ headers: finalHeaders,
150
+ body: requestBody,
151
+ timeout: timeout ? timeout * 1000 : 30000,
152
+ };
153
+
154
+ const response = await httpClient.sendRequest(request);
155
+
156
+ return {
157
+ status: response.status,
158
+ headers: response.headers,
159
+ body: response.body,
160
+ };
161
+ },
162
+ });