Skip to content

Commit 29ac629

Browse files
committed
Initial
1 parent 49f4eb1 commit 29ac629

22 files changed

+1660
-3
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
export default {
2+
3+
isString(input) {
4+
return typeof input === "string";
5+
},
6+
7+
isNumber(input) {
8+
return typeof input === "number";
9+
},
10+
11+
isEmptyString(input) {
12+
if (this.isString(input)) {
13+
if (input.trim() === "") return true;
14+
};
15+
16+
return false;
17+
},
18+
19+
isIdNumber(input) {
20+
return Number.isInteger(input) && input > 0;
21+
},
22+
23+
isObject(input) {
24+
return (
25+
typeof input === "object" &&
26+
input !== null &&
27+
!Array.isArray(input)
28+
);
29+
},
30+
31+
isArrayOfStrings(input) {
32+
33+
if (!Array.isArray(input)) return false;
34+
35+
for (let i = 0; i < input.length; i++) {
36+
if (!this.isString(input[i]))
37+
return false;
38+
};
39+
40+
return true;
41+
},
42+
43+
isFormData(input) {
44+
return (
45+
typeof input === "object" &&
46+
input !== null &&
47+
typeof input.getHeaders === "function" &&
48+
typeof input.append === "function"
49+
);
50+
},
51+
52+
/* ==============================================================================================
53+
Return the trimmed string or input
54+
as is if it's not a string
55+
==================================================================================================*/
56+
trimIfString(input) {
57+
return (typeof input === "string")
58+
? input.trim()
59+
: input;
60+
},
61+
62+
/* =============================================================================================
63+
Function tries to parse the input as JSON,
64+
If it is not return the value as it was passed
65+
//==============================================================================================*/
66+
parseIfJSONString(input) {
67+
68+
if (typeof input === "string") {
69+
try {
70+
return JSON.parse(input);
71+
} catch (error) {
72+
// Parsing failed — return original input
73+
return input;
74+
}
75+
}
76+
77+
// If input is not a string, just return it as-is
78+
return input;
79+
},
80+
81+
/* =============================================================================================
82+
Validates a URL string:
83+
- Rejects blank strings, spaces, tabs, and newlines
84+
- Warns about suspicious or unusual characters
85+
- Adds a warning if protocol is missing or malformed
86+
================================================================================================ */
87+
checkIfUrlValid(input) {
88+
89+
// Warning accumulator
90+
let warnings = [];
91+
92+
if (!this.isString(input)) {
93+
warnings.push("URL is not a string");
94+
95+
};
96+
97+
if (this.isEmptyString(input)) {
98+
warnings.push("URL is empty string");
99+
return warnings;
100+
};
101+
102+
const trimmedInput = input.trim();
103+
104+
// Reject if spaces, tabs, or newlines are present
105+
if ((/[ \t\n]/.test(trimmedInput))) {
106+
warnings.push( "Url contains invalid characters like space, backslash etc., please check." +
107+
this._reasonMsg(input));
108+
return warnings;
109+
};
110+
111+
// Warn about suspicious characters
112+
const simpleSuspiciousChars = /[\\[\]<>"^`]/g;
113+
const invisibleChars = /\u200B|\u200C|\u200D|\u2060|\uFEFF|\u00A0/g;
114+
115+
const dubiousMatches =
116+
trimmedInput.match(simpleSuspiciousChars) ||
117+
trimmedInput.match(invisibleChars);
118+
119+
if (dubiousMatches) {
120+
warnings.push(" URL contains dubious or non-standard characters " + this._reasonMsg(input) );
121+
};
122+
123+
// urlObject for further use if the next check passes.
124+
let urlObject;
125+
// Tries to create a new URL object with the input string;
126+
try {
127+
urlObject = new URL(trimmedInput); // throws if invalid or has no protocol
128+
129+
// Warn if user typed only one slash (e.g., https:/)
130+
if (/^(https?):\/(?!\/)/.test(input)) {
131+
132+
warnings.push(` It looks like you're missing one slash after "${urlObject.protocol}".` +
133+
`Did you mean "${urlObject.protocol}//..."? ${this._reasonMsg(input)} `);
134+
135+
};
136+
137+
} catch (err) {
138+
// If the URL is invalid, try to create a new URL object with "http://"
139+
// in case user forgot to add it;
140+
try {
141+
// If it works then there was no protocol in the input string;
142+
urlObject = new URL("http://" + trimmedInput);
143+
144+
warnings.push(" URL does not have http or https protocol \"");
145+
146+
} catch (err) {
147+
warnings.push(" URL contains potentionally unacceptable characters\"" +
148+
this._reasonMsg(input));
149+
150+
};
151+
152+
};
153+
154+
return warnings;
155+
156+
},
157+
158+
/* =============================================================================================
159+
Validates an email string:
160+
- Warns about empty, non-string, or whitespace-only values
161+
- Warns about invalid characters, malformed structure, or suspicious format
162+
- Ensures basic email format (e.g., [email protected])
163+
============================================================================================== */
164+
165+
checkIfEmailValid(input) {
166+
const warnings = [];
167+
168+
if (!this.isString(input)) {
169+
warnings.push("Email is not a string.");
170+
return warnings;
171+
}
172+
173+
const trimmedInput = input.trim();
174+
175+
if (this.isEmptyString(trimmedInput)) {
176+
warnings.push("Email is an empty string.");
177+
return warnings;
178+
}
179+
180+
// Reject obvious invalid characters
181+
const suspiciousChars = /[\\[\]<>^`"(),;:]/g;
182+
if (suspiciousChars.test(trimmedInput)) {
183+
warnings.push(
184+
"Email contains suspicious or invalid characters. " +
185+
this._reasonMsg(input)
186+
);
187+
}
188+
189+
// Contains space, tab, or newline
190+
if (/[ \t\n]/.test(trimmedInput)) {
191+
warnings.push(
192+
"Email contains spaces or newline characters. " +
193+
this._reasonMsg(input)
194+
);
195+
}
196+
197+
// Rough structural check
198+
const basicPattern = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
199+
if (!basicPattern.test(trimmedInput)) {
200+
warnings.push(
201+
"Email does not appear to be in a valid format (e.g., [email protected]). " +
202+
this._reasonMsg(input)
203+
);
204+
}
205+
return warnings;
206+
},
207+
208+
/* ===========================================================================================
209+
Validates a Site Identifier string, which may be either:
210+
- A numeric Site ID (e.g., "123456")
211+
- A custom or subdomain (e.g., "mysite.example.com")
212+
=============================================================================================== */
213+
214+
checkDomainOrId(input) {
215+
216+
const warnings = [];
217+
218+
// If it's an ID like number or string (e.g 12345 or "12345");
219+
// it's Valid. Return empty warnings array.
220+
if (this.isIdNumber(Number(input))) return warnings;
221+
222+
// If it's not a string.
223+
if (!this.isString(input)) {
224+
warnings.push("Provided value is not a domain or ID-like value (e.g., 1234 or '1234').");
225+
return warnings;
226+
}
227+
228+
const trimmed = input.trim();
229+
230+
// Now treat it as a domain and run checks:
231+
if (/https?:\/\//.test(trimmed)) {
232+
warnings.push("Domain contains protocol (http or https). Remove it." +
233+
this._reasonMsg(input));
234+
}
235+
236+
if (/[^a-zA-Z0-9.-]/.test(trimmed)) {
237+
warnings.push("Domain. Only letters, numbers, dots, and dashes are allowed." +
238+
this._reasonMsg(input));
239+
}
240+
241+
if (!trimmed.includes(".")) {
242+
warnings.push("Domain should contain at least one dot (e.g. example.com)." +
243+
this._reasonMsg(input));
244+
}
245+
246+
return warnings;
247+
},
248+
249+
/* =============================================================================================
250+
Throws if axios request fails.
251+
Determines whether an error originated from your own validation code or from the API request.
252+
Useful for debugging and crafting more helpful error messages.
253+
=================================================================================================*/
254+
throwCustomError(mainMessage, error, warnings = []) {
255+
256+
const thrower = error?.response?.status
257+
? "API response"
258+
: "Internal Code";
259+
260+
throw new Error(` ${mainMessage} ( ${thrower} error ) : ${error.message}. ` + "\n- " +
261+
warnings.join("\n- "));
262+
},
263+
264+
/* ==============================================================================================
265+
Appends a reason string to error messages for additional context.
266+
=============================================================================================== */
267+
268+
_reasonMsg(reason) {
269+
270+
return (reason && typeof reason === "string")
271+
? ` Reason: ${reason} `
272+
: "";
273+
},
274+
275+
};
276+

components/drift/common/utils.mjs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const removeNullEntries = (obj) =>
2+
obj && Object.entries(obj).reduce((acc, [
3+
key,
4+
value,
5+
]) => {
6+
const isNumber = typeof value === "number";
7+
const isBoolean = typeof value === "boolean";
8+
const isNotEmpyString = typeof value === "string" && value.trim() !== "";
9+
const isNotEmptyArray = Array.isArray(value) && value.length;
10+
const isNotEmptyObject =
11+
typeof value === "object" &&
12+
value !== null &&
13+
!Array.isArray(value) &&
14+
Object.keys(value).length !== 0;
15+
isNotEmptyObject && (value = removeNullEntries(value));
16+
return ((value || value === false) &&
17+
(isNotEmpyString || isNotEmptyArray || isNotEmptyObject || isBoolean || isNumber))
18+
? {
19+
...acc,
20+
[key]: value,
21+
}
22+
: acc;
23+
}, {});
24+
25+
export {
26+
removeNullEntries,
27+
};
28+

components/drift/drift.app.mjs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,82 @@
1+
import { axios } from "@pipedream/platform";
2+
import methods from "./common/methods.mjs"
3+
14
export default {
25
type: "app",
36
app: "drift",
47
propDefinitions: {},
58
methods: {
6-
// this.$auth contains connected account data
7-
authKeys() {
8-
console.log(Object.keys(this.$auth));
9+
...methods,
10+
_baseUrl() {
11+
return "https://driftapi.com";
912
},
13+
14+
_makeRequest({
15+
$ = this,
16+
path,
17+
method = "GET",
18+
contentType,
19+
...opts
20+
}) {
21+
22+
//console.log(opts);
23+
//return;
24+
return axios($, {
25+
method,
26+
url: `${this._baseUrl()}${path}`,
27+
headers: {
28+
Authorization: `Bearer ${this.$auth?.oauth_access_token || "iHlC8LmFQiTH0DcWds7ETMRMmo3BvUyP"}`,
29+
"Content-Type": contentType || "application/json",
30+
},
31+
...opts,
32+
});
33+
},
34+
35+
getContactByEmail(opts) {
36+
return this._makeRequest({
37+
path: "/contacts",
38+
...opts,
39+
});
40+
},
41+
42+
createContact(opts) {
43+
return this._makeRequest({
44+
method: "POST",
45+
path: "/contacts",
46+
...opts,
47+
});
48+
},
49+
50+
51+
updateContactById(contactId, opts) {
52+
const attributes = {
53+
name: opts.name,
54+
phone: opts.phone,
55+
...opts.customAttributes,
56+
};
57+
58+
return this._makeRequest({
59+
method: "PUT",
60+
path: `/contacts/${contactId}`,
61+
data: { attributes },
62+
});
63+
},
64+
65+
// 4. Fetch user information using an end-user ID
66+
getUserByEndUserId(endUserId) {
67+
return this._makeRequest({
68+
method: "GET",
69+
path: `/users/${endUserId}`,
70+
});
71+
},
72+
73+
// 7. Delete a contact by ID
74+
deleteContactById(contactId) {
75+
return this._makeRequest({
76+
method: "DELETE",
77+
path: `/contacts/${contactId}`,
78+
});
79+
},
80+
1081
},
1182
};

0 commit comments

Comments
 (0)