Giter Site home page Giter Site logo

jotform-api-nodejs's People

Contributors

appaky avatar denisozgovde avatar dependabot[bot] avatar diki avatar eeertekin avatar elifceren avatar fatiiates avatar gokayform avatar hasansoydabas avatar kennethpdev avatar samjetski avatar umutbugrahan avatar utkubekci avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jotform-api-nodejs's Issues

npm install error

node -v
v7.7.1

npm i jotform --save gives

Failed to compile.

Error in .//jotform//sntp/lib/index.js
Module not found: 'dgram' in /react-app/node_modules/jotform/node_modules/sntp/lib

@ .//jotform//sntp/lib/index.js 3:12-28

Error in .//jotform//sntp/lib/index.js
Module not found: 'dns' in /react-app/node_modules/jotform/node_modules/sntp/lib

@ .//jotform//sntp/lib/index.js 4:10-24

Filter "form_id" doesn't work for "GET /user/forms"

Check out this method of Node.js SDK, it doesn't filter by formID GET /user/forms

Try yourself.

See, I specify limit=100 but I also specify to filter result by two formIDs only. So I have to receive just 2 items. But I receive full 100 items. How it can be??
id filter key doesn't work either

https://api.jotform.com/user/forms?apiKey=...&filter="{"form_id":[827356126,9384673498]}"&limit=100&orderby=created_at

// Encoding URI component doesn't do the job either:
https://api.jotform.com/user/forms?apiKey=&filter="%7B%22form_id%22%3A%5B4065204955%2C5309169199%5D%7D"&limit=100&orderby=created_at


{
    responseCode: 200,
    resultSet: {
        offset: 0,
        limit: 100,
        orderby: "created_at",
        filter: ""{"form_id":[60422050068949,53609169109965]}"", // <==== the filter
        count: 100  // <==== HOW IS IT POSSIBLE??? Must be only 2
    },
    message: "success",
    content: [ 100 items here ]
}

How to get images as answers to questions

I have a question like: vehicle_image5
When I read this with the getFormSubmissions api call I get back all the answers. But for the image I get back the data like:

{
    "widget_metadata": {
        "type": "imagelinks",
        "value": [
            {
                "name": "AC Cobra 6.jpg",
                "url": "/widget-uploads/imagepreview/123123123123/123AC_Cobra_665bffac78e450.jpg"
            }
        ]
    }
}

but how can I get to this image to download the jpg file while authorized by the API?
I know that the image is at https://files.jotform.com/jotformWidgets/imagepreview/123123123123/123AC_Cobra_665bffac78e450.jpg but of course I have no rights to wget or curl it directly.

Error in node v11.10.1 If `timeout` is not specified

TypeError [ERR_INVALID_ARG_TYPE]: The "msecs" argument must be of type number. Received type object

node has several changes to handle socket timeout and agent here nodejs/node@949e885#diff-5f7fb0850412c6be189faeddea6c5359R180. This impacts jotform as the default value for timeout is null

sending request with timeout: added fix the issue

TypeError: Cannot read property 'statusCode' of undefined

Your system throws an error when requesting getFormQuestions:

            if(response.statusCode != 200){
                       ^
TypeError: Cannot read property 'statusCode' of undefined
    at Request._callback (C:\Users\user\node\app\node_modules\jotform\index.js:42:24)
    at self.callback (C:\Users\user\node\app\node_modules\jotform\node_modules\request\index.js:148:22)
    at emitOne (events.js:96:13)
    at Request.emit (events.js:188:7)
    at ClientRequest.self.clientErrorHandler (C:\Users\user\node\app\node_modules\jotform\node_modules\request\index.js:257:10)
    ....

Usecase:

const jf = require("jotform");

jf.options({
    apiKey: api_key,
    debug: debug,
});

jf.getForms({
    offset: 0,
    limit: 300, // I set only 300, and response has just 150.
    filter: {}
})
.then(forms => {

    // forms is an array with > 100 entries
    // I iterate it and make a request for every formId to "getFormQuestions(form.id)"
    return Promise.all( forms.map(form => {
        return jf.getFormQuestions(form.id)
            .then(formQ => {
                return {
                    form: form,
                    formQ: formQ
                }
            });
    }));
})
.then(resp => {
    console.log(resp);
})
.fail(err => {
    console.log(err);
});

What error is this? How to get rid of it and get responses without errors?

TypeError: Jotform is not a constructor

I followed the instructions from the readme to import the Jotform class using esm imports

node version 18.19.0

import Jotform from 'jotform'
const client = new Jotform('mytoken')

TypeError: Jotform is not a constructor

Changing to

import JotformPackage from 'jotform'
const Jotform = JotformPackage.default
const client = new Jotform('mytoken')

solves the problem

formId filter for "GET /user/forms" endpoint

Could you please implement a formId filter for GET /user/forms endpoint?
And please don't limit it 29 units as you usually like to do with other filters. Let it be not less than 100, though the more the better. Example:

https://api.jotform.com/user/forms?filter={"ids": ["FormID1","FormID2","FormID3",... and not less than 100]}

With that filter we could avoid looping GET /form/{id} endpoint.

It is notorious that your servers are very weak when query something from your servers with a loop.

For instance, when requesting only 10 form questions (GET /form/id/questions) in a loop, your servers may respond longer than 2 minutes, or throw error, or just freeze.

Example. We want to avoid doing this. Your servers throw errors very often when looped.

        let promises = formIds.map(id => {
            let p = new Promise((success, fail) => {
                request('https://api.jotform.com/form/' + id + '?apikey={...}', function (error, response) {
                    if (error !== null) {
                        fail(error);
                    } else {
                        success(response.body);
                    }
                });
            });
            return p;
        });
    
    Promise.all(promises);

While waiting for the filter we will use this approach. So please be aware that we will query your servers with loops very intensively.

Hot To Use?

CODE:

const jf = require("JotForm");
jf.options({
	debug: true,
	apiKey: 'b***93**2ca31***c995**79fe******'
});
jf.getFormSubmissions('90**3054***661')
            .then(function(r){

            })
            .fail(function(e){

            });

ERROR:

GET to URL: http://api.jotform.com/user?apiKey=b***93**2ca31***c995**79fe******
internal/validators.js:126
    throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "timeout" argument must be of type number. Received null
    at validateNumber (internal/validators.js:126:11)
    at getTimerDuration (internal/timers.js:376:3)
    at new ClientRequest (_http_client.js:170:20)
    at Object.request (http.js:47:10)
    at Request.start (C:\Users\Mateus\Desktop\Área de Trabalho\BOT Discord\JotFormBOT\node_modules\request\index.js:594:30)
    at Request.end (C:\Users\Mateus\Desktop\Área de Trabalho\BOT Discord\JotFormBOT\node_modules\request\index.js:1186:28)
    at C:\Users\Mateus\Desktop\Área de Trabalho\BOT Discord\JotFormBOT\node_modules\request\index.js:436:12
    at processTicksAndRejections (internal/process/task_queues.js:79:11) {
  code: 'ERR_INVALID_ARG_TYPE'
}

Error: connect ETIMEDOUT 104.23.128.7:443

What's going on with your servers? I constantly get this error when requesting you API. Constantly means 3-4 times for every 5 requests. It is not possible at all to use your API.

api_getsubmissions - google chrome 2016-12-25 19 30 45

{
    Error: connect ETIMEDOUT 104.23.128.7:443
        at Object.exports._errnoException (util.js:1012:11)
        at exports._exceptionWithHostPort (util.js:1035:20)
        at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1080:14)

    code: 'ETIMEDOUT',
    errno: 'ETIMEDOUT',
    syscall: 'connect',
    address: '104.23.128.7',
    port: 443
}

IP address lookup reveals that that IP is connected with your organisation.

What error is this?
Why can't your servers serve my requests?
Can you fix it?
How to make requests while you will fix your issues?

Which should be installed, jotform-api-nodejs or jotform?

Could you please elaborate which module should be installed and used, jotform-api-nodejs or jotform?

Read here http://api.jotform.com/docs/:

NodeJS Client Library
npm install jotform-api-nodejs

Now read your README:

Installation
npm install jotform

And they both can be installed, and have different versions. This is from package.json after installation:

    "jotform": "0.0.8",
    "jotform-api-nodejs": "0.0.2",

How can it be? Which one should be used?

ReferenceError: fullText is not defined

What error is this?

I run jotform.getForms({}) with Nodejs SDK and constantly get this error.

C:\Users\user\app\node_modules\jotform\index.js:102
            (fullText !== undefined ? "&fullText=" + fullText : "") +
             ^

ReferenceError: fullText is not defined
    at Object.exports.getForms (C:\Users\user\app\node_modules\jotform\index.js:102:14)
    at Object.<anonymous> (C:\Users\user\app\routes\routes.js:20:4)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)
    at Module.require (module.js:468:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (C:\Users\user\app\app.js:25:14)
  1. It doesn't matter if I provide fullText option to the method or don't - the error is throw anyhow.
    Neither jotform.getForms({ limit: 10 }) nor jotform.getForms({ limit: 10, fullText: 'foo'} ) works. Error it thrown in both cases.
  2. Read your docs and your codebase for getForms method of Node SDK. There is no ANY mention of fullText for getForms method in the docs. Then why did you include it in the code?

Your docs:

jotform api - google chrome 2016-10-10 13 51 52

You code in getForms method:

jotforms api - sublime text unregistered 2016-10-10 13 55 19

Guys, you collect money for your service. Every month! Be plz serious about the service you offer!

Still empty response for GET /user/submissions

Earlier today you claimed here #16 that issues were fixed.
Now, six hours later the issue is still not fixed.

no subs response

I want to test new features on real live data and I can't do it. When will you fix it?

Vulnerable Dependencies in Package

Running npm audit in a project with Jotform installed returns this result amongst others:

node_modules/tunnel-agent
  request  2.2.6 - 2.86.0
  Depends on vulnerable versions of form-data
  Depends on vulnerable versions of hawk
  Depends on vulnerable versions of http-signature
  Depends on vulnerable versions of mime
  Depends on vulnerable versions of qs
  Depends on vulnerable versions of tunnel-agent
  node_modules/request
    jotform  *
    Depends on vulnerable versions of request
    node_modules/jotform

The main issue here is that Jotform depends on a vulnerable version of request

TypeScript typings

Since I had to write these anyway, I'm sharing TypeScript typings for this package in case somebody needs them. It would be also a neat base for potential TypeScript migration in the future.

They are far from being ideal; I was not able to find a definite documentation for each and every call, so this is my "best guess" so that I have ANYTHING to work with.

/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars */

declare module 'jotform' {
  type ControlOptions = string; // optionone|optiontwo|optionthree…
  type ControlValues = string; // valueone|valuetwo|valuethree…
  type EnableDisableFlag = 'enable' | 'disable';
  type EnableDisableUppercaseFlag = 'Enable' | 'Disable';
  type HintOptions = string; // optionone,optiontwo,optionthree…
  type HorizontalAlign = 'Auto' | 'Center' | 'Left' | 'Right' | string; // TODO: Add all possible values
  type ISODate = string;
  type NumberLikeString = `${number}`;
  type Special =
    | 'Canadian Provinces'
    | 'Countries'
    | 'Days'
    | 'Gender'
    | 'Last 100 Years'
    | 'Months'
    | 'None'
    | 'Time Zones'
    | 'US States Abbr'
    | 'US States';
  type StringifiedJson<T> = string;
  type Url = string;
  type Validation =
    | 'Alphabetic'
    | 'AlphaNumeric'
    | 'Currency'
    | 'Cyrillic'
    | 'Email'
    | 'Numeric'
    | 'Url'
    | 'None';
  type VerticalAlign = 'Top' | 'Middle' | 'Bottom' | string; // TODO: Add all possible values
  type Weekday = 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday';
  type WidgetType = 'native' | string; // TODO: Add all possible values
  type YesNoFlag = 'Yes' | 'No';
  type ZeroOneFlag = '0' | '1';

  export type Options = {
    apiKey?: string;
    debug?: boolean;
    url?: string;
    version?: string;
    timeout?: number;
  };

  type Filter = Record<string, any>;

  type Query = {
    direction?: 'ASC' | 'DESC';
    filter?: Filter;
    limit?: number;
    offset?: number;
    orderby?: any;
  };

  export type User = {
    username: string;
    name: string;
    email: string;
    website: Url | null;
    time_zone: string;
    account_type: Url;
    status: 'ACTIVE' | string; // TODO: Add all possible values
    created_at: ISODate;
    updated_at: ISODate | null;
    region: 'EMEA' | string; // TODO: Add all possible values
    is_verified: ZeroOneFlag;
    usage: Url;
    euOnly: ZeroOneFlag;
    new_users_campaign: string;
    loginToGetSubmissions: ZeroOneFlag;
    loginToViewUploadedFiles: ZeroOneFlag;
    loginToViewSubmissionRSS: ZeroOneFlag;
    showJotFormPowered: ZeroOneFlag;
    defaultTheme: string;
    submission_inbox_group: string;
    avatarUrl: Url;
    formListingsTestUser: ZeroOneFlag;
    company_logo: Url;
    company: string;
    tablesIntroVideoShown: YesNoFlag;
    disableViewLimits: boolean;
    allowBoards: boolean;
    allowDigest: boolean;
    allowPrefills: boolean;
    allowWorkflowFeatures: boolean;
    allowAutoDeleteSubmissions: boolean;
    teamsBetaUser: ZeroOneFlag;
    paymentNewProductManagement: boolean;
    allowEncryptionV2: boolean;
  };

  export type Usage = {
    submissions: string;
    ssl_submissions: string;
    payments: string;
    uploads: string;
    total_submissions: string;
    signed_documents: string;
    pdf_attachment_submissions: string;
    mobile_submissions: string;
    views: string;
    api: number;
    form_count: string;
  };

  export type Form<K extends string = string> = {
    id: K;
    username: string;
    title: string;
    height: NumberLikeString;
    status: 'ENABLED' | 'DELETED' | string; // TODO: Add all possible values
    created_at: ISODate;
    updated_at: ISODate | null;
    last_submission: string | null;
    new: ZeroOneFlag;
    count: NumberLikeString;
    type: 'LEGACY' | 'CARD';
    favorite: ZeroOneFlag;
    archived: ZeroOneFlag;
    url: Url;
  };

  export type FormQuestionBase<K extends string = string> = {
    hidden?: YesNoFlag;
    name: string;
    order: NumberLikeString;
    protected?: YesNoFlag;
    qid: K;
    text: string;
    type: string;
  };

  export type FormQuestionControlBase<K extends string = string> = FormQuestionBase<K> & {
    labelAlign: HorizontalAlign;
    readonly?: YesNoFlag;
    required?: YesNoFlag;
    subLabel?: string;
    useCalculations?: YesNoFlag;
  };

  export type FormQuestionComplexBase<K extends string = string> = FormQuestionBase<K> & {
    labelAlign: HorizontalAlign;
    readonly?: YesNoFlag;
    required?: YesNoFlag;
  };

  export type FormQuestionAddress<K extends string = string> = FormQuestionComplexBase<K> & {
    compoundHint: HintOptions;
    countryDropdownDefaultText: string;
    customCountryDropdown: YesNoFlag;
    customCountryOptions: string;
    customStateOptions?: '';
    description?: string;
    hasAutocomplete: YesNoFlag;
    hasGeolocation: YesNoFlag;
    requiredInputs: ControlOptions;
    selectedCountry: string;
    states: 'americanStates' | 'textbox';
    subfields: ControlOptions;
    sublabels: {
      addr_line1: string;
      addr_line2: string;
      city: string;
      state: string;
      postal: string;
      country: string;
    };
    type: 'control_address';
  };

  export type FormQuestionButton<K extends string = string> = FormQuestionBase<K> & {
    buttonAlign: HorizontalAlign;
    buttonStyle?: 'None' | string; // TODO: Add all possible values
    clear?: YesNoFlag;
    clearText?: string;
    encryptIcon?: YesNoFlag;
    print?: YesNoFlag;
    printText?: string;
    type: 'control_button';
  };

  export type FormQuestionCheckbox<K extends string = string> = FormQuestionControlBase<K> & {
    allowOther?: YesNoFlag;
    calcValues?: ControlValues;
    description?: string;
    maxSelection?: NumberLikeString | '';
    minSelection?: NumberLikeString | '';
    options: ControlOptions;
    otherText?: string;
    selected?: string;
    shuffle?: YesNoFlag;
    special?: Special;
    spreadCols?: NumberLikeString;
    type: 'control_checkbox';
  };

  export type FormQuestionDatetime<K extends string = string> = FormQuestionComplexBase<K> & {
    ageVerification: YesNoFlag;
    allowTime: YesNoFlag;
    autoCalendar: YesNoFlag;
    dateSeparator: '-' | '/' | '.';
    days: ControlOptions; // Localized names of days
    description?: string;
    defaultDate: 'none' | 'current' | `custom_${string}-${string}-${string}`;
    defaultTime: 'none' | 'current' | `custom_${string}:${string}`;
    format: string;
    limitDate: StringifiedJson<{
      days: {
        monday: boolean;
        tuesday: boolean;
        wednesday: boolean;
        thursday: boolean;
        friday: boolean;
        saturday: boolean;
        sunday: boolean;
      };
      future: true;
      past: boolean;
      custom: boolean;
      ranges: boolean;
      start: ISODate | '';
      end: ISODate | '';
    }>;
    liteMode: YesNoFlag;
    minAge: NumberLikeString;
    months: ControlOptions; // Localized names of months
    onlyFuture: YesNoFlag;
    startWeekOn: Weekday;
    step: NumberLikeString;
    sublabels: {
      day: string;
      month: string;
      year: string;
      last: string;
      hour: string;
      minutes: string;
      litemode: string;
    };
    timeFormat: 'AM/PM' | '24 Hour';
    today: string; // Localized "Today"
    validateLiteDate: YesNoFlag;
    type: 'control_datetime';
  };

  export type FormQuestionDivider<K extends string = string> = FormQuestionBase<K> & {
    color: string;
    height: NumberLikeString;
    lineStyle: 'solid' | 'dotted' | 'dashed';
    margin: NumberLikeString;
    protected?: never;
    spaceAbove: NumberLikeString;
    spaceBelow: NumberLikeString;
    type: 'control_divider';
  };

  export type FormQuestionDropdown<K extends string = string> = FormQuestionControlBase<K> & {
    autoFixed?: YesNoFlag;
    calcValues?: ControlValues;
    description?: string;
    emptyText?: string;
    multipleSelections?: YesNoFlag;
    options: ControlOptions;
    searchText?: string;
    selected?: string;
    shuffle?: YesNoFlag;
    size?: NumberLikeString;
    special?: Special;
    type: 'control_dropdown';
    visibleOptions?: NumberLikeString;
    width?: NumberLikeString;
  };

  export type FormQuestionEmail<K extends string = string> = FormQuestionControlBase<K> & {
    allowCustomDomains: YesNoFlag;
    allowedDomains: string;
    autoFixed?: YesNoFlag;
    confirmation: YesNoFlag;
    confirmationHint: string;
    confirmationSublabel: string;
    defaultValue: string;
    description?: string;
    disallowFree: YesNoFlag;
    domainCheck: YesNoFlag;
    hint: string;
    maxsize: NumberLikeString | '';
    shrink?: YesNoFlag;
    size?: NumberLikeString;
    validation?: Extract<Validation, 'Email'>;
    verificationCode: YesNoFlag;
    type: 'control_email';
  };

  export type FormQuestionFullname<K extends string = string> = FormQuestionComplexBase<K> & {
    compoundHint: HintOptions;
    description?: string;
    middle: YesNoFlag;
    prefix: YesNoFlag;
    prefixChoices: ControlOptions;
    sublabels: {
      prefix: string;
      first: string;
      middle: string;
      last: string;
      suffix: string;
    };
    suffix: YesNoFlag;
    type: 'control_fullname';
  };

  export type FormQuestionHead<K extends string = string> = FormQuestionBase<K> & {
    alt?: string;
    headerImage?: string;
    headerType: 'Default' | 'Large' | 'Small';
    imageAlign: HorizontalAlign;
    nextButtonText?: string;
    previousButtonText?: string;
    protected?: never;
    showQuestionCount?: YesNoFlag;
    subHeader: string;
    textAlign: HorizontalAlign;
    type: 'control_head';
    verticalTextAlign: VerticalAlign;
    width?: string;
  };

  export type FormQuestionHidden<K extends string = string> = FormQuestionBase<K> & {
    browserInfo: YesNoFlag;
    defaultValue: string;
    type: 'control_hidden';
    labelAlign: HorizontalAlign;
    selectedField: string;
    widgetType: WidgetType;
  };

  export type FormQuestionImage<K extends string = string> = FormQuestionBase<K> & {
    align?: HorizontalAlign;
    alt: string;
    description?: string;
    height: NumberLikeString;
    labelText: string;
    link: Url | '';
    protected?: never;
    src: Url;
    target: '_blank' | '_self' | '_parent' | '_top';
    type: 'control_image';
    width: NumberLikeString;
  };

  export type FormQuestionNumber<K extends string = string> = FormQuestionControlBase<K> & {
    autoFixed: YesNoFlag;
    defaultValue: string;
    description?: string;
    hint: string;
    maxsize: NumberLikeString | '';
    maxValue: NumberLikeString | '';
    minValue: NumberLikeString | '';
    shrink?: YesNoFlag;
    size?: NumberLikeString;
    type: 'control_number';
  };

  export type FormQuestionPagebreak<K extends string = string> = FormQuestionBase<K> & {
    autoNext: YesNoFlag;
    backText: string;
    backVisi: 'Visible' | 'Hidden';
    buttonStyle: 'None' | string; // TODO: Add all possible values
    nextText: string;
    nextVisi: 'Visible' | 'Hidden';
    pageInfo: string;
    type: 'control_pagebreak';
  };

  export type FormQuestionPhone<K extends string = string> = FormQuestionComplexBase<K> & {
    compoundHint: HintOptions;
    countryCode: YesNoFlag;
    description?: string;
    inputMask: EnableDisableFlag;
    inputMaskValue: string;
    sublabels: {
      country: string;
      area: string;
      phone: string;
      full: string;
      masked: string;
    };
    shrink?: YesNoFlag;
    size?: NumberLikeString;
    type: 'control_phone';
  };

  export type FormQuestionRadio<K extends string = string> = FormQuestionControlBase<K> & {
    allowOther?: YesNoFlag;
    calcValues?: ControlValues;
    description?: string;
    options: ControlOptions;
    otherText?: string;
    selected?: string;
    shuffle?: YesNoFlag;
    special?: Special;
    spreadCols?: NumberLikeString;
    type: 'control_radio';
  };

  export type FormQuestionText<K extends string = string> = FormQuestionBase<K> & {
    type: 'control_text';
  };

  export type FormQuestionTextbox<K extends string = string> = FormQuestionControlBase<K> & {
    autoFixed?: YesNoFlag;
    defaultValue?: string;
    description?: string;
    hint?: string;
    inputTextMask?: string;
    maxsize?: NumberLikeString | '';
    minsize?: NumberLikeString | '';
    shrink?: YesNoFlag;
    size?: NumberLikeString;
    type: 'control_textbox';
    validation?: Validation;
  };

  type FormQuestionTextareaV1<K extends string = string> = FormQuestionControlBase<K> & {
    cols: NumberLikeString;
    maxsize?: NumberLikeString | '';
    rows: NumberLikeString;
    type: 'control_textarea';
  };

  type FormQuestionTextareaV2<K extends string = string> = FormQuestionControlBase<K> & {
    autoFixed: YesNoFlag;
    cols: NumberLikeString; // Height in pixels, NOT rows
    defaultValue: string;
    description?: string;
    entryLimit: string;
    entryLimitMin: string;
    hint: string;
    maxsize?: NumberLikeString | '';
    mde: YesNoFlag;
    rows: NumberLikeString; // Height in pixels, NOT rows
    type: 'control_textarea';
    validation?: Validation;
    wysiwyg: EnableDisableUppercaseFlag;
  };

  export type FormQuestionTextarea<K extends string = string> =
    | FormQuestionTextareaV1<K>
    | FormQuestionTextareaV2<K>;

  export type FormQuestion<K extends string = string> =
    | FormQuestionAddress<K>
    | FormQuestionButton<K>
    | FormQuestionCheckbox<K>
    | FormQuestionDatetime<K>
    | FormQuestionDivider<K>
    | FormQuestionDropdown<K>
    | FormQuestionEmail<K>
    | FormQuestionFullname<K>
    | FormQuestionHead<K>
    | FormQuestionHidden<K>
    | FormQuestionImage<K>
    | FormQuestionNumber<K>
    | FormQuestionPagebreak<K>
    | FormQuestionPhone<K>
    | FormQuestionRadio<K>
    | FormQuestionText<K>
    | FormQuestionTextbox<K>
    | FormQuestionTextarea<K>;

  export type FormQuestions = { [K in string]: FormQuestion<K> };

  type SubmissionAnswerBase<T> = Omit<T, 'answer'> & {
    name: string;
    order: NumberLikeString;
    text: string;
    type: string;
    answer?: any;
  };

  type SubmissionAnswerAddress = SubmissionAnswerBase<{
    answer?: {
      addr_line1?: string;
      addr_line2?: string;
      city?: string;
      state?: string;
      postal?: string;
      country?: string;
    };
    prettyFormat?: string;
  }>;

  type SubmissionAnswerButton = SubmissionAnswerBase<{ answer?: never }>;

  type SubmissionAnswerCheckbox = SubmissionAnswerBase<{ answer?: string[] }>;

  type SubmissionAnswerDatetime = SubmissionAnswerBase<{ answer?: any }>; // TODO

  type SubmissionAnswerDivider = SubmissionAnswerBase<{ answer?: never }>;

  type SubmissionAnswerDropdown = SubmissionAnswerBase<{ answer?: string }>;

  type SubmissionAnswerEmail = SubmissionAnswerBase<{ answer?: string }>;

  type SubmissionAnswerFullname = SubmissionAnswerBase<{
    answer?: {
      prefix?: string;
      first?: string;
      middle?: string;
      last?: string;
      suffix?: string;
    };
    prettyFormat?: string;
  }>;

  type SubmissionAnswerHead = SubmissionAnswerBase<{ answer?: never }>;

  type SubmissionAnswerImage = SubmissionAnswerBase<{ answer?: never }>;

  type SubmissionAnswerNumber = SubmissionAnswerBase<{ answer?: any }>; // TODO

  type SubmissionAnswerPhone = SubmissionAnswerBase<{
    answer?: {
      country: string;
      area: string;
      phone: string;
      full: string;
      masked: string;
    };
    prettyFormat?: string;
  }>;

  type SubmissionAnswerRadio = SubmissionAnswerBase<{ answer?: string }>;

  type SubmissionAnswerText = SubmissionAnswerBase<{ answer?: never }>;

  type SubmissionAnswerTextbox = SubmissionAnswerBase<{ answer?: string }>;

  type SubmissionAnswerTextarea = SubmissionAnswerBase<{ answer?: string }>;

  type SubmissionAnswer =
    | SubmissionAnswerButton
    | SubmissionAnswerCheckbox
    | SubmissionAnswerDatetime
    | SubmissionAnswerDivider
    | SubmissionAnswerDropdown
    | SubmissionAnswerEmail
    | SubmissionAnswerFullname
    | SubmissionAnswerHead
    | SubmissionAnswerImage
    | SubmissionAnswerNumber
    | SubmissionAnswerPhone
    | SubmissionAnswerRadio
    | SubmissionAnswerText
    | SubmissionAnswerTextbox
    | SubmissionAnswerTextarea;

  export type Submission<K1 extends string = string, K2 extends string = string> = {
    id: K2;
    form_id: K1;
    ip: string;
    created_at: ISODate;
    status: 'ACTIVE' | 'CUSTOM' | 'DELETED' | string; // TODO: Add all possible values
    new: ZeroOneFlag;
    flag: ZeroOneFlag;
    notes: string;
    updated_at: ISODate | null;
    answers: {
      [K in string]: SubmissionAnswer;
    };
  };

  export type Subuser = Record<string, any>; // TODO

  export type Folder<K extends string = string> = {
    id: K;
    path: string;
    owner: string;
    name: string;
    parent: string;
    color: string;
    forms: Form[];
    subfolders: Folder[];
  };

  export type FolderRoot = {
    id: string;
    path: string;
    owner: string;
    name: string;
    parent: 'root';
    color: string;
    forms: {
      [K in string]: Form<K>;
    };
    subfolders: Folder[];
  };

  export type Report<K extends string = string> = Record<string, any>; // TODO

  type FolderLayoutNode = {
    [K in string]: {
      nodes: FolderLayoutNode;
    };
  };

  export type Settings = {
    username: string;
    name: string;
    email: string;
    website: string | null;
    time_zone: string;
    account_type: Url;
    status: 'ACTIVE' | string; // TODO: Add all possible values
    created_at: ISODate;
    updated_at: ISODate | null;
    region: 'EMEA' | string; // TODO: Add all possible values
    is_verified: ZeroOneFlag;
    usage: Url;
    euOnly: ZeroOneFlag;
    new_users_campaign: string;
    loginToGetSubmissions: ZeroOneFlag;
    loginToViewUploadedFiles: ZeroOneFlag;
    loginToViewSubmissionRSS: ZeroOneFlag;
    showJotFormPowered: ZeroOneFlag;
    defaultTheme: string;
    submission_inbox_group: string;
    avatarUrl: Url;
    formListingsTestUser: ZeroOneFlag;
    company_logo: Url;
    company: string;
    tablesIntroVideoShown: YesNoFlag;
    folderLayout: StringifiedJson<FolderLayoutNode>;
    disableViewLimits: boolean;
  };

  export type HistoryEntry = Record<string, any>; // TODO

  export type FormFile<K1 extends string = string, K2 extends string = string> = {
    id: K2;
    name: string;
    type: string;
    size: string;
    username: string;
    form_id: K1;
    submission_id: string;
    uploaded: string;
    date: string;
    url: Url;
  };

  export type FormWebhook = string;

  type ConditionActionBase = {
    id: string;
    isError: boolean;
  };

  export type ConditionVisibilityActionSingle = ConditionActionBase & {
    visibility: 'Show' | 'Hide' | 'Require' | 'Unrequire';
    field: string;
  };

  export type ConditionVisibilityActionMultiple = ConditionActionBase & {
    visibility: 'ShowMultiple' | 'HideMultiple' | 'RequireMultiple' | 'UnrequireMultiple';
    fields: string[];
  };

  export type ConditionVisibilityAction =
    | ConditionVisibilityActionSingle
    | ConditionVisibilityActionMultiple;

  export type ConditionCalculationAction = ConditionActionBase & {
    equation: string;
    resultField: string;
  };

  export type ConditionAction = ConditionVisibilityAction | ConditionCalculationAction;

  export type ConditionTerm = {
    id: string;
    field: string;
    operator: 'contains' | 'notContains' | 'equals' | 'notEquals' | 'isEmpty' | 'isFilled';
    value: string;
    isError: boolean;
  };

  type Condition = {
    action: StringifiedJson<ConditionAction[]>;
    disabled?: ZeroOneFlag;
    id: string;
    index: string;
    link: 'Any' | 'All';
    priority: NumberLikeString;
    terms: StringifiedJson<ConditionTerm[]>;
    type: 'field' | 'require' | string; // TODO: Add all possible values
  };

  type Email = {
    body: string;
    branding21Email: ZeroOneFlag | '';
    dirty: string;
    from: 'default' | string;
    hideEmptyFields: ZeroOneFlag;
    html: ZeroOneFlag;
    lastQuestionID: string;
    name: string;
    pdfattachment: string;
    replyTo: string;
    sendOnEdit: ZeroOneFlag;
    sendOnSubmit: ZeroOneFlag;
    subject: string;
    to: string;
    type: 'notification' | string; // TODO: Add all possible values
    uniqueID: string;
    uploadAttachment: string;
  };

  export type SubmissionSetting = Record<string, any>; // TODO

  export type Integrations = Record<string, any> & {
    webhooks?: {
     endpoints: StringifiedJson<string[]>;
    };
  }; // TODO

  type FormPropertiesBase<K extends string = string> = {
    activeRedirect?: 'thanktext' | 'thankurl';
    alignment: VerticalAlign;
    conditions?: Condition[];
    defaultAutoResponderEmailAssigned: YesNoFlag;
    defaultEmailAssigned: YesNoFlag;
    defaultTheme: string;
    emails: Email[];
    formOwnerAccountType: 'ENTERPRISE' | 'FREE' | string; // TODO: Add all possible values
    formOwnerName: string;
    hash: string;
    height: NumberLikeString;
    id: K;
    injectCSS: string;
    integrations: Integrations | never[];
    isEUForm: ZeroOneFlag;
    isHIPAA: ZeroOneFlag;
    lastQuestionID: string;
    owner: string;
    pagetitle: string;
    pageTitleChanged: YesNoFlag;
    slug: string;
    status: 'ENABLED' | string; // TODO: Add all possible values
    styleJSON: string;
    submissionSettings: SubmissionSetting[];
    thanktext: string;
    thankurl?: Url;
    thankYouIconSrc: Url;
    thankYouImageSrc: Url;
    thankYouPageLayout: 'smallImageUp' | string; // TODO: Add all possible values
    themeID: string;
    title: string;
    type: 'LEGACY' | string; // TODO: Add all possible values
  };

  export type FormPropertiesV3<K extends string = string> = FormPropertiesBase<K> & {
    allowFormFillerToContact: YesNoFlag;
    assigneeSubmissionPermission: 'submitOnly' | string; // TODO: Add all possible values
    assignLinkExpireDate: string;
    assignLinkExpireTimezone: string;
    assignSlug: string;
    datetimeMigrationIncludesBirthDate: ZeroOneFlag;
    datetimeMigrationStatus: 'done' | string; // TODO: Add all possible values
    defaultTheme: 'v1';
    isOrganizationSettingModalClosed: ZeroOneFlag;
  };

  type FormStrings = Record<string, string>;

  export type FormPropertiesV4<K extends string = string> = FormPropertiesBase<K> & {
    allowSubmissionEdit: YesNoFlag;
    background: string;
    cardThemeID: string;
    clearFieldOnHide: EnableDisableFlag;
    creationLanguage: string;
    defaultTheme: 'v2';
    errorNavigation: YesNoFlag;
    expireDate: 'No Limit' | any; // TODO: Add all possible values
    font: string;
    fontcolor: string;
    fontsize: NumberLikeString;
    formStrings: [FormStrings];
    formStringsChanged: YesNoFlag;
    formType: 'legacyForm' | string; // TODO: Add all possible values
    formWidth: NumberLikeString;
    hideEmptySubmissionFields: YesNoFlag;
    hideMailEmptyFields: EnableDisableFlag;
    hideNonInputSubmissionFields: YesNoFlag;
    hideSubmissionHeader: YesNoFlag;
    highlightLine: 'Enabled' | string; // TODO: Add all possible values
    isEncrypted: YesNoFlag;
    labelWidth: NumberLikeString;
    limitSubmission: 'No Limit' | any; // TODO: Add all possible values
    lineSpacing: NumberLikeString;
    messageOfLimitedForm: string;
    mobileGoButton: 'enable' | string; // TODO: Add all possible values
    newPaymentUIForNewCreatedForms: YesNoFlag;
    optioncolor: string;
    pageColor: string;
    preventCloningForm: YesNoFlag;
    responsive: YesNoFlag;
    sendpostdata: YesNoFlag;
    showJotFormLogo: YesNoFlag;
    showProgressBar: EnableDisableFlag;
    styles: string;
    submissionHeaders: string;
    submitError: 'jumpToFirstError' | string; // TODO: Add all possible values
    thankYouDownloadPDF: YesNoFlag;
    thankYouEditSubmission: YesNoFlag;
    thankYouFillAgain: YesNoFlag;
    thankYouSelectedPDFs: YesNoFlag;
    unique: 'None' | string; // TODO: Add all possible values
    uniqueField: '<Field Id>' | string; // TODO: Add all possible values
    usesNewPDF: YesNoFlag;
    v4: '1';
  };

  export type FormProperties<K extends string = string> = FormPropertiesV3<K> | FormPropertiesV4<K>;

  export function options(options: Options): void;

  export function getUser(): Promise<User>;

  export function getUsage(): Promise<Usage>;

  export function getForms(query?: Query): Promise<Form[]>;

  export function getSubmissions(
    query?: Query & {
      fullText?: any;
      nocache?: any;
    },
  ): Promise<Submission[]>;

  export function getSubusers(): Promise<Subuser[]>;

  export function getFolders(): Promise<FolderRoot | 'You do not have any folders'>;

  export function getReports(): Promise<Report[]>;

  export function getSettings(): Promise<Settings>;

  export function getHistory(query?: {
    action?: any;
    date?: any;
    sortBy?: any;
    startDate?: any;
    endDate?: any;
  }): Promise<HistoryEntry[]>;

  export function getForm<K extends string>(formID: K): Promise<Form<K>>;

  export function getFormQuestions(formID: string): Promise<FormQuestions>;

  export function getFormQuestion<K extends string>(
    formID: string,
    questionID: K,
  ): Promise<FormQuestion<K>>;

  export function getFormSubmissions<K extends string>(
    formID: K,
    query?: Query,
  ): Promise<Submission<K>[]>;

  export function createFormSubmission<K extends string>(
    formID: K,
    submission: {
      submission: Record<string, any>;
    },
  ): Promise<{ submissionID: string, URL: string }>;

  export function createFormSubmissions(
    formID: string,
    submissions: Record<string, any>[],
  ): Promise<{ submissionID: string, URL: string }[]>;

  export function getFormFiles<K extends string>(formID: K): Promise<FormFile<K>[]>;

  export function getFormWebhooks(formID: string): Promise<Record<string, FormWebhook>>;

  export function createFormWebhook(
    formID: string,
    webhookURL: string,
  ): Promise<Record<string, FormWebhook>>;

  export function deleteFormWebhook(formID: string, webhookID: string): Promise<any>;

  export function getSubmission<K extends string>(submissionID: K): Promise<Submission<string, K>>;

  export function editSubmission<K extends string>(
    submissionID: K,
    submission: Partial<Submission<string, K>>,
  ): Promise<{ submissionID: K; URL: Url }>;

  export function deleteSubmission(submissionID: string): Promise<any>;

  export function getReport<K extends string>(reportID: K): Promise<Report<K>>;

  export function getFolder<K extends string>(folderID: K): Promise<Folder<K>>;

  export function createForm(form: Partial<Form>): Promise<Form>;

  export function createForms(forms: Partial<Form>[]): Promise<any>;

  export function deleteForm(formID: string): Promise<any>;

  export function cloneForm(formID: string): Promise<Form>;

  export function addFormQuestion(
    formID: string,
    question: Partial<FormQuestion>,
  ): Promise<FormQuestion>;

  export function addFormQuestions(
    formID: string,
    questions: Partial<FormQuestion>[],
  ): Promise<any>;

  export function deleteFormQuestion(formID: string, questionID: string): Promise<any>;

  export function getFormProperties<K extends string>(formID: K): Promise<FormProperties<K>>;

  export function addFormProperty(formID: string, property: any): Promise<any>;

  export function addFormProperties(formID: string, properties: any): Promise<any>;

  export function getFormPropertyByKey(formID: string, propertyKey: string): Promise<any>;
}

Bad handling of API errors in library

I was calling getFormSubmissions() and seeing debug output, but my process exited immediately without waiting for the promise. After much frustration I accessed the API URL outputted by the debug info through my browser, and found the following error:

{"responseCode":301,"location":"https:\/\/eu-api.jotform.com\/form\/REDACTED\/submissions","message":"Your account is in EU Safe mode. Please use \"eu-api.jotform.com\" endpoint to get your results."}

I was handling errors correctly, so I didn't understand why I was not getting an exception (through the "fail" handler). I checked the code and came across the following in the sendRequest function of your index.js file (line 41):

request(options, function(err, response, body){
            if(err){
                deferred.reject(err);
                return;
            }
            if(response.statusCode == 200 && body.responseCode == 200){
                deferred.resolve(body.content);
            }
            if(response.statusCode != 200){
                deferred.reject(new Error(body.message));
            }
        });

If the request comes back with a response.statusCode == 200, but there's a different response code in the JSON body, the deferred is neither resolved nor rejected. The code assumes that the HTTP status code will match the API response code, but to be sure you should check

            if(response.statusCode != 200 || body.responseCode != 200)

you probably also should initialize your Error with body.message only when there's a body, and with a library error otherwise... if the HTTP response fails it's not guaranteed you'll have a JSON body.

[formIDs, form_ids:ne] None of the filters work in your API

Your through and through broken API just make me crazy!

Method GET /user/submissions.

First, formIDs filter:

Why do you say it in your docs if it is not true?

You can also use formIDs commands to get more advanced filtering
Example: {"formIDs":["your-form-id","your-form-id#2"]}

Guys, it is not true. Filter doesn't work. Try to pass in at least 100 ids, just 100, not to say 1000. Then either delete your lie from your docs or edit that max number of items is about 25. I didn't check exact number, it is your job to test your code, we pay you for your service!

Second, form_ids:ne filter, not equal.

Exactly the same thing with it. Doesn't work for more then 29.

Could you please repair these filters?

jf.getForms() doesn't accept "limit" filter

I use Node.js library.

I want to fetch 1000 forms. By your API it is max. I provide limit filter but it doesn't work. Only default 100 is returned (for any limit number, not only 1000):

    jf.getForms({
            limit: 1000 // filter
        })
        .then(function(forms) {
            console.log(forms.length); // 100, not 1000
        })
        .fail(function(e) {
        });

I use jotform, not jotform-api-nodejs. See the difference: #4

How to make filter work?

POST /submission/{id} doesn't work, doesn't update type: "control_checkbox" with array value

Your stuff doesn't work. POST request returns success but it not success, nothing updated!

I have such a form field:

123: {
   text: "Status",
   type: "control_checkbox",
   answer: [
       "Status 1"
    ],
   prettyFormat: "Status 1"
},

I need to update it with this array ["Status 2", "Status 3", "Status 4"]
How do I do it?

Code:

var request = require("request");

var options = { method: 'POST',
  url: 'https://api.jotform.com/submission/12345',
  qs: { apikey: '...' },
  headers: 
   { 'cache-control': 'no-cache',
     'content-type': 'application/x-www-form-urlencoded',
     apikey: '...',
     accept: 'application/json' },
  form: { 'submission[123]': '["Status 2", "Status 3", "Status 4"]' } };

request(options, function (error, response, body) {
  if (error) throw new Error(error);

  console.log(body);
});

UPD:
Your API doesn't consider array.
When I pass this array ["Status 2", "Status 3", "Status 4"] your API for some reason takes only first item of the array. Why?? What do you do with the rest of the items? Why they disappeared?

123: {
    text: "Status",
    type: "control_checkbox",
    answer: [
        "Status 2"
    ],
    prettyFormat: "Status 2"
},

Please provide us a working example on HOW TO UPDATE type: "control_checkbox" WITH ARRAY using your API?

No possibility of accessing forms belonging to teams

In @wojtekmaj/jotform, a fork I maintain, you can get forms belonging to team like so:

jotform.getForm(formId, { 'jf-team-id': teamId })

In jotform 1.0.1, you can only get forms by ID:

client.form.get(formId)

which (kind of understandably), fails to fetch the form.

created_at:{operator} filters do not work when formIDs filter is specified

If formIDs array is, say about 100 items, created_at:{operator} filters are not applied (docs).

jotform api - google chrome 2016-10-30 02 41 59

Example.
If I remove formIDs option, oh my goodness, the API suddenly start working and returns as expected with created_at: applied.
If I specify formIDs, like in an example below, created_at:operators are not applied and number of returned submissions is as many as limit option says.

jf.getSubmissions({
        offset: '0',
        limit : '100',
        filter: {
            "created_at:gte": moment().set({hour: 0, minutes: 0, seconds: 0}).format('YYYY-MM-DD HH:mm:ss'),
            "created_at:lt": moment().add(1, 'day').set({hour: 0, minutes: 0, seconds: 0}).format('YYYY-MM-DD HH:mm:ss'),
            formIDs: ["formID1", "formID2", ...say 100 items]
        }
})

When may we expect the fix?
As always: "sorry, we cannot fix it quickly"?

TypeError: Cannot read property 'statusCode' of undefined || Best practices for bulk requests?

What is your best practices for bulk requests?

For example, a request for form/questions for more than 100 forms (with Promise.all, of course) never finishes and trows error:

            if(response.statusCode != 200){
                       ^
TypeError: Cannot read property 'statusCode' of undefined
    at Request._callback (C:\Users\user\node\app\node_modules\jotform\index.js:42:24)
    at self.callback (C:\Users\user\node\app\node_modules\jotform\node_modules\request\index.js:148:22)
    at emitOne (events.js:96:13)
    at Request.emit (events.js:188:7)
    at ClientRequest.self.clientErrorHandler (C:\Users\user\node\app\node_modules\jotform\node_modules\request\index.js:257:10)
    ....

Here is a use case.

I need to create a response like this [ { form: {}, formQuestions: {} }, {...}, {...}, ... ]
So I use two requests: 1) getForms and 2) getFormQuestions in Promise.all for every form.id.

It works only for negligeable getForms limits. I tested for limit: 10 and it is still ok. But for limit: 300 and if result forms.length is > 100, it fails. The error is thrown.

const jf = require("jotform");

jf.options({
    apiKey: api_key,
    debug: debug,
});

jf.getForms({
    offset: 0,
    limit: 300, // I set only 300, and response has just 150.
    filter: {}
})
.then(forms => {

    // forms is an array with > 100 entries
    // I iterate it and make a request for every formId to "getFormQuestions(form.id)"
    return Promise.all( forms.map(form => {
        return jf.getFormQuestions(form.id)
            .then(formQ => {
                return {
                    form: form,
                    formQ: formQ
                }
            });
    }));
})
.then(resp => {
    console.log(resp);
})
.fail(err => {
    console.log(err);
});

So what is your best practice to perform such requests?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.