How to Handle Sensitive Information

Contents

Overview

This article is a step-by-step guide for building Kintone plug-ins for integrating with other cloud services, and covers the following points:

  • How to save sensitive information, such as API Keys, in the plug-in settings
  • How to send an external API request using the saved sensitive information

It is recommended to read all of the previous articles before continuing with this one:
Plug-ins: Development Guide

Refer to the section of the following article for the advantages of using plug-ins to handle sensitive information and how they work:
Get Started with Developing Plug-ins

The Outline of the Plug-in for this Tutorial

In this sample, data within the Text area field will be translated to Spanish when the Translate button is clicked on the edit record page. The plug-in runs the Web API provided by DeepL, a machine learning translation service. This sample uses the free version of the service. For more information, refer to the official website:
DeepL API (External link)

An API key is required to run this API. By securely storing the authentication information within the plug-in settings, the Web API can be called without the API key being leaked to end users.

STEP 1: Create a Sample App

First, create a new Kintone App. Set the fields in the sample App as follows.

Field Type Field Code / Element ID Field Name Notes
Text area source Enter text The text to translate.
Text area translation Translation The translation result.
Blank space button-space none The space to place the Translation button.

STEP 2: Generate an API Key from DeepL

Follow these steps to create a DeepL account and generate an API key to call the DeepL API.

  1. Register an account at the following page
    DeepL account registration (External link)
  2. Log in to DeepL.
  3. Click the profile icon on the top right of the page, and then click Account.
  4. Open the API Keys tab and click the copy icon to the right of the API key to copy it. Note down the API key as it is needed for the integration in the later steps.

STEP 3: Prepare the Plug-in Files

Next, prepare the necessary files for the Kintone plug-in. Create a directory with the following structure.

1
2
3
4
5
sample-plugin
├── css
├── html
├── image
└── js

Prepare a Plug-in Icon

Prepare an icon file for the plug-in. Set the image under the "image" directory with the file name "icon.png".

Create a CSS file for the Plug-in Settings Page

Save the contents of the 51-modern-default.css file and name it "51-modern-default.css". Save the file under the "css" directory.
51-modern-default.css (External link)

Create an HTML file for the Plug-in Settings Page

Create an HTML file "config.html" and save it under the "html" directory. This HTML file will be used for creating the plug-in settings page.

Set up the HTML to display drop-downs and text boxes for the plug-in settings page.

Name Type Description
Space field Drop-down The Space field to place the Translate button.
Only space fields can be selected.
Source field Drop-down The source field that will be translated.
Only Text areas can be selected.
Translation field Drop-down The translation field to save the translation results.
Only Text areas can be selected.
DeepL Key Textbox The API key from the DeepL account.

In this sample code, the class name is set so that the 51-modern-default style is applied.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<section class="settings">
  <form id="submit-settings">
    <div class="term-setting">
      <label class="kintoneplugin-label" for="space-field">
        <span>Space field</span>
        <span class="kintoneplugin-require">*</span>
      </label>
      <div class="kintoneplugin-row">
        Select a space field for the "Translate" button
      </div>
      <div class="kintoneplugin-select-outer">
        <div class="kintoneplugin-select">
          <select id="space-field">
            <option value="">-----</option>
          </select>
        </div>
      </div>
    </div>
    <div class="term-setting">
      <label class="kintoneplugin-label" for="source-field">
        <span>Source</span>
        <span class="kintoneplugin-require">*</span>
      </label>
      <div class="kintoneplugin-row">
        Select a Text area to be translated.
      </div>
      <div class="kintoneplugin-select-outer">
        <div class="kintoneplugin-select">
          <select id="source-field">
            <option value="">-----</option>
          </select>
        </div>
      </div>
    </div>
    <div class="term-setting">
      <label class="kintoneplugin-label" for="target-field">
        <span>Translation</span>
      <span class="kintoneplugin-require">*</span>
      </label>
      <div class="kintoneplugin-row">
        Select a Text area to export the translation.
      </div>
      <div class="kintoneplugin-select-outer">
        <div class="kintoneplugin-select">
          <select id="target-field">
            <option value="">-----</option>
          </select>
        </div>
      </div>
    </div>
    <div class="term-setting">
      <label class="kintoneplugin-label" for="api-key">
        <span>DeepL Key</span>
        <span class="kintoneplugin-require">*</span>
      </label>
      <div class="kintoneplugin-row">
        Enter your API key
      </div>
      <input type="text" id="token" class="api-key kintoneplugin-input-text" />
    </div>
    <div class="kintoneplugin-row">
      <button type="button" id="cancel-button" class="kintoneplugin-button-dialog-cancel">Cancel</button>
      <button id="save-button" class="kintoneplugin-button-dialog-ok">Save</button>
    </div>
  </form>
</section>

Create a JavaScript file for the Plug-in Settings Page

desktop.js

Create an empty "desktop.js" file and save it under the "js" directory. This file will be updated in the later steps.

config.js

Next, create a "config.js" file and save it under the "js" directory.

Use kintone-config-helper to get the field information of the App. Read the following guide for reference:
How to Create a Dynamic Settings Page

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
(async (PLUGIN_ID) => {
  'use strict';

  // Escape values
  const escapeHtml = (str) => {
    return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;')
      .replace(/\n/g, '&#xA;');
  };

  // Drop-down list for the translate button
  const createOptionsForSpace = async () => {
    let options = [];

    // Get space fields
    const spaceFields = await KintoneConfigHelper.getFields('SPACER');
    if (spaceFields) {
      spaceFields.forEach(field => {
        const option = document.createElement('option');
        option.value = field.elementId;
        option.textContent = field.elementId;
        options = options.concat(option);
      });
    }
    return options;
  };

  // Create the drop-down list
  const spaceField = document.getElementById('space-field');
  const spaceOptins = await createOptionsForSpace();
  spaceOptins.forEach(option => {
    spaceField.appendChild(option);
  });

  // Drop-down list for the source and translation
  const getKintoneFiled = async () => {
    let options = [];

    // Get text area fields
    const textFields = await KintoneConfigHelper.getFields('MULTI_LINE_TEXT');
    if (textFields) {
      textFields.forEach(field => {
        const option = document.createElement('option');
        option.value = field.code;
        option.textContent = field.label;
        options = options.concat(option);
      });
    }
    return options;
  };

  // Create the drop-down list
  const textOptions = await getKintoneFiled();
  const sourceField = document.getElementById('source-field');
  const targetField = document.getElementById('target-field');
  textOptions.forEach(option => {
    const sourceFieldOption = option.cloneNode(true);
    const targetFieldOptine = option.cloneNode(true);
    sourceField.appendChild(sourceFieldOption);
    targetField.appendChild(targetFieldOptine);
  });

  // Set initial value
  const config = kintone.plugin.app.getConfig(PLUGIN_ID);
  const setConfigValue = (field, options, element) => {
    const selectedOption = options.find(
      (option) => option.value === config[field]
    );
    if (selectedOption) {
      element.value = config[field];
    }
  };
  setConfigValue('sourceFieldValue', textOptions, sourceField);
  setConfigValue('targetFieldValue', textOptions, targetField);
  setConfigValue('spaceFieldID', spaceOptins, spaceField);

  // Save and cancel button
  const appId = kintone.app.getId();
  const form = document.getElementById('submit-settings');
  const cancelButton = document.getElementById('cancel-button');
  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const newConfig = {
      sourceFieldValue: escapeHtml(sourceField.value),
      targetFieldValue: escapeHtml(targetField.value),
      spaceFieldID: escapeHtml(spaceField.value)
    };

    if (spaceField.value === '' || sourceField.value === '' || targetField.value === '') {
      alert('Required value is missing.');
    } else {
      kintone.plugin.app.setConfig(newConfig, () => {
        window.location.href = `/k/admin/app/flow?app=${appId}`;
      });
    }
  });
  cancelButton.addEventListener('click', () => {
    window.location.href = `../../${appId}/plugin/`;
  });
})(kintone.$PLUGIN_ID);

Create a Manifest File

Save the following sample as "manifest.json" and place it under the "sample-plugin" directory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "manifest_version": 1,
  "version": 1,
  "type": "APP",
  "desktop": {
    "js": [
      "js/desktop.js"
    ]
  },
  "icon": "image/icon.png",
  "config": {
    "html": "html/config.html",
    "js": [
      "js/kintone-config-helper.js",
      "js/config.js"
    ],
    "css": [
      "css/51-modern-default.css"
    ]
  },
  "name": {
    "en": "Machine Translation Plug-in"
  },
  "description": {
    "en": "Machine Translation Plug-in"
  }
}

Directory Structure

Once all the files are prepared, the file tree should now look like the following structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
sample-plugin
├── css
│   └── 51-modern-default.css
├── html
│   └── config.html
├── image
│   └── icon.png
├── js
│   ├── config.js
│   ├── desktop.js
│   └── kintone-config-helper.js
└── manifest.json

STEP 4: Save the Sensitive Information in the Plug-in Settings

On the plug-in settings page, kintone.plugin.app.setConfig() can be used to save plug-in configuration settings. The information saved by this API can be retrieved using kintone.plugin.app.getConfig(), which can be called on both the Plug-in settings page and the record pages. This means that the saved settings can be viewed by both App Admins and non-admin users of the App. Therefore, kintone.plugin.app.setConfig() should not be used to save sensitive information.

Instead of using kintone.plugin.app.setConfig() to save the sensitive data, use the following Kintone JavaScript API:
Proxy Set Config : kintone.plugin.app.setProxyConfig()

kintone.plugin.app.setProxyConfig() saves the plug-in configuration settings including the request URL, method, and headers. Information saved with this API can be retrieved only on the plug-in settings page, using the kintone.plugin.app.getProxyConfig() API. App users who do not have access to the plug-in's settings cannot retrieve the saved information.
The saved data though can be used outside of the plug-in's settings page to make external API requests, introduced in the later steps.

The details of the parameters are as follows:

kintone.plugin.app.setProxyConfig(url, method, headers, data, successCallback)

Parameter Type Description
url String The URL of the Web API
method String The HTTP method
headers Object The request header
data Object The request body
successCallback Function The function to be called after the settings have been successfully saved

Next, prepare the information required to run the API. The contents of the request and the method of authentication differ depending on the Web API. According to the DeepL API document, the following information is required to run this API:
Translate text (External link)

Item Value
URL https://api-free.deepl.com/v2/translate
Method POST
Request Header
  • Authorization: DeepL-Auth-Key [yourAuthKey]
  • Content-Type: application/json
Request Body
  • text: Translation source in array format
  • target_lang: Translation target language

Set these values as the parameters for the kintone.plugin.app.setProxyConfig() API. Since the actual request is made from the customization file, specify an empty object for the request body.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Specify external API
const apiUrl = 'https://api-free.deepl.com/v2/translate';
const method = 'POST';

// Get text box
const apiKeyField = document.getElementById('token');

const auth = escapeHtml(apiKeyField.value);
const apiHeader = {
  Authorization: `DeepL-Auth-Key ${auth}`,
  'Content-Type': 'application/json'
};
const apiBody = {};
const successCallback = () => {};
if (apiKeyField.value === '') {
  alert('Required value is missing.');
  return;
}
kintone.plugin.app.setProxyConfig(apiUrl, method, apiHeader, apiBody, successCallback);

Next, add the code to the "config.js" file made in the previous step. The process of saving information that is not sensitive needs to be run after the execution of the kintone.plugin.app.setProxyConfig() API. Therefore, kintone.plugin.app.setConfig() is run in the successCallback() function.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
(async (PLUGIN_ID) => {
  'use strict';

  // Escape values
  const escapeHtml = (str) => {
    return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;')
      .replace(/\n/g, '&#xA;');
  };

  // Drop-down list for the translate button
  const createOptionsForSpace = async () => {
    let options = [];

    // Get space fields
    const spaceFields = await KintoneConfigHelper.getFields('SPACER');
    if (spaceFields) {
      spaceFields.forEach(field => {
        const option = document.createElement('option');
        option.value = field.elementId;
        option.textContent = field.elementId;
        options = options.concat(option);
      });
    }
    return options;
  };

  // Create the drop-down list
  const spaceField = document.getElementById('space-field');
  const spaceOptins = await createOptionsForSpace();
  spaceOptins.forEach(option => {
    spaceField.appendChild(option);
  });

  // Drop-down list for the source and translation
  const getKintoneFiled = async () => {
    let options = [];

    // Get text area fields
    const textFields = await KintoneConfigHelper.getFields('MULTI_LINE_TEXT');
    if (textFields) {
      textFields.forEach(field => {
        const option = document.createElement('option');
        option.value = field.code;
        option.textContent = field.label;
        options = options.concat(option);
      });
    }
    return options;
  };

  // Create the drop-down list
  const textOptions = await getKintoneFiled();
  const sourceField = document.getElementById('source-field');
  const targetField = document.getElementById('target-field');
  textOptions.forEach(option => {
    const sourceFieldOption = option.cloneNode(true);
    const targetFieldOptine = option.cloneNode(true);
    sourceField.appendChild(sourceFieldOption);
    targetField.appendChild(targetFieldOptine);
  });

  // Set initial value
  const config = kintone.plugin.app.getConfig(PLUGIN_ID);
  const setConfigValue = (field, options, element) => {
    const selectedOption = options.find(
      (option) => option.value === config[field]
    );
    if (selectedOption) {
      element.value = config[field];
    }
  };
  setConfigValue('sourceFieldValue', textOptions, sourceField);
  setConfigValue('targetFieldValue', textOptions, targetField);
  setConfigValue('spaceFieldID', spaceOptins, spaceField);

  // Get text box
  const apiKeyField = document.getElementById('token');
  // Specify external API
  const apiUrl = 'https://api-free.deepl.com/v2/translate';
  const method = 'POST';

  // Save and Cancel button
  const appId = kintone.app.getId();
  const form = document.getElementById('submit-settings');
  const cancelButton = document.getElementById('cancel-button');
  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const auth = escapeHtml(apiKeyField.value);
    const apiHeader = {
      Authorization: `DeepL-Auth-Key ${auth}`,
      'Content-Type': 'application/json'
    };
    const apiBody = {};
    const successCallback = () => {
      const newConfig = {
        sourceFieldValue: escapeHtml(sourceField.value),
        targetFieldValue: escapeHtml(targetField.value),
        spaceFieldID: escapeHtml(spaceField.value)
      };

      if (spaceField.value === '' || sourceField.value === '' || targetField.value === '') {
        alert('Required value is missing.');

      } else {
        kintone.plugin.app.setConfig(newConfig, () => {
          window.location.href = `/k/admin/app/flow?app=${appId}`;
        });
      }
    };
    if (apiKeyField.value === '') {
      alert('Required value is missing.');
      return;
    }
    kintone.plugin.app.setProxyConfig(apiUrl, method, apiHeader, apiBody, successCallback);
  });
  cancelButton.addEventListener('click', () => {
    window.location.href = `../../${appId}/plugin/`;
  });
})(kintone.$PLUGIN_ID);

STEP 5: Retrieve the Saved Sensitive Information From the Plug-in Settings

When users navigate to the plug-in settings page, initial values need to be set in the fields. These initial values will come from the values saved with the kintone.plugin.app.setProxyConfig() API.

Use the following API to retrieve the saved sensitive information.
Proxy Get Config : kintone.plugin.app.getProxyConfig()

1
const proxyConfig = kintone.plugin.app.getProxyConfig(apiUrl, method);

Next, get the DeepL API key from the response. The header information is stored in the headers property.

1
2
3
4
{
  "Authorization": 'DeepL-Auth-Key YOUR_AUTH_KEY',
  "Content-Type": 'application/json'
}

The value of Authorization is prefixed with the string "DeepL-Auth-Key". Therefore, it is necessary to extract the API key as follows.

1
const deepLApiToken = proxyConfig ? proxyConfig.headers.Authorization.split(' ')[1] : '';

Finally, set the API key in the text box.

1
2
3
if (deepLApiToken) {
  apiKeyField.value = deepLApiToken;
}

The "config.js" file should look like the following.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
(async (PLUGIN_ID) => {
  'use strict';

  // Escape values
  const escapeHtml = (str) => {
    return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;')
      .replace(/\n/g, '&#xA;');
  };

  // Drop-down list for the translate button
  const createOptionsForSpace = async () => {
    let options = [];

    // Get space fields
    const spaceFields = await KintoneConfigHelper.getFields('SPACER');
    if (spaceFields) {
      spaceFields.forEach(field => {
        const option = document.createElement('option');
        option.value = field.elementId;
        option.textContent = field.elementId;
        options = options.concat(option);
      });
    }
    return options;
  };

  // Create the drop-down list
  const spaceField = document.getElementById('space-field');
  const spaceOptins = await createOptionsForSpace();
  spaceOptins.forEach(option => {
    spaceField.appendChild(option);
  });

  // Drop-down list for the source and translation
  const getKintoneFiled = async () => {
    let options = [];

    // Get text area fields
    const textFields = await KintoneConfigHelper.getFields('MULTI_LINE_TEXT');
    if (textFields) {
      textFields.forEach(field => {
        const option = document.createElement('option');
        option.value = field.code;
        option.textContent = field.label;
        options = options.concat(option);
      });
    }
    return options;
  };

  // Create the drop-down list
  const textOptions = await getKintoneFiled();
  const sourceField = document.getElementById('source-field');
  const targetField = document.getElementById('target-field');
  textOptions.forEach(option => {
    const sourceFieldOption = option.cloneNode(true);
    const targetFieldOptine = option.cloneNode(true);
    sourceField.appendChild(sourceFieldOption);
    targetField.appendChild(targetFieldOptine);
  });

  // Set initial value
  const config = kintone.plugin.app.getConfig(PLUGIN_ID);
  const setConfigValue = (field, options, element) => {
    const selectedOption = options.find(
      (option) => option.value === config[field]
    );
    if (selectedOption) {
      element.value = config[field];
    }
  };
  setConfigValue('sourceFieldValue', textOptions, sourceField);
  setConfigValue('targetFieldValue', textOptions, targetField);
  setConfigValue('spaceFieldID', spaceOptins, spaceField);

  // Get text box
  const apiKeyField = document.getElementById('token');
  // Specify external API
  const apiUrl = 'https://api-free.deepl.com/v2/translate';
  const method = 'POST';

  // Get settings information from the API request and set it as initial value
  const proxyConfig = kintone.plugin.app.getProxyConfig(apiUrl, method);
  const deepLApiToken = proxyConfig ? proxyConfig.headers.Authorization.split(' ')[1] : '';
  if (deepLApiToken) {
    apiKeyField.value = deepLApiToken;
  }

  // Save and Cancel button
  const appId = kintone.app.getId();
  const form = document.getElementById('submit-settings');
  const cancelButton = document.getElementById('cancel-button');
  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const auth = escapeHtml(apiKeyField.value);
    const apiHeader = {
      Authorization: `DeepL-Auth-Key ${auth}`,
      'Content-Type': 'application/json'
    };
    const apiBody = {};
    const successCallback = () => {
      const newConfig = {
        sourceFieldValue: escapeHtml(sourceField.value),
        targetFieldValue: escapeHtml(targetField.value),
        spaceFieldID: escapeHtml(spaceField.value)
      };

      if (spaceField.value === '' || sourceField.value === '' || targetField.value === '') {
        alert('Required value is missing.');

      } else {
        kintone.plugin.app.setConfig(newConfig, () => {
          window.location.href = `/k/admin/app/flow?app=${appId}`;
        });
      }
    };
    if (apiKeyField.value === '') {
      alert('Required value is missing.');
      return;
    }
    kintone.plugin.app.setProxyConfig(apiUrl, method, apiHeader, apiBody, successCallback);
  });
  cancelButton.addEventListener('click', () => {
    window.location.href = `../../${appId}/plugin/`;
  });
})(kintone.$PLUGIN_ID);

STEP 6: Send an External API Request

Update the "desktop.js" file in the "js" directory to implement the following features:

  • Use the plug-in's settings to retrieve the field information.
  • When the Translate button is clicked, run the DeepL API to translate the contents of the Enter text field and set the result into the Translation field.

Retrieve the Field Information

Use the Get Config API to retrieve the plug-in's configuration settings.
Get Config : kintone.plugin.app.getConfig()

In the space field, display the Translate button and register an event handler for the click event.

The code is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
((PLUGIN_ID) => {
  'use strict';

  // Get plug-in configuration settings
  const config = kintone.plugin.app.getConfig(PLUGIN_ID);
  if (!config) {
    return;
  }
  const {sourceFieldValue, targetFieldValue, spaceFieldID} = config;

  kintone.events.on('app.record.edit.show', (event) => {
    const source = event.record[sourceFieldValue];

    // Prepare "Translate" button
    const button = document.createElement('button');
    button.id = 'button-space';
    button.textContent = 'Translate';
    // Click event
    button.onclick = async () => {
      // External API request here
    };
    kintone.app.record
      .getSpaceElement(spaceFieldID)
      .appendChild(button);
    return event;
  });
})(kintone.$PLUGIN_ID);

Run The DeepL API Request

Implement the function to run the DeepL API to translate the contents of the Enter text field. To run an external Web API, use the following Kintone JavaScript API.
Plug-in Proxy Request : kintone.plugin.app.proxy()

Specify the following information as the arguments:

  • pluginId: ID of this plug-in
  • url: URL of the Web API. Specify https://api-free.deepl.com/v2/translate.
  • method: HTTP method. Specify POST.
  • data: Request body. Specify data of the DeepL API request. For more information, refer to the following link:
    Translate Text (External link)

When the API is run with the above arguments, the proxy server makes the request to the DeepL API. The contents saved using kintone.plugin.app.setProxyConfig() and the contents specified in kintone.plugin.app.proxy() will be compared. If the following information match, the saved information will be added to the request header and request body.

  • Plug-in ID
  • URL
  • HTTP method

After the request to the DeepL API, set the returned translation result into the Translation field.

The final code of the "desktop.js" file is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
((PLUGIN_ID) => {
  'use strict';

  // Get plug-in configuration settings
  const config = kintone.plugin.app.getConfig(PLUGIN_ID);
  if (!config) {
    return;
  }
  const {sourceFieldValue, targetFieldValue, spaceFieldID} = config;

  kintone.events.on('app.record.edit.show', (event) => {
    const source = event.record[sourceFieldValue];
    // Set parameter
    const apiUrl = 'https://api-free.deepl.com/v2/translate';
    const method = 'POST';
    const headers = {};
    const data = {
      text: [source.value],
      target_lang: 'ES',
      source_lang: 'EN'
    };

    // Prepare "Translate" button
    const button = document.createElement('button');
    button.id = 'button-space';
    button.textContent = 'Translate';
    // Click event
    button.onclick = async () => {
      // Run the external API request
      const [result, statusCode] = await kintone.plugin.app
        .proxy(PLUGIN_ID, apiUrl, method, headers, data);
      // Error
      if (statusCode !== 200) {
        console.error(JSON.parse(result));
      }
      // Success
      const objResult = JSON.parse(result);
      const translation = objResult.translations[0].text;
      const response = kintone.app.record.get();
      response.record[targetFieldValue].value = translation;
      kintone.app.record.set(response);
    };
    kintone.app.record
      .getSpaceElement(spaceFieldID)
      .appendChild(button);
    return event;
  });
})(kintone.$PLUGIN_ID);

STEP 7: Package and Upload the Plug-in

Follow the steps in the following articles to package and install the plug-in.

Navigate to Kintone on the browser and check if the plug-in has been uploaded successfully.

STEP 8: Test the Plug-in

Test the Translate Button

When the Translate button is clicked on the edit record page, the Enter text field will be translated into Spanish and the result will appear in the Translation field.

Check that the sensitive information is not visible

Go to the Plug-in Settings page and confirm the following:

  • The drop-down list is displayed and can be selected
  • The information of the saved plug-in settings is set as the initial value.

Check using kintone.plugin.app.getProxyConfig()

Go to the edit record page and open the Console tab of the browser. Run the following code.

1
2
3
4
const apiUrl = 'https://api-free.deepl.com/v2/translate';
const method = 'POST';
const proxyConfig = kintone.plugin.app.getProxyConfig(apiUrl, method);
console.log(proxyConfig);

As shown below, the execution result is null, confirming that the sensitive information is not visible to the App user.

Check using network tab request

Open the browser's Network tab on the edit record page. Click the Translate button and call.json?... will be displayed. Click it, and the contents of the request sent by the DeepL API will be displayed. Confirm that the API key in the header is not displayed.

Conclusion

The past few tutorials have covered how to build out plug-ins, with this article focusing on how to deal with sensitive information. To provide more secure services for end-users of Kintone, always keep in mind how to handle sensitive information inside Kintone plug-ins.