n8n-nodes-excellent-survey 1.0.0
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/LICENSE +24 -0
- package/README.md +49 -0
- package/dist/assets/n8n.svg +3 -0
- package/dist/credentials/ExcellentSurveyOAuth2Api.credentials.d.ts +12 -0
- package/dist/credentials/ExcellentSurveyOAuth2Api.credentials.js +23 -0
- package/dist/credentials/excellentSurvey.dark.svg +5 -0
- package/dist/credentials/excellentSurvey.svg +5 -0
- package/dist/nodes/ExcellentSurvey/ExcellentSurveyTrigger.node.d.ts +17 -0
- package/dist/nodes/ExcellentSurvey/ExcellentSurveyTrigger.node.js +156 -0
- package/dist/nodes/ExcellentSurvey/ExcellentSurveyTrigger.node.json +15 -0
- package/dist/nodes/ExcellentSurvey/excellentSurvey.dark.svg +5 -0
- package/dist/nodes/ExcellentSurvey/excellentSurvey.svg +5 -0
- package/dist/nodes/ExcellentSurvey/response.d.ts +3 -0
- package/dist/nodes/ExcellentSurvey/response.js +23 -0
- package/examples/watch-responses.json +17 -0
- package/package.json +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Excellent Survey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
Product names and brand marks remain the property of their respective owners;
|
|
24
|
+
this software license does not grant trademark rights.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Excellent Survey for n8n
|
|
2
|
+
|
|
3
|
+
The **Excellent Survey Trigger** receives new completed responses through OAuth2 and automatic subscription registration. It includes the complete typed response and convenient `answersById` / `displayAnswersById` objects. Question IDs remain stable when titles change. `id` is the original event ID.
|
|
4
|
+
|
|
5
|
+
The connector has been tested on a self-hosted n8n **2.38.7** instance with Node **24.21.0**, including OAuth consent, automatic token refresh, survey selection, saved-response delivery, and subscription removal. Public package publication and n8n verification are separate release steps; n8n Cloud availability requires n8n's approval.
|
|
6
|
+
|
|
7
|
+
## Instance setup
|
|
8
|
+
|
|
9
|
+
1. Use Node 24 or newer for development. The connector is built against `n8n-workflow` 2.16.0; its peer dependency follows n8n's required `*` convention, which does not establish compatibility with every historical release. Build and pack this directory using the commands below. Install the resulting tarball in your self-hosted test instance's custom/community nodes location according to its deployment method, then restart that test instance.
|
|
10
|
+
2. Expose n8n's webhook endpoint through **public HTTPS**. The website must be able to reach it. A localhost callback or a private network address is not a supported receiving destination.
|
|
11
|
+
3. Create an **Excellent Survey OAuth2 API** credential. Contact **support@excellentsurvey.com** with its **OAuth Redirect URL** for exact callback registration. Enter the issued client ID and secret in n8n's encrypted credential fields. Then select **Connect my account**, sign in, and approve survey access.
|
|
12
|
+
4. Add **Excellent Survey Trigger**, select the credential and a survey, then activate/publish the workflow in n8n. The node automatically registers its callback. Deactivation removes that callback's subscription. A test listener has a separate subscription so it cannot remove the production subscription.
|
|
13
|
+
5. Complete a designated synthetic fixture survey to verify the receiving workflow, then deactivate it and check that its subscription is removed. Reconnect the credential to authorize additional surveys.
|
|
14
|
+
|
|
15
|
+
No application client secret is bundled in this npm package. Self-hosted n8n instances have different OAuth redirect URLs, so an unregistered instance cannot have universal one-click OAuth. This is the same callback-registration constraint documented for [n8n's Typeform OAuth2 credential](https://docs.n8n.io/integrations/builtin/credentials/typeform/). The user never pastes a webhook URL; the one-time operator step registers the OAuth client, then n8n manages webhook subscriptions.
|
|
16
|
+
|
|
17
|
+
## Build and test
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm ci
|
|
21
|
+
npm run lint
|
|
22
|
+
npm test
|
|
23
|
+
npm pack
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`dist/` contains the credential, trigger and local brand icon. There are no application secrets or arbitrary runtime dependencies in the packed node. Tests compile against the pinned n8n types and mock the n8n request helper to verify survey selection, multiple subscriptions, test/production isolation, deletion retry, typed answer preservation, and event filtering. OAuth token refresh is delegated to n8n's inherited OAuth2 credential/request helper; these tests do not run a hosted n8n OAuth session.
|
|
27
|
+
|
|
28
|
+
The receiver uses n8n's generated callback address, validates event version/type/survey, and ignores synthetic tests. Do not disclose the callback URL. This API version does not return a webhook signing secret to native connectors, so this node does not claim independent HMAC signature verification. Use event `id` for downstream idempotency; n8n does not automatically deduplicate this trigger's workflow executions. Do not discard an event before the receiving action successfully handles it.
|
|
29
|
+
|
|
30
|
+
Only future completed responses are delivered. Renaming a question does not break question-ID mappings; deleting and recreating it produces a different ID. Existing Excellent Survey responses are not modified or backfilled.
|
|
31
|
+
|
|
32
|
+
## Example workflow
|
|
33
|
+
|
|
34
|
+
Import [Watch Responses](examples/watch-responses.json) into your test instance, select your credential and survey, and start the listener. The example is inactive, contains only the trigger, and includes no credentials or response data. Complete a new response to the selected survey to inspect its output before adding destination actions.
|
|
35
|
+
|
|
36
|
+
## Troubleshooting
|
|
37
|
+
|
|
38
|
+
- **No survey options:** reconnect and approve the required survey. Check that the credential belongs to the correct account.
|
|
39
|
+
- **Authorization or access errors:** follow the displayed recovery guidance; reconnect the credential or approve the survey again when required.
|
|
40
|
+
- **No response arrives:** check that the workflow or test listener is listening, complete a new response to the selected survey, and check public HTTPS reachability. Starting a listener does not replay old responses.
|
|
41
|
+
- **Subscription removal fails:** correct the connection or network error and retry deactivation. The node retains its subscription record until deletion succeeds.
|
|
42
|
+
|
|
43
|
+
Contact **support@excellentsurvey.com** for service or callback-registration support.
|
|
44
|
+
|
|
45
|
+
## Publication
|
|
46
|
+
|
|
47
|
+
The repository's manually started **Publish package** GitHub Actions workflow verifies the public repository identity and exact package version, runs lint and tests, and publishes with npm provenance. Publication and n8n verification are separate steps; npm availability does not imply n8n Cloud approval.
|
|
48
|
+
|
|
49
|
+
Before publication, complete real-instance OAuth and delivery tests. See [community-node requirements](https://docs.n8n.io/integrations/community-nodes/building-community-nodes/) and [n8n's built-in Typeform trigger source](https://github.com/n8n-io/n8n/blob/master/packages/nodes-base/nodes/Typeform/TypeformTrigger.node.ts).
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="32" height="26" viewBox="0 0 32 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.2 11.3955C26.4903 11.3959 25.8006 11.1603 25.2394 10.7259C24.6783 10.2914 24.2774 9.68271 24.1 8.99555H20.433C20.0543 8.9956 19.6879 9.12999 19.3989 9.3748C19.11 9.61962 18.9173 9.95899 18.855 10.3325L18.723 11.1225C18.6018 11.8478 18.2346 12.5092 17.683 12.9955C18.2348 13.4821 18.6021 14.1439 18.723 14.8695L18.855 15.6585C18.9173 16.0321 19.11 16.3715 19.3989 16.6163C19.6879 16.8611 20.0543 16.9955 20.433 16.9955H20.901C21.0968 16.2424 21.5603 15.5864 22.2047 15.1502C22.8491 14.714 23.6303 14.5275 24.4023 14.6255C25.1743 14.7236 25.8841 15.0995 26.399 15.6829C26.9139 16.2663 27.1987 17.0174 27.2 17.7955C27.2015 18.5755 26.9182 19.3292 26.4031 19.9149C25.8881 20.5006 25.1769 20.8781 24.4031 20.9764C23.6294 21.0746 22.8464 20.8869 22.2013 20.4485C21.5562 20.0101 21.0935 19.3511 20.9 18.5955H20.433C19.6756 18.5954 18.9428 18.3267 18.3649 17.837C17.787 17.3474 17.4015 16.6687 17.277 15.9215L17.145 15.1325C17.0828 14.759 16.89 14.4196 16.6011 14.1748C16.3121 13.93 15.9457 13.7956 15.567 13.7955H14.299C14.1214 14.4823 13.7206 15.0907 13.1596 15.525C12.5987 15.9593 11.9094 16.1949 11.2 16.1949C10.4906 16.1949 9.80129 15.9593 9.24036 15.525C8.67943 15.0907 8.27866 14.4823 8.10101 13.7955H6.29901C6.1032 14.5487 5.63975 15.2047 4.99533 15.6409C4.35091 16.0771 3.56967 16.2636 2.7977 16.1656C2.02573 16.0675 1.31592 15.6916 0.800999 15.1082C0.286083 14.5247 0.00133389 13.7737 6.20563e-06 12.9955C-0.00152906 12.2156 0.281849 11.4619 0.796882 10.8762C1.31191 10.2905 2.02314 9.91299 2.79689 9.81474C3.57064 9.71649 4.35363 9.90421 4.99871 10.3426C5.6438 10.781 6.10655 11.44 6.30001 12.1955H8.10001C8.27697 11.5079 8.67758 10.8985 9.23878 10.4635C9.79998 10.0284 10.4899 9.79229 11.2 9.79229C11.9101 9.79229 12.6 10.0284 13.1612 10.4635C13.7224 10.8985 14.123 11.5079 14.3 12.1955H15.567C15.9457 12.1955 16.3121 12.0611 16.6011 11.8163C16.89 11.5715 17.0828 11.2321 17.145 10.8585L17.277 10.0685C17.4017 9.32161 17.7873 8.64311 18.3652 8.15368C18.943 7.66425 19.6757 7.39562 20.433 7.39555H24.101C24.2968 6.64242 24.7603 5.9864 25.4047 5.5502C26.0491 5.114 26.8303 4.92747 27.6023 5.02552C28.3743 5.12356 29.0841 5.49945 29.599 6.0829C30.1139 6.66634 30.3987 7.41738 30.4 8.19555C30.4 9.04424 30.0629 9.85817 29.4627 10.4583C28.8626 11.0584 28.0487 11.3955 27.2 11.3955ZM27.2 9.79555C27.6244 9.79555 28.0313 9.62698 28.3314 9.32692C28.6314 9.02686 28.8 8.61989 28.8 8.19555C28.8 7.7712 28.6314 7.36423 28.3314 7.06418C28.0313 6.76412 27.6244 6.59555 27.2 6.59555C26.7757 6.59555 26.3687 6.76412 26.0686 7.06418C25.7686 7.36423 25.6 7.7712 25.6 8.19555C25.6 8.61989 25.7686 9.02686 26.0686 9.32692C26.3687 9.62698 26.7757 9.79555 27.2 9.79555ZM3.20001 14.5955C3.62435 14.5955 4.03132 14.427 4.33138 14.1269C4.63144 13.8269 4.80001 13.4199 4.80001 12.9955C4.80001 12.5712 4.63144 12.1642 4.33138 11.8642C4.03132 11.5641 3.62435 11.3955 3.20001 11.3955C2.77566 11.3955 2.36869 11.5641 2.06864 11.8642C1.76858 12.1642 1.60001 12.5712 1.60001 12.9955C1.60001 13.4199 1.76858 13.8269 2.06864 14.1269C2.36869 14.427 2.77566 14.5955 3.20001 14.5955ZM12.8 12.9955C12.8 13.2057 12.7586 13.4137 12.6782 13.6078C12.5978 13.802 12.48 13.9783 12.3314 14.1269C12.1828 14.2755 12.0064 14.3933 11.8123 14.4738C11.6182 14.5542 11.4101 14.5955 11.2 14.5955C10.9899 14.5955 10.7818 14.5542 10.5877 14.4738C10.3936 14.3933 10.2172 14.2755 10.0686 14.1269C9.92006 13.9783 9.80221 13.802 9.7218 13.6078C9.64139 13.4137 9.60001 13.2057 9.60001 12.9955C9.60001 12.5712 9.76858 12.1642 10.0686 11.8642C10.3687 11.5641 10.7757 11.3955 11.2 11.3955C11.6244 11.3955 12.0313 11.5641 12.3314 11.8642C12.6314 12.1642 12.8 12.5712 12.8 12.9955ZM25.6 17.7955C25.6 18.0057 25.5586 18.2137 25.4782 18.4078C25.3978 18.602 25.28 18.7783 25.1314 18.9269C24.9828 19.0755 24.8064 19.1933 24.6123 19.2738C24.4182 19.3542 24.2101 19.3955 24 19.3955C23.7899 19.3955 23.5818 19.3542 23.3877 19.2738C23.1936 19.1933 23.0172 19.0755 22.8686 18.9269C22.7201 18.7783 22.6022 18.602 22.5218 18.4078C22.4414 18.2137 22.4 18.0057 22.4 17.7955C22.4 17.3712 22.5686 16.9642 22.8686 16.6642C23.1687 16.3641 23.5757 16.1955 24 16.1955C24.4244 16.1955 24.8313 16.3641 25.1314 16.6642C25.4314 16.9642 25.6 17.3712 25.6 17.7955Z" fill="#EA4B71"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class ExcellentSurveyOAuth2Api implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
extends: string[];
|
|
6
|
+
icon: {
|
|
7
|
+
readonly light: "file:excellentSurvey.svg";
|
|
8
|
+
readonly dark: "file:excellentSurvey.dark.svg";
|
|
9
|
+
};
|
|
10
|
+
documentationUrl: string;
|
|
11
|
+
properties: INodeProperties[];
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExcellentSurveyOAuth2Api = void 0;
|
|
4
|
+
class ExcellentSurveyOAuth2Api {
|
|
5
|
+
name = 'excellentSurveyOAuth2Api';
|
|
6
|
+
displayName = 'Excellent Survey OAuth2 API';
|
|
7
|
+
extends = ['oAuth2Api'];
|
|
8
|
+
icon = { light: 'file:excellentSurvey.svg', dark: 'file:excellentSurvey.dark.svg' };
|
|
9
|
+
documentationUrl = 'https://www.npmjs.com/package/n8n-nodes-excellent-survey';
|
|
10
|
+
properties = [
|
|
11
|
+
{
|
|
12
|
+
displayName: 'Contact support@excellentsurvey.com with the OAuth Redirect URL shown above to register your n8n instance. Enter the client ID and secret provided by Excellent Survey, then connect your account.',
|
|
13
|
+
name: 'registrationNotice', type: 'notice', default: '',
|
|
14
|
+
},
|
|
15
|
+
{ displayName: 'Grant Type', name: 'grantType', type: 'hidden', default: 'authorizationCode' },
|
|
16
|
+
{ displayName: 'Authorization URL', name: 'authUrl', type: 'hidden', default: 'https://excellentsurvey.com/connect/authorize' },
|
|
17
|
+
{ displayName: 'Access Token URL', name: 'accessTokenUrl', type: 'hidden', default: 'https://excellentsurvey.com/api/connect/token' },
|
|
18
|
+
{ displayName: 'Scope', name: 'scope', type: 'hidden', default: 'surveys:read responses:read subscriptions:write' },
|
|
19
|
+
{ displayName: 'Authentication', name: 'authentication', type: 'hidden', default: 'body' },
|
|
20
|
+
{ displayName: 'Auth URI Query Parameters', name: 'authQueryParameters', type: 'hidden', default: '' },
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
exports.ExcellentSurveyOAuth2Api = ExcellentSurveyOAuth2Api;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 280 234">
|
|
2
|
+
<path fill="#e8e8e8" fill-rule="evenodd" d="M158.03,6.3c7.67-8.41,20.2-8.41,27.86,0,7.67,8.4,7.67,22.13,0,30.53l-87.69,96.11c-7.67,8.41-20.2,8.41-27.87,0-7.67-8.41-7.67-22.13,0-30.53L158.03,6.3Z"/>
|
|
3
|
+
<path fill="#e8e8e8" fill-rule="evenodd" d="M244.46,101.02c7.67-8.41,20.19-8.41,27.86,0,7.67,8.4,7.67,22.13,0,30.53l-87.69,96.1c-7.67,8.41-20.2,8.41-27.87,0-7.67-8.41-7.67-22.13,0-30.53l87.69-96.1Z"/>
|
|
4
|
+
<path fill="#f7941d" fill-rule="evenodd" d="M84.27,181.86L223.02,29.79c7.67-8.41,20.2-8.41,27.86,0,7.67,8.41,7.67,22.13,0,30.53L98.16,227.71l-.37.39-.37.38-.39.37-.38.34-.79.66-.82.62-.83.56-.83.5-.44.25-.44.23-.89.42-.88.36-.46.16-.45.15-.48.15-.91.24-.46.1-.46.09-.93.14-.96.1-.93.04h-.93l-.96-.05-.93-.1-.93-.15-.46-.09-.48-.11-.92-.25-.46-.14-.46-.16-.46-.17-.89-.37-.87-.41-.45-.24-.44-.25-.85-.52-.82-.57-.82-.63-.75-.65-.39-.36-.38-.37-.37-.38-.31-.33L5.75,156.89c-7.67-8.41-7.67-22.13,0-30.53,7.67-8.41,20.19-8.41,27.87,0l50.65,55.5Z"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 280 234">
|
|
2
|
+
<path fill="#4c4c4c" fill-rule="evenodd" d="M158.03,6.3c7.67-8.41,20.2-8.41,27.86,0,7.67,8.4,7.67,22.13,0,30.53l-87.69,96.11c-7.67,8.41-20.2,8.41-27.87,0-7.67-8.41-7.67-22.13,0-30.53L158.03,6.3Z"/>
|
|
3
|
+
<path fill="#4c4c4c" fill-rule="evenodd" d="M244.46,101.02c7.67-8.41,20.19-8.41,27.86,0,7.67,8.4,7.67,22.13,0,30.53l-87.69,96.1c-7.67,8.41-20.2,8.41-27.87,0-7.67-8.41-7.67-22.13,0-30.53l87.69-96.1Z"/>
|
|
4
|
+
<path fill="#f7941d" fill-rule="evenodd" d="M84.27,181.86L223.02,29.79c7.67-8.41,20.2-8.41,27.86,0,7.67,8.41,7.67,22.13,0,30.53L98.16,227.71l-.37.39-.37.38-.39.37-.38.34-.79.66-.82.62-.83.56-.83.5-.44.25-.44.23-.89.42-.88.36-.46.16-.45.15-.48.15-.91.24-.46.1-.46.09-.93.14-.96.1-.93.04h-.93l-.96-.05-.93-.1-.93-.15-.46-.09-.48-.11-.92-.25-.46-.14-.46-.16-.46-.17-.89-.37-.87-.41-.45-.24-.44-.25-.85-.52-.82-.57-.82-.63-.75-.65-.39-.36-.38-.37-.37-.38-.31-.33L5.75,156.89c-7.67-8.41-7.67-22.13,0-30.53,7.67-8.41,20.19-8.41,27.87,0l50.65,55.5Z"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IHookFunctions, ILoadOptionsFunctions, INodeType, INodeTypeDescription, INodePropertyOptions, IWebhookFunctions, IWebhookResponseData } from 'n8n-workflow';
|
|
2
|
+
export declare class ExcellentSurveyTrigger implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
methods: {
|
|
5
|
+
loadOptions: {
|
|
6
|
+
getSurveys(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
webhookMethods: {
|
|
10
|
+
default: {
|
|
11
|
+
checkExists(this: IHookFunctions): Promise<boolean>;
|
|
12
|
+
create(this: IHookFunctions): Promise<boolean>;
|
|
13
|
+
delete(this: IHookFunctions): Promise<boolean>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExcellentSurveyTrigger = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const response_1 = require("./response");
|
|
6
|
+
const API = 'https://excellentsurvey.com/api/connect/v1';
|
|
7
|
+
async function request(context, method, path, body) {
|
|
8
|
+
try {
|
|
9
|
+
return await context.helpers.httpRequestWithAuthentication.call(context, 'excellentSurveyOAuth2Api', {
|
|
10
|
+
method, url: `${API}${path}`, json: true, ...(body ? { body } : {}),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
const apiError = new n8n_workflow_1.NodeApiError(context.getNode(), error);
|
|
15
|
+
const guidance = {
|
|
16
|
+
'401': 'Reconnect your Excellent Survey account in the selected credential, then try again.',
|
|
17
|
+
'403': 'Reconnect your Excellent Survey account and approve the selected survey and all requested permissions.',
|
|
18
|
+
'404': 'Check that the selected survey still exists and is approved for this connection.',
|
|
19
|
+
'409': 'Your Excellent Survey integration limit has been reached. Remove an unused connection before activating this trigger.',
|
|
20
|
+
'429': 'Wait before trying again. Excellent Survey limits how frequently connections can request data.',
|
|
21
|
+
};
|
|
22
|
+
if (apiError.httpCode && guidance[apiError.httpCode]) {
|
|
23
|
+
apiError.description = guidance[apiError.httpCode];
|
|
24
|
+
}
|
|
25
|
+
throw apiError;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function location(context) {
|
|
29
|
+
const url = context.getNodeWebhookUrl('default');
|
|
30
|
+
let valid = false;
|
|
31
|
+
try {
|
|
32
|
+
if (url) {
|
|
33
|
+
const parsed = new URL(url);
|
|
34
|
+
valid = parsed.protocol === 'https:' && !parsed.username && !parsed.password;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch { /* The configuration message below also covers malformed URLs. */ }
|
|
38
|
+
if (!valid || !url) {
|
|
39
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'The webhook URL must use HTTPS', {
|
|
40
|
+
description: 'Configure a public HTTPS webhook base URL in n8n before activating this trigger.',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Test and production URLs have separate lifecycle records and cannot remove each other.
|
|
44
|
+
const key = `excellentSurveySubscription_${encodeURIComponent(url)}`;
|
|
45
|
+
return { url, key, store: context.getWorkflowStaticData('node') };
|
|
46
|
+
}
|
|
47
|
+
function selectedSurveyId(context) {
|
|
48
|
+
const surveyId = context.getNodeParameter('surveyId');
|
|
49
|
+
if (typeof surveyId !== 'string' || !surveyId.trim()) {
|
|
50
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'A survey must be selected', {
|
|
51
|
+
description: 'Choose a survey in \'Survey Name or ID\' before activating this trigger.',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return surveyId;
|
|
55
|
+
}
|
|
56
|
+
class ExcellentSurveyTrigger {
|
|
57
|
+
description = {
|
|
58
|
+
displayName: 'Excellent Survey Trigger', name: 'excellentSurveyTrigger',
|
|
59
|
+
icon: { light: 'file:excellentSurvey.svg', dark: 'file:excellentSurvey.dark.svg' }, group: ['trigger'], version: 1,
|
|
60
|
+
subtitle: '=Survey: {{$parameter["surveyId"]}}',
|
|
61
|
+
description: 'Starts a workflow when a respondent completes your selected survey',
|
|
62
|
+
defaults: { name: 'Excellent Survey Trigger' },
|
|
63
|
+
inputs: [], outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
|
|
64
|
+
credentials: [{ name: 'excellentSurveyOAuth2Api', required: true }],
|
|
65
|
+
webhooks: [{ name: 'default', httpMethod: 'POST', responseMode: 'onReceived', path: 'response' }],
|
|
66
|
+
properties: [
|
|
67
|
+
{
|
|
68
|
+
displayName: 'Survey Name or ID', name: 'surveyId', type: 'options', default: '', required: true,
|
|
69
|
+
typeOptions: { loadOptionsMethod: 'getSurveys' },
|
|
70
|
+
description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
displayName: 'Reconnect your account to approve more surveys. This trigger subscribes automatically and starts only for new completed responses. Answers use stable question IDs; the complete response stays available.',
|
|
74
|
+
name: 'responseNotice', type: 'notice', default: '',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
methods = {
|
|
79
|
+
loadOptions: {
|
|
80
|
+
async getSurveys() {
|
|
81
|
+
const result = await request(this, 'GET', '/surveys');
|
|
82
|
+
if (!(0, response_1.isDataObject)(result) || !Array.isArray(result.surveys) || !result.surveys.every(survey => (0, response_1.isDataObject)(survey) && typeof survey.id === 'string' && survey.id && typeof survey.title === 'string')) {
|
|
83
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Excellent Survey returned an unsupported survey list', {
|
|
84
|
+
description: 'Try loading the surveys again. If this continues, contact support@excellentsurvey.com.',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const { surveys } = result;
|
|
88
|
+
return surveys.map(survey => ({ name: survey.title, value: survey.id })).sort((a, b) => a.name.localeCompare(b.name));
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
webhookMethods = {
|
|
93
|
+
default: {
|
|
94
|
+
async checkExists() {
|
|
95
|
+
const { store, key } = location(this);
|
|
96
|
+
const surveyId = selectedSurveyId(this);
|
|
97
|
+
const saved = store[key];
|
|
98
|
+
if (!saved || saved.surveyId !== surveyId)
|
|
99
|
+
return false;
|
|
100
|
+
const result = await request(this, 'GET', '/subscriptions');
|
|
101
|
+
if (!(0, response_1.isDataObject)(result) || !Array.isArray(result.subscriptions) || !result.subscriptions.every(subscription => (0, response_1.isDataObject)(subscription) && typeof subscription.id === 'string' && typeof subscription.surveyId === 'string')) {
|
|
102
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Excellent Survey returned an unsupported subscription list', {
|
|
103
|
+
description: 'Try activating the trigger again. If this continues, contact support@excellentsurvey.com.',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const { subscriptions } = result;
|
|
107
|
+
return subscriptions.some(subscription => subscription.id === saved.id && subscription.surveyId === surveyId);
|
|
108
|
+
},
|
|
109
|
+
async create() {
|
|
110
|
+
const { store, key, url } = location(this);
|
|
111
|
+
const surveyId = selectedSurveyId(this);
|
|
112
|
+
const previous = store[key];
|
|
113
|
+
if (previous && previous.surveyId !== surveyId) {
|
|
114
|
+
await request(this, 'DELETE', `/subscriptions/${encodeURIComponent(previous.id)}`);
|
|
115
|
+
delete store[key];
|
|
116
|
+
}
|
|
117
|
+
const created = await request(this, 'POST', '/subscriptions', { surveyId, url });
|
|
118
|
+
if (!(0, response_1.isDataObject)(created) || typeof created.id !== 'string' || !created.id) {
|
|
119
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Excellent Survey did not confirm the new subscription', {
|
|
120
|
+
description: 'Try activating this trigger again. If this continues, contact support@excellentsurvey.com.',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
store[key] = { id: created.id, surveyId, url };
|
|
124
|
+
return true;
|
|
125
|
+
},
|
|
126
|
+
async delete() {
|
|
127
|
+
const { store, key } = location(this);
|
|
128
|
+
const saved = store[key];
|
|
129
|
+
if (saved) {
|
|
130
|
+
await request(this, 'DELETE', `/subscriptions/${encodeURIComponent(saved.id)}`);
|
|
131
|
+
delete store[key];
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
async webhook() {
|
|
138
|
+
const payload = this.getBodyData();
|
|
139
|
+
if (!(0, response_1.isDataObject)(payload))
|
|
140
|
+
return { workflowData: [] };
|
|
141
|
+
const survey = payload.survey;
|
|
142
|
+
if (payload.event !== 'response.completed' || payload.test !== false || survey?.id !== selectedSurveyId(this))
|
|
143
|
+
return { workflowData: [] };
|
|
144
|
+
let record;
|
|
145
|
+
try {
|
|
146
|
+
record = (0, response_1.responseRecord)(payload);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Excellent Survey sent an unsupported response payload', {
|
|
150
|
+
description: 'Update the Excellent Survey node to the latest version. If this continues, contact support@excellentsurvey.com.',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return { workflowData: [this.helpers.returnJsonArray([record])] };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.ExcellentSurveyTrigger = ExcellentSurveyTrigger;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"node": "n8n-nodes-excellent-survey.excellentSurveyTrigger",
|
|
3
|
+
"nodeVersion": "1.0",
|
|
4
|
+
"codexVersion": "1.0",
|
|
5
|
+
"categories": ["Communication"],
|
|
6
|
+
"resources": {
|
|
7
|
+
"credentialDocumentation": [
|
|
8
|
+
{ "url": "https://www.npmjs.com/package/n8n-nodes-excellent-survey" }
|
|
9
|
+
],
|
|
10
|
+
"primaryDocumentation": [
|
|
11
|
+
{ "url": "https://www.npmjs.com/package/n8n-nodes-excellent-survey" }
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"alias": ["Form", "Survey", "Response"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 280 234">
|
|
2
|
+
<path fill="#e8e8e8" fill-rule="evenodd" d="M158.03,6.3c7.67-8.41,20.2-8.41,27.86,0,7.67,8.4,7.67,22.13,0,30.53l-87.69,96.11c-7.67,8.41-20.2,8.41-27.87,0-7.67-8.41-7.67-22.13,0-30.53L158.03,6.3Z"/>
|
|
3
|
+
<path fill="#e8e8e8" fill-rule="evenodd" d="M244.46,101.02c7.67-8.41,20.19-8.41,27.86,0,7.67,8.4,7.67,22.13,0,30.53l-87.69,96.1c-7.67,8.41-20.2,8.41-27.87,0-7.67-8.41-7.67-22.13,0-30.53l87.69-96.1Z"/>
|
|
4
|
+
<path fill="#f7941d" fill-rule="evenodd" d="M84.27,181.86L223.02,29.79c7.67-8.41,20.2-8.41,27.86,0,7.67,8.41,7.67,22.13,0,30.53L98.16,227.71l-.37.39-.37.38-.39.37-.38.34-.79.66-.82.62-.83.56-.83.5-.44.25-.44.23-.89.42-.88.36-.46.16-.45.15-.48.15-.91.24-.46.1-.46.09-.93.14-.96.1-.93.04h-.93l-.96-.05-.93-.1-.93-.15-.46-.09-.48-.11-.92-.25-.46-.14-.46-.16-.46-.17-.89-.37-.87-.41-.45-.24-.44-.25-.85-.52-.82-.57-.82-.63-.75-.65-.39-.36-.38-.37-.37-.38-.31-.33L5.75,156.89c-7.67-8.41-7.67-22.13,0-30.53,7.67-8.41,20.19-8.41,27.87,0l50.65,55.5Z"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 280 234">
|
|
2
|
+
<path fill="#4c4c4c" fill-rule="evenodd" d="M158.03,6.3c7.67-8.41,20.2-8.41,27.86,0,7.67,8.4,7.67,22.13,0,30.53l-87.69,96.11c-7.67,8.41-20.2,8.41-27.87,0-7.67-8.41-7.67-22.13,0-30.53L158.03,6.3Z"/>
|
|
3
|
+
<path fill="#4c4c4c" fill-rule="evenodd" d="M244.46,101.02c7.67-8.41,20.19-8.41,27.86,0,7.67,8.4,7.67,22.13,0,30.53l-87.69,96.1c-7.67,8.41-20.2,8.41-27.87,0-7.67-8.41-7.67-22.13,0-30.53l87.69-96.1Z"/>
|
|
4
|
+
<path fill="#f7941d" fill-rule="evenodd" d="M84.27,181.86L223.02,29.79c7.67-8.41,20.2-8.41,27.86,0,7.67,8.41,7.67,22.13,0,30.53L98.16,227.71l-.37.39-.37.38-.39.37-.38.34-.79.66-.82.62-.83.56-.83.5-.44.25-.44.23-.89.42-.88.36-.46.16-.45.15-.48.15-.91.24-.46.1-.46.09-.93.14-.96.1-.93.04h-.93l-.96-.05-.93-.1-.93-.15-.46-.09-.48-.11-.92-.25-.46-.14-.46-.16-.46-.17-.89-.37-.87-.41-.45-.24-.44-.25-.85-.52-.82-.57-.82-.63-.75-.65-.39-.36-.38-.37-.37-.38-.31-.33L5.75,156.89c-7.67-8.41-7.67-22.13,0-30.53,7.67-8.41,20.19-8.41,27.87,0l50.65,55.5Z"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isDataObject = isDataObject;
|
|
4
|
+
exports.responseRecord = responseRecord;
|
|
5
|
+
function isDataObject(value) {
|
|
6
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function responseRecord(payload) {
|
|
9
|
+
const response = payload.response;
|
|
10
|
+
if (payload.version !== '1' || typeof payload.eventId !== 'string' || !payload.eventId ||
|
|
11
|
+
!isDataObject(payload.survey) || typeof payload.survey.id !== 'string' || !payload.survey.id ||
|
|
12
|
+
!isDataObject(response) || typeof response.id !== 'string' || !response.id ||
|
|
13
|
+
!Array.isArray(response.answers) || !response.answers.every(answer => isDataObject(answer) && typeof answer.questionId === 'string' && answer.questionId.length > 0 && answer.value !== undefined)) {
|
|
14
|
+
throw new Error('Excellent Survey returned an unsupported response payload.');
|
|
15
|
+
}
|
|
16
|
+
const answers = response.answers;
|
|
17
|
+
return {
|
|
18
|
+
...payload,
|
|
19
|
+
id: payload.eventId,
|
|
20
|
+
answersById: Object.fromEntries(answers.map(answer => [String(answer.questionId), answer.value])),
|
|
21
|
+
displayAnswersById: Object.fromEntries(answers.map(answer => [String(answer.questionId), answer.displayValue === undefined ? answer.value : answer.displayValue])),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Excellent Survey - Watch Responses",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"parameters": { "surveyId": "" },
|
|
6
|
+
"id": "b7f3d50f-1265-427f-90f6-d608b451bb5e",
|
|
7
|
+
"name": "Excellent Survey Trigger",
|
|
8
|
+
"type": "n8n-nodes-excellent-survey.excellentSurveyTrigger",
|
|
9
|
+
"typeVersion": 1,
|
|
10
|
+
"position": [260, 300]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"connections": {},
|
|
14
|
+
"active": false,
|
|
15
|
+
"settings": { "executionOrder": "v1" },
|
|
16
|
+
"pinData": {}
|
|
17
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-excellent-survey",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Excellent Survey response trigger with OAuth2 and automatic subscriptions",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://excellentsurvey.com",
|
|
7
|
+
"author": { "name": "Excellent Survey", "email": "support@excellentsurvey.com", "url": "https://github.com/excellentsurvey" },
|
|
8
|
+
"repository": { "type": "git", "url": "git+https://github.com/excellentsurvey/n8n-nodes-excellent-survey.git" },
|
|
9
|
+
"bugs": { "url": "https://github.com/excellentsurvey/n8n-nodes-excellent-survey/issues" },
|
|
10
|
+
"keywords": ["n8n-community-node-package", "survey", "excellent-survey"],
|
|
11
|
+
"engines": { "node": ">=24" },
|
|
12
|
+
"files": ["dist", "README.md", "LICENSE", "examples"],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "n8n-node build",
|
|
15
|
+
"lint": "n8n-node lint",
|
|
16
|
+
"lint:fix": "n8n-node lint --fix",
|
|
17
|
+
"dev": "n8n-node dev",
|
|
18
|
+
"test": "npm run build && node --test tests/*.test.cjs",
|
|
19
|
+
"prepublishOnly": "npm run lint && npm test"
|
|
20
|
+
},
|
|
21
|
+
"n8n": {
|
|
22
|
+
"n8nNodesApiVersion": 1,
|
|
23
|
+
"strict": true,
|
|
24
|
+
"credentials": ["dist/credentials/ExcellentSurveyOAuth2Api.credentials.js"],
|
|
25
|
+
"nodes": ["dist/nodes/ExcellentSurvey/ExcellentSurveyTrigger.node.js"]
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": { "n8n-workflow": "*" },
|
|
28
|
+
"devDependencies": { "@n8n/node-cli": "0.47.2", "n8n-workflow": "2.16.0", "typescript": "5.9.3", "@types/node": "22.15.30" },
|
|
29
|
+
"publishConfig": { "access": "public" }
|
|
30
|
+
}
|