前回までプラグインの作り方の基本を学んできましたが、今回はプラグインのメリットのひとつ「プラグインで秘匿情報を扱う方法」を紹介します。
具体的には、次の3点を学習します。
- 秘匿情報をプラグインの設定情報として保存する方法
- 保存した秘匿情報を取得する方法
- 保存した秘匿情報を使って外部APIのリクエストを送信する方法
プラグインで秘匿情報を扱うメリットとそのしくみは、次のページを参考してください。
kintoneプラグインのメリット:秘匿情報を隠蔽できるためセキュリティが向上する
サンプルプラグインのイメージ
固定リンクがコピーされました
今回は、レコードの編集画面で翻訳ボタンをクリックすると「原文」フィールドの内容を英訳して「訳文」フィールドに書き込むプラグインを作ります。
プラグインでは、翻訳サービスが提供するWeb APIを実行します。
このAPIの実行にはサービスの認証情報が必要ですが、プラグインで認証情報を隠すことで、アプリの利用者に認証情報を漏洩させることなく安全にWeb APIを実行します。
翻訳サービスは、DeepL社が提供している翻訳サービスの無料版を使います。
詳細はDeepLの公式サイトを参考してください。
DeepL API Free
DeepLアカウント作成
固定リンクがコピーされました
次の手順にしたがってDeepLアカウントを作成し、DeepL APIを呼び出すには必要なAPIキーを発行しておきます。
- 次のページからアカウントを登録します。
DeepLアカウント登録
- DeepLにログインします。
- 画面右上からプロフィールのアイコンをクリックし、「アカウント」をクリックします。
- 「APIキー」タブを開き、APIキー右側のアイコンをクリックしてAPIキーをコピーします。
APIキーは後で使うので、メモしておきます。
kintoneアプリ作成
固定リンクがコピーされました
プラグインを動作確認するためのアプリを作成します。
以下のフィールドをアプリのフォームに配置します。
他のフィールドの配置は任意です。
フィールドタイプ |
フィールドコード |
フィールド名 |
文字列(複数行) |
source |
原文 |
文字列(複数行) |
target |
訳文 |
スペース |
なし 要素IDに「button-space」を設定します。 |
なし |
ベースとなるプラグインの作成
固定リンクがコピーされました
これまで習ったことを復習しながら、ベースとなるプラグインを作成します。
まずは、「原文」「訳文」フィールドや「翻訳」ボタンを表示するスペースフィールドを設定画面から指定できるようにします。
-
プラグインの設定画面のHTMLファイル(config.html)を作成します。
コードは、後述の
設定画面のHTMLサンプルソースコード
を参考してください。
設定項目のスタイルには、51-modern-defaultを適用しています。
51-modern-default
項目名 |
タイプ |
説明 |
スペースフィールド |
ドロップダウン |
「翻訳」ボタンを配置するためのスペースフィールドを指定します。 スペースフィールドのみ選択可能です。 |
原文フィールド |
ドロップダウン |
原文フィールドを指定します。 文字列(複数行)のみ選択可能です。 |
訳文フィールド |
ドロップダウン |
翻訳結果を保存するための訳文フィールドを指定します。 |
DeepLキー |
テキストボックス |
DeepLアカウント作成でメモしたAPIキーを入力します。 文字列(複数行)のみ選択可能です。 |
-
設定項目を制御するJavaScriptを記述し、config.jsとして保存します。
ドロップダウンは、前回までに学習したkintone-config-helperでアプリのフィールド情報を取得する方法を使って、実際のアプリのフィールド情報を選択できるように実装しています。
コードは、後述の
設定画面のJSサンプルソースコード
を参考してください。
-
desktop.jsという名前で中身が空白のカスタマイズファイルを作成します。
-
プラグインのアイコンファイルを用意し、icon.pngとして保存します。
お手軽にプラグインを作ってみたい方向けに、今回使ったアイコンファイルを貼っておきます。
-
最後に、マニフェストファイルを作成します。
コードは後述の
manifestのサンプルソースコード
を参考してください。
マニフェストファイルの書き方は次の記事を参考してください。
プラグインを作成してみよう
最終的なファイル構造は次のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
|
kintoneProxySamplePlugin
├── css
│ └── 51-modern-default.css
├── js
│ ├── config.js -> プラグイン設定画面のJS
│ ├── desktop.js -> カスタマイズファイルJS
│ └── kintone-config-helper.js
├── html
│ └── config.html -> プラグイン設定画面のHTML
├── image
│ └── icon.png
└── manifest.json
|
設定画面のHTMLサンプルソースコード
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>スペースフィールド</span>
<span class="kintoneplugin-require">*</span>
</label>
<div class="kintoneplugin-row">
[翻訳]ボタンを配置するためのフィールドを選択してください。
</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>原文フィールド</span>
<span class="kintoneplugin-require">*</span>
</label>
<div class="kintoneplugin-row">
翻訳対象フィールドを選択してください。
</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>訳文フィールド</span>
<span class="kintoneplugin-require">*</span>
</label>
<div class="kintoneplugin-row">
翻訳結果を保存するフィールドを選択してください。
</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 キー</span>
<span class="kintoneplugin-require">*</span>
</label>
<div class="kintoneplugin-row">
API キーを入力してください。
</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">キャンセル</button>
<button id="save-button" class="kintoneplugin-button-dialog-ok">保存</button>
</div>
</form>
</section>
|
設定画面のJSサンプルソースコード
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
|
(async (PLUGIN_ID) => {
'use strict';
// XSSを防ぐためのエスケープ処理
const escapeHtml = (str) => {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\n/g, '
');
};
// 「翻訳ボタン」を配置するためのスペース要素のセレクタボックスの項目を作成
const createOptinesForSpace = async () => {
let options = [];
// kintoneフォームからスペースフィールドの要素を取得
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;
};
// スペースフィールドのセレクタボックスを作成
const spaceField = document.getElementById('space-field');
const spaceOptins = await createOptinesForSpace();
spaceOptins.forEach(option => {
spaceField.appendChild(option);
});
// 翻訳リソースフィールドとターゲットフィールドのセレクタボックスの項目を作成
const getKintoneFiled = async () => {
let options = [];
// kintoneフォームからテキスト複数行フィールドの要素を取得し、セレクタオプションにセット
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;
};
// 翻訳リソースフィールドとターゲットフィールドのセレクタボックスを作成
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);
});
// 前回保存した設定情報を初期値として設定項目にセットする
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);
// 「保存」ボタンと「キャンセル」ボタンをクリックした時の処理
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('未入力項目があります。');
} 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);
|
manifestのサンプルソースコード
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
|
{
"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 plugin",
"ja": "機械翻訳プラグイン"
},
"description": {
"en": "machine translation plugin",
"ja": "機械翻訳プラグイン"
}
}
|
秘匿情報をプラグインの設定情報として保存する方法
固定リンクがコピーされました
利用するkintone JavaScript API
固定リンクがコピーされました
これまで、プラグインの設定情報を保存するにはkintone.plugin.app.setConfig()
を使っていました。
このAPIで保存した情報は、kintone.plugin.app.getConfig()
を使って取り出すことができます。
kintone.plugin.app.getConfig()
はレコードの画面でも実行できるため、アプリの利用者にも保存した設定情報が見えてしまいます。
そのため秘匿情報の保存には使えません。
その代わり、kintone.plugin.app.setProxyConfig()
というkintone JavaScript APIを利用します。
外部APIの実行に必要な情報をプラグインへ保存する
:kintone.plugin.app.setProxyConfig()
このAPIで保存した情報は、kintone.plugin.app.getProxyConfig()
というAPIで取得できます。
kintone.plugin.app.getProxyConfig()
はプラグインの設定画面でだけ実行できるAPIなので、プラグインの設定画面にアクセス権がないアプリの利用者は、保存した情報を見ることができません。
APIドキュメントによると、kintone.plugin.app.setProxyConfig()
に指定する引数は次のとおりです。
kintone.plugin.app.setProxyConfig(url, method, headers, data, successCallback)
- 第1引数:Web APIのURL(文字列)
- 第2引数:HTTPメソッド(文字列)
- 第3引数:リクエストヘッダー(オブジェクト)
- 第4引数:リクエストボディ(オブジェクト)
- 第5引数:
kintone.plugin.app.setProxyConfig()
の実行が終わった後の処理
DeepL APIのリクエスト情報
固定リンクがコピーされました
リクエストの内容や、認証情報の指定方法はWeb APIによって異なります。
今回使うDeepL APIに必要な情報を調べてみましょう。
DeepL API
のドキュメントによると、このAPIを実行するには、次の情報が必要です。
項目 |
値 |
URL |
https://api-free.deepl.com/v2/translate |
メソッド |
POST |
リクエストヘッダー |
- Authorization: DeepL-Auth-Key YOUR_AUTH_KEY
- Content-Type: application/json
|
リクエストボディ |
- text:配列形式の翻訳ソース
- target_lang:翻訳ターゲット言語
|
kintone.plugin.app.setProxyConfig()
の引数の指定方法に合わせると、コードは次のようになります。
実際のリクエストはカスタマイズファイルから行うので、リクエストボディには空のオブジェクトを指定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 外部APIの情報を定義
const apiUrl = 'https://api-free.deepl.com/v2/translate';
const method = 'POST';
// テキストボックス要素の取得
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('未入力項目があります。');
return;
}
kintone.plugin.app.setProxyConfig(apiUrl, method, apiHeader, apiBody, successCallback);
|
では、ベースとなるプラグインにkintone.plugin.app.setProxyConfig()
の処理を追加しましょう。
config.jsを開いて、81行目〜85行目を追記します。
また、秘匿情報ではない設定情報を保存処理は、kintone.plugin.app.setProxyConfig()
の実行が終わって行います。
そのため、successCallback()
の中でkintone.plugin.app.setConfig()
を実行するように、92行目〜121行目のように変更します。
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
|
(async (PLUGIN_ID) => {
'use strict';
// XSSを防ぐためのエスケープ処理
const escapeHtml = (str) => {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\n/g, '
');
};
// 「翻訳ボタン」を配置するためのスペース要素のセレクタボックスの項目を作成
const createOptinesForSpace = async () => {
let options = [];
// kintoneフォームからスペースフィールドの要素を取得
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;
};
// スペースフィールドのセレクタボックスを作成
const spaceField = document.getElementById('space-field');
const spaceOptins = await createOptinesForSpace();
spaceOptins.forEach(option => {
spaceField.appendChild(option);
});
// 翻訳リソースフィールドとターゲットフィールドのセレクタボックスの項目を作成
const getKintoneFiled = async () => {
let options = [];
// kintoneフォームからテキスト複数行フィールドの要素を取得し、セレクタオプションにセット
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;
};
// 翻訳リソースフィールドとターゲットフィールドのセレクタボックスを作成
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);
});
// 前回保存した設定情報を初期値として設定項目にセットする
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);
// テキストボックス要素の取得
const apiKeyField = document.getElementById('token');
// 外部APIの情報を定義
const apiUrl = 'https://api-free.deepl.com/v2/translate';
const method = 'POST';
// 「保存」ボタンと「キャンセル」ボタンをクリックした時の処理
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('未入力項目があります。');
} else {
kintone.plugin.app.setConfig(newConfig, () => {
window.location.href = `/k/admin/app/flow?app=${appId}`;
});
}
};
if (apiKeyField.value === '') {
alert('未入力項目があります。');
return;
}
kintone.plugin.app.setProxyConfig(apiUrl, method, apiHeader, apiBody, successCallback);
});
cancelButton.addEventListener('click', () => {
window.location.href = `../../${appId}/plugin/`;
});
})(kintone.$PLUGIN_ID);
|
保存した秘匿情報を取得する方法
固定リンクがコピーされました
設定画面を表示したときにkintone.plugin.app.setProxyConfig()
で保存した内容が表示されるよう、保存した設定情報を取得し設定項目の初期値としてセットしましょう。
保存した秘匿情報を取得するには、次のAPIを利用します。
外部APIの実行に必要な情報を取得する
:kintone.plugin.app.getProxyConfig()
上記APIドキュメントによると、引数に次の2つを指定します。
したがって、コードは次のようになります。
1
|
const proxyConfig = kintone.plugin.app.getProxyConfig(apiUrl, method);
|
続いて、取得した設定情報からDeepL APIキーを取り出します。
ヘッダー情報はproxyConfig
のheaders
プロパティに保存されています。
データ構造は次のとおりです。
1
2
3
4
|
{
"Authorization": 'DeepL-Auth-Key YOUR_AUTH_KEY',
"Content-Type": 'application/json'
}
|
ここでは、DeepL-Auth-Key YOUR_AUTH_KEY
に注目してください。
Authorization
の値には、APIキーだけではなく「DeepL-Auth-Key」という文字列が先頭についています。
そのため、次のようにAPIキーだけを取り出す処理が必要になります。
1
|
const deepLApiToken = proxyConfig ? proxyConfig.headers.Authorization.split(' ')[1] : '';
|
最後に、テキストボックスのHTML要素にAPIキーをセットします。
1
2
3
|
if (deepLApiToken) {
apiKeyField.value = deepLApiToken;
}
|
最終的なコードは次のようになります。
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
132
133
134
135
136
137
138
|
/*
* handle sensitive data sample code
* Copyright (c) 2024 Cybozu
*
* Licensed under the MIT License
*/
(async (PLUGIN_ID) => {
'use strict';
// XSSを防ぐためのエスケープ処理
const escapeHtml = (str) => {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\n/g, '
');
};
// 「翻訳ボタン」を配置するためのスペース要素のセレクタボックスの項目を作成
const createOptinesForSpace = async () => {
let options = [];
// kintoneフォームからスペースフィールドの要素を取得
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;
};
// スペースフィールドのセレクタボックスを作成
const spaceField = document.getElementById('space-field');
const spaceOptins = await createOptinesForSpace();
spaceOptins.forEach(option => {
spaceField.appendChild(option);
});
// 翻訳リソースフィールドとターゲットフィールドのセレクタボックスの項目を作成
const getKintoneFiled = async () => {
let options = [];
// kintoneフォームからテキスト複数行フィールドの要素を取得し、セレクタオプションにセット
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;
};
// 翻訳リソースフィールドとターゲットフィールドのセレクタボックスを作成
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);
});
// 前回保存した設定情報を初期値として設定項目にセットする
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);
// テキストボックス要素の取得
const apiKeyField = document.getElementById('token');
// 外部APIの情報を定義
const apiUrl = 'https://api-free.deepl.com/v2/translate';
const method = 'POST';
// APIリクエストの設定情報を取得し、初期値としてセット
const proxyConfig = kintone.plugin.app.getProxyConfig(apiUrl, method);
const deepLApiToken = proxyConfig ? proxyConfig.headers.Authorization.split(' ')[1] : '';
if (deepLApiToken) {
apiKeyField.value = deepLApiToken;
}
// 「保存」ボタンと「キャンセル」ボタンをクリックした時の処理
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('未入力項目があります。');
} else {
kintone.plugin.app.setConfig(newConfig, () => {
window.location.href = `/k/admin/app/flow?app=${appId}`;
});
}
};
if (apiKeyField.value === '') {
alert('未入力項目があります。');
return;
}
kintone.plugin.app.setProxyConfig(apiUrl, method, apiHeader, apiBody, successCallback);
});
cancelButton.addEventListener('click', () => {
window.location.href = `../../${appId}/plugin/`;
});
})(kintone.$PLUGIN_ID);
|
保存した秘匿情報を使って外部APIのリクエストを送信する方法
固定リンクがコピーされました
次はカスタマイズファイルのdesktop.jsを編集していきます。
カスタマイズファイルでは、次の機能を実装します。
- プラグインの設定情報を使って、カスタマイズを適用するフィールド情報を取得します。
- 「翻訳」ボタンをクリックしたときに、DeepL APIを実行し、「原文」フィールドの内容を翻訳して「訳文」フィールドにセットします。
プラグインの設定情報を使ってカスタマイズを適用するフィールド情報を取得する
固定リンクがコピーされました
これまでのチュートリアルで学習した内容の復習です。
プラグインの設定情報を取得して、「原文」「訳文」フィールドのフィールドコードや「翻訳」ボタンを表示するスペースフィールドの情報を取得しましょう。
「翻訳」ボタンを表示するスペースフィールドには、「翻訳」ボタンを表示し、クリックイベントのイベントハンドラーを登録しておきます。
忘れてしまった人は、次のページで復習しましょう。
プラグインを作成してみよう
また、ボタンの作り方は次のページを参考してください。
レコード詳細画面にボタンを配置してみよう
コードは次のとおりになります。
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';
// プラグインの設定情報を取得
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];
// [翻訳]ボタンを用意
const button = document.createElement('button');
button.id = 'button-space';
button.textContent = '翻訳';
// ボタンのクリックイベント
button.onclick = async () => {
// 外部APIのリクエストを送信する処理はここに記載する
};
kintone.app.record
.getSpaceElement(spaceFieldID)
.appendChild(button);
return event;
});
})(kintone.$PLUGIN_ID);
|
DeepLのAPIを実行して「原文」の内容を翻訳する
固定リンクがコピーされました
最後に、DeepLのAPIを実行して「原文」の内容を翻訳する機能を実装します。
DeepLのAPIを実行、すなわち外部のWeb APIを実行するには、次のkintone JavaScript APIを利用します。
プラグインから外部APIを実行する
:kintone.plugin.app.proxy()
引数として、今回は次の情報を指定します。
- pluginId:当プラグインのID
- url:Web APIのURL
今回は、”https://api-free.deepl.com/v2/translate”です。
- method:HTTPメソッド
今回は”POST”です。
- data:リクエストボディ
今回は、
DeepL APIリクエスト
のdata
です。
上記の引数を指定してAPIを実行すると、プロキシサーバー上で、DeepLのAPIが実行されます。
このとき、kintone.plugin.app.setProxyConfig()
を使って保存した内容と、kintone.plugin.app.proxy()
で指定した内容が比較されます。
次の3つの情報が一致すると、リクエストヘッダーとリクエストボディが付与されます。
レスポンスとして返ってきた翻訳結果を「訳文」フィールドにセットします。
最終的なコードは次のとおりです。
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
|
/*
* handle sensitive data sample code
* Copyright (c) 2024 Cybozu
*
* Licensed under the MIT License
*/
((PLUGIN_ID) => {
'use strict';
// プラグインの設定情報を取得
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];
// 外部APIパラメータを定義
const apiUrl = 'https://api-free.deepl.com/v2/translate';
const method = 'POST';
const headers = {};
const data = {
text: [source.value],
target_lang: 'EN',
source_lang: 'JA'
};
// [翻訳]ボタンを用意
const button = document.createElement('button');
button.id = 'button-space';
button.textContent = '翻訳';
// ボタンのクリックイベント
button.onclick = async () => {
// kintone.plguin.app.proxyで外部APIを実行
const [result, statusCode] = await kintone.plugin.app
.proxy(PLUGIN_ID, apiUrl, method, headers, data);
// API実行が失敗した場合
if (statusCode !== 200) {
console.error(JSON.parse(result));
}
// API実行が成功した場合
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);
|
プラグインの動作確認
固定リンクがコピーされました
作ったプラグインが正しく動作するか確認しましょう。
作ったプラグインファイルをパッケージングし、「kintoneシステム管理」からプラグインを読み込み、
kintoneアプリ作成
で作ったアプリに追加します。
次の2点を確認します。
- 設定画面では次のことを確認します。
- 複数選択フィールドの一覧が表示され、選択できること
- 保存したプラグインの情報が初期値としてセットされること
- レコード編集画面で「翻訳」ボタンをクリックすると、原文フィールドの内容の英訳が訳文フィールドに書き込まれること
秘密情報が見えないことを確認
固定リンクがコピーされました
アプリの利用者には、プラグインの設定画面で保存したAPIキーが見えないことを確認してみましょう。
kintone.plugin.app.proxy.getConfig() で秘匿情報が見えないこと
レコードの編集画面を開いて、ブラウザーの「コンソール」タブを開きます。
次のコードを貼り付けてkintone.plugin.app.proxy.getConfig()
を実行してみましょう。
プラグインIDは後述の
「ネットワーク」タブのリクエスト情報
から確認できます。
1
2
3
4
5
|
const pluginId = '今回のプラグインのID';
const apiUrl = 'https://api-free.deepl.com/v2/translate';
const method = 'POST';
const proxyConfig = kintone.plugin.app.getProxyConfig(pluginId, apiUrl, method);
console.log(proxyConfig);
|
次のように、実行結果がnullになり、アプリの利用者には秘匿情報が見えないことを確認できました。
ネットワークタブのリクエストでも見えないこと
レコードの編集画面でブラウザーの「ネットワーク」タブを開きます。
「翻訳」ボタンをクリックして表示されたcall.json?文字列...
をクリックします。
すると、DeepL APIを実行したときのリクエスト内容が表示されます。
リクエスト内容から、ヘッダーに付与されるAPIキーが表示されていないことを確認できます。
詳細な検証方法は、
kintoneプラグインで秘匿情報を隠す〜実践編〜
を参考してください。
今回は、プラグインで認証情報などの秘匿情報を隠す方法を紹介しました。
より安全に外部サービスと連携したい方、ぜひプラグインを検討してみてください。