electronでBox APIを使って定期自動バックアップ - 事前準備編
訳あって、ローカルのファイルサーバのとあるディレクトリを定期的にZIPで固めて、Boxにアップロードするアプリケーションを作成する事になった為、やってみました。この程度フリーソフト使えばいいじゃないか?また、VBAとBox Driveの組み合わせでも良いのではないかと思ったのですが、
ちょっと出来ない理由が出てきてしまったので、作らざるを得ない状況に。electronで作る理由は設定変更などをGUIで簡単にできるようにというのが要件にあるので、以前のようなタスクトレイ常駐型アプリを作る事にします(Linuxでも作れるのですが、Trayモジュールに深刻なバグがあってトレイがうまく動作しない)
※今回準備編はAccess Tokenを取得するまでを挑戦します
目次
今回使用するモジュール等
- date-utils – npm
- node-cron – npm - 定期的に特定のルーチンを実行する為に必要なCronモジュール
- electron-store – 各種設定情報を格納する為のモジュール
- jQueryモジュール – ElectronでjQueryを使えるようにする為のモジュール
- passport - OAuth2.0認証を行うためのモジュール
- passport-box - passportを使ってBoxの認証を行うモジュール
- express – npm
- express-generator – npm
- npm-run-all – npm
- express-session – npm
- httpx-proxy-agent-config – npm - expressで社内プロキシを越える為に必要なモジュール
- request – npm - Box APIを叩く為に必要なHTTPリクエストモジュール
今回の操作上の問題点
今回、このような要件になったのにはいくつかの制限がある為です。
- 社内PCにはフリーソフト類のインストールが禁じられている為
- 作成したアプリはOKなものの、各担当者のPCを常に起動した状態のままにする事はNGとなる(労務管理上の監視に影響が出る為)
- 定期実行(1時間に1回など)を行うのに、VBAで作成するのは厄介である
- 認証の関係上、Box Driveを使えない
- バックアップ先の変更や定期バックアップの間隔変更などは担当者が行うので設定ファイルではNG(GUIが必要)
- 仮想環境は直接ネットワーク環境に参加出来ない(仮想マシンのNATで運用する必要がある)
ということで、Node.jsだけで作ろうかなと思ってたのですが、色々と退路を封じられてしまい、Electronがこういった仕事をやらせるには最適かなという事で、このような形になりました。
Box Driveが使えないとなると、Box APIを使ってファイルサーバのファイルをZIPで固めて、日付で名前を付けてアップロードしつづける。故に、Node-Cronも使用する事になります。
事前準備
electronプロジェクトの用意
今回は、以前作成したGraph APIを叩くプロジェクトのようにexpressにてプロジェクトを作成する必要があります。まずは、express-generatorをグローバルインストールする
npm i express-generator -g
いつもならば、npm init -yでプロジェクトを作る所ですが、今回は以下のコマンドでexpress-generatorを使ってプロジェクトを作成します。今回はboxmanというディレクトリを作って作業をします。
express boxman cd boxman npm install
boxmanというディレクトリが作られて、中にapp.js他たくさんの何かが生成されます。この段階で、以下のコマンドでテストをしてみます。
npm start
そして、FireFoxなどでhttp://localhost:3000/にアクセスした場合に、welcome to expressが表示されれば成功です。Ctrl+Cでnpm startしたサーバを停止できます。プロジェクトフォルダ内に空のindex.jsを作成しておきましょう。
既にマシン内で3000番ポートを使用していたり、仮想マシンにポートフォワーディングしているとエラーになるので要注意です。「Unknown authentication strategy "box"」というエラーが出て、認証が出来なくなります。
モジュールを追加する
前項で用意したプロジェクトに対してターミナルより以下のコマンドを入力して、npmでモジュールを追加しておきます。
npm install date-utils npm install --save node-cron npm install electron-store npm i jquery npm install passport npm install passport-box npm i npm-run-all --save-dev npm i express-session --save-dev npm i ejs npm i request npm i httpx-proxy-agent-config
また、BoxのOAuth2.0認証には、passportおよびpassport-boxを利用して行います。全てのインストールを終えたら、生成されているpackage.jsonを開いて、"main": "index.js",をversionの行の次にでも追記して保存します。
keytarに関してはリビルドが必要なので、使う場合にはこちらを参考にネイティブリビルドが必要です。
Boxでプロジェクト作成
Box側ではClient IDやClient Secret等を作る必要性があります。以下の手順でBox Developers Portalにて作成しましょう。
- Box Developers Portalにログインする
- マイアプリにて「アプリの新規作成」をクリックする
- 次の画面では「カスタムアプリ」をクリックし、次へ進みます。
- 認証方法のページでは、「標準OAuth2.0」をクリックし、次へ進みます。
- アプリの名前は適当に設定し、「アプリの作成」をクリックします。アプリ名は同じものが設定できませんので注意!!
- アプリの表示をクリックして設定データを取得しておきます。
- OAuth2.0資格情報の欄にて、クライアントIDおよびクライアント機密コード(Secret)をコピーして控えて置きます。
- OAuth2.0リダイレクトURIですが、ローカルアプリなので「http://127.0.0.1:3000/auth/box/callback」を入れておきます
- アプリケーションのスコープでは、許可するアクションにチェックを入れます。
- 高度な機能では、「ユーザとして操作を実行」を有効にします。
- CORSドメインはウェブなどで利用する場合に許可送信元として入力する場合にそのサイトのドメインを入れておきます。
- 変更を保存するボタンをクリックして完了
リダイレクトURLではlocalhostの3000版にリダイレクトさせるようにしています。
図:リダイレクトURLの設定が嵌りどころだったりします。
expressのテスト
index.jsにとりあえず以下のテスト用コードを追記してみる。npm startでexpressのサーバを起動した状態で、electron .で起動してみて、welcome to expressが表示されればテストはOK。
'use strict'; //標準モジュールの呼び出し const electron = require('electron'); const { app, Tray, Menu, dialog } = require('electron'); const BrowserWindow = electron.BrowserWindow; var fs = require('fs'); //Node.js側とHTML側で通信をするモジュール const ipcMain = require('electron').ipcMain; // メインウィンドウはグローバル宣言 let mainWindow = null; //Expressにアクセスする const express = require('./app'); express.listen(3000, 'localhost'); // Electronの初期化完了後に実行 app.on('ready', () => { // メイン画面の表示。ウィンドウの幅、高さを指定できる mainWindow = new BrowserWindow({ 'width': 800, 'height': 600, 'autoHideMenuBar':true, //nodeIntegrationを有効にしないとrenderProcessでrequireを使えない。v5.0.0ではデフォルトで廃止 webPreferences: { nodeIntegration: true }, 'resizable':true, 'fullscreenable':true, 'fullscreen':false }); //初期ページの表示 mainWindow.loadURL('http://localhost:3000') // ウィンドウが閉じられたらアプリも終了 mainWindow.on('closed', () => { mainWindow = null }) }) //全てのウィンドウが閉じたら終了 app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } })
しかし、このままでは別途npm startでexpressを立ち上げて置いてから、electron .で起動する必要があるため、package.jsonのscriptsのラインにnpm-run-allを使って複数のタスクを同時に起動するように細工をします。これで、テスト時は、electron .だけで全て起動します。
"scripts": { "start": "node ./bin/www", "electron": "electron .", "dev": "npm-run-all --parallel electron start" }
図:expressサーバをelectronから開けた
社内プロキシーを越える
社内向けのアプリを作る上での実は一番の障害は、「プロキシーサーバ」だと思います。npmにしかり、electron、expressそれぞれにプロキシーを超える設定をする必要がある場合があります(特に大企業の場合、普通にプロキシーサーバ経由になってる場合が多く、これを超える設定を行わないと、外部と通信が出来ない)
electron側の設定
こちらは簡単です。プロキシーサーバもしくはプロキシーのpacファイルの指定を1行追加すれば、外部との通信が可能になります。これを入れておかないと、外部のCSSなどもロードされないので、注意。
社内プロキシ環境下で設定無しでpassport-boxにて認証をしAccess Tokenを取得しようとすると「InternalOAuthError: Failed to obtain access token」というエラーが出て、プロセスが停止してしまいます。
//プロキシーサーバ直の場合 app.commandLine.appendSwitch('proxy-server', 'http://192.168.1.2:8080'); //プロキシーのPACファイルの場合 app.commandLine.appendSwitch('proxy-pac-url', 'http://192.168.1.2:8080/proxy.pac');
図:プロキシー経由させずにDNSエラーが出る
パッケージを追加
今回、expressをプロキシーの背後で動かす為に、httpx-proxy-agent-configというモジュールが必要でした。公式サイトなどでのコマンドラインで打つとインストールが出来ません。
npm i httpx-proxy-agent-config
関連モジュールもインストールされて、これでexpressからプロキシー経由で外に出る事が可能です。また、requestモジュール等にも別途Proxy設定せずとも有効になるので、非常に便利なモジュールです。
express側の設定
expressのapp.js側に追加するコードは以下の通り。
var proxy = require('httpx-proxy-agent-config'); //プロキシーの設定 proxy.install({ http_proxy: 'http://proxyのURL:8080', https_proxy: 'http://proxyのURL:8080', //blacklist: ['localhost'] });
たったこれだけです。http_proxyとhttps_proxyは環境によっては同じURLで問題ないかと。ただし、blacklistについてコメントアウトしてありますが、blacklistに入れたものはこのモジュールがブロックするので、注意してください。今回はelectronがlocalhost:3000にアクセスする必要があるので、localhostなどもblacklistには入れません。
以降のexpressの外部への通信はすべてプロキシー経由になります。
一部のパッケージの為に
一部のnpmモジュールに於いて(例えば、electron-packagerなど)、利用時にプロキシ越え出来ずにエラー(ENOTFOUND github.com)が出る場合があります。環境変数にプロキシ設定をセットしたはずなのに。。
ということで調べてみたら、electron本体ではVersion7.0以降、また一部のこういったモジュールでは、最新版より環境変数の参照先が変更されているようです。ということで、Windowsの環境変数に以下の設定を追加します。
- ELECTRON_GET_USE_PROXY:値はtrue
- GLOBAL_AGENT_HTTP_PROXY:値はhttpから始まるプロキシアドレス:プロキシポート
- GLOBAL_AGENT_HTTPS_PROXY:値はhttpから始まるプロキシアドレス:プロキシポート
セットしたら、さっそくelectron-packagerでパッケージングを実行してみたところ、エラーが解消し無事にパッケージの作成が成功しました。今後他のモジュールも移行していくのかもしれません。
ソースコード
ここからが一番の峠道です。通常であればindex.jsに色々と記述する所ですが、今回の認証アプリの場合には、app.jsの方に記述する事になります。自動生成されたapp.jsにはすでに色々記述されているので、これを改造して使います。
今回は、こちらのソースコードを利用しています(少し改変を加えています)。また、利用するejsファイルについてもexampleにあるものを追加しています。
app.js側コード
//モジュールの読み込み var createError = require('http-errors'); var express = require('express'); var path = require('path'); const join = require('path').join; var cookieParser = require('cookie-parser'); var logger = require('morgan'); var bodyParser = require('body-parser'); var session = require('express-session'); var fs = require("fs"); var logger = require('morgan'); const storeman = require('electron-store'); const storedata = new storeman(); var proxy = require('httpx-proxy-agent-config'); //HTTPリクエスト用モジュールの読み込み var request = require('request'); //プロキシーの設定 proxy.install({ http_proxy: 'http://プロキシのURL:プロキシのポート番号', https_proxy: 'http://プロキシのURL:プロキシのポート番号', //blacklist: ['localhost'] }); //expressの読み込み var app = express(); //passportの設定 var passport = require('passport'); var BoxStrategy = require('passport-box').Strategy; passport.serializeUser(function(user, done) { done(null, user); }); passport.deserializeUser(function(user, done) { done(null, user); }); //認証用データ var redirecturi = "http://127.0.0.1:3000/auth/box/callback"; var clientID = storedata.get("clientid"); var clientsecret = storedata.get("secret"); //認証フロー if(clientID == undefined){ //何もしない }else{ passport.use(new BoxStrategy({ clientID: clientID, clientSecret: clientsecret, callbackURL: redirecturi }, function(accessToken, refreshToken, profile, done) { process.nextTick(function () { //Access Tokenなどを書き出し console.log(accessToken); return done(null, profile); }); } )); } //Expressの設定 app.use(logger('dev')); app.set('views', __dirname + '/views'); app.set('view engine', 'ejs'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(require('express-session')({ secret: 'keyboard cat', resave: false, saveUninitialized: false })); app.use(passport.initialize()); app.use(passport.session()); app.use(express.static(path.join(__dirname, '/public'))); //初期ページの表示 app.get('/', function(req, res){ res.render('index', { user: req.user }); }); //アカウントを表示 app.get('/account', ensureAuthenticated, function(req, res){ res.render('account', { user: req.user }); }); //ログイン実行時 app.get('/login', function(req, res){ res.render('login', { user: req.user }); }); //認証実行時 app.get('/auth/box', passport.authenticate('box'), function(req, res){ // The request will be redirected to Box for authentication, so this // function will not be called. }); //コールバック時 app.get('/auth/box/callback', passport.authenticate('box', { failureRedirect: '/login' }), function(req, res) { res.redirect('/'); }); //ログアウト時 app.get('/logout', function(req, res){ req.logout(); res.redirect('/'); }); //404検知 app.use(function(req, res, next) { next(createError(404)); }); function ensureAuthenticated(req, res, next) { if (req.isAuthenticated()) { return next(); } res.redirect('/login') } module.exports = app;
- Microsoft Graph APIを叩くコードとほとんどが同じになります。このモジュールの良い点です。
- http-proxy-agent-configによるプロキシを透過する為の設定を冒頭に入れてあります。
- electron-storeに格納されているClient IDとClient Secretを用いて、BoxStrategyにて認証を実行します。
- 初期ページ、ログイン用ページなどのejsファイルはexpressにてREST APIのように構築しています。コールバック先URLもこれで実現。
- Access Token書き出し用のルーチンは次回以降作成します。
- 初回起動時はClient IDなどがセットされていないので、passport.useを実行しないように判定を入れています(エラーになってしまうため)
- ちなみに、electronで実行しアプリを立ち上げると、ウェブブラウザでhttp://localhost:3000にアクセスすると普通に認証ページが開けたりします。遠隔でアプリの設定変更や、認証の実行などが可能です。
index.js側コード
'use strict'; //標準モジュールの呼び出し const electron = require('electron'); const { app, Tray, Menu, dialog } = require('electron'); const BrowserWindow = electron.BrowserWindow; var fs = require('fs'); const path = require('path'); const join = require('path').join; //Node.js側とHTML側で通信をするモジュール const ipcMain = require('electron').ipcMain; //追加モジュールの宣言 const keytar = require('keytar'); const Store = require('electron-store'); const store = new Store(); var Promise = require('promise'); var CronJob = require('node-cron'); const openAboutWindow = require('about-window').default; var AutoLaunch = require('auto-launch'); var jobs; //自動起動設定を初期化 var boxman = new AutoLaunch({ name:'Box Sync', path:app.getPath('exe'), }); boxman.isEnabled() .then(function(isEnabled){ if(isEnabled){ return; } //デバッグ時にはここはコメントアウトしておこう //boxman.enable(); }) .catch(function(err){ //エラー捕捉時の動作 }); //cronjobをセットする var autoreload = store.get("interval"); if(autoreload == "" || autoreload == undefined){ //設定がないので、cron設定は行わない }else{ setCronJob(autoreload); } //cronJob設定を行う function setCronJob(timeval){ if(jobs == "" || jobs == undefined){ //既存のジョブ設定がないので、何もしない }else{ //既存のジョブ設定を削除する jobs.destroy(); } //crontimeを設定する(毎時 var cronTime = "0 0 */" + Number(timeval) + " * * *"; //引数の分を元に、分単位トリガーを設置する jobs = CronJob.schedule(cronTime, () => { updatefolder(0); }); console.log(timeval + "hour CronJob Setting Up"); } //二重起動の防止 const doubleboot = app.requestSingleInstanceLock(); if(!doubleboot){ app.quit(); } //終了時処理 process.on('exit', function(){ //終了時メッセージ console.log('Exiting Application....'); //終了処理 //setWindow = null; }); //Ctrl+Cで強制終了時の処理 process.on('SIGINT',function(){ process.exit(0); }); // メインウィンドウはグローバル宣言 let mainWindow = null; let setWindow = null; let tray = null; let splash = null; //Expressにアクセスする const express = require('./app'); express.listen(3000, 'localhost'); //electron自体をプロキシー通過できるようにする app.commandLine.appendSwitch('proxy-pac-url', 'プロキシのpacファイルのURL'); // Electronの初期化完了後に実行 app.on('ready', () => { //トレイアイコンを設定 tray = new Tray(__dirname + '/img/naruto.ico') //トレイのコンテキストメニューを設定 const contextMenu = Menu.buildFromTemplate([ {label:'設定', click(menuItem){ setwindowopen(); //setting.htmlを開く }}, {label:'Box認証実行',click(menuItem){ boxauth(); }}, {type:'separator'}, {label:'バージョン',click(menuItem){ openAboutWindow({ icon_path: join(__dirname,'/img/naruto.ico'), copyright: 'Copyright (c) 2020 officeforest', package_json_dir: __dirname, homepage: 'https://officeforest.org/', product_name: 'Box Sync', win_title: 'バージョン情報', win_options:{ parent:null, modal:true }, }); app.on('window-all-closed', () => { //なにもしない //これをいれておかないと、バージョン情報を閉じると、アプリも終わっちゃう }); }}, {label:'閉じる',click(menuItem){ app.quit(); }} ]); //ツールチップの設定 tray.setToolTip("Box Sync"); //右クリック時にコンテキストメニュー表示をセットする tray.on('right-click',() =>{ //メニューを表示 tray.popUpContextMenu(contextMenu); }); //スプラッシュスクリーンを生成 splash = new BrowserWindow({ width: 640, height: 480, frame: false, //ブラウザをフレームレスで表示 transparent: true //ブラウザの背景を透過 }); splash.webContents.once('did-finish-load', function(){ setTimeout(function(){ //5秒後にクローズする splash.close(); }, 5000); }); //表示するHTMLファイルを指定 splash.loadURL('file://' + __dirname + '/splash.html'); splash.on('closed', function() { //キャッシュを捨てる electron.session.defaultSession.clearCache(() => {}) splash = null; }); app.on('window-all-closed', () => { //なにもしない //これをいれておかないと、全部のウィンドウが閉じられるとアプリまで閉じてしまう、。 }); }) //セッティングウィンドウを表示する function setwindowopen(){ //セッティングウィンドウ setWindow = new BrowserWindow({ 'width': 580, 'height': 450, 'autoHideMenuBar':true, //nodeIntegrationを有効にしないとrenderProcessでrequireを使えない。v5.0.0ではデフォルトで廃止 webPreferences: { nodeIntegration: true }, 'resizable':false, 'fullscreenable':false, 'fullscreen':false, 'modal':true, 'minimizable':false, 'maximizable':false, 'icon':__dirname + "/img/ramens.ico", 'title':'アプリの設定' }); setWindow.loadURL('file://' + __dirname + '/setting.html'); setWindow.on('closed', function() { //キャッシュを捨てる electron.session.defaultSession.clearCache(() => {}) setWindow = null; }); // 全てのウィンドウが閉じたときの処理 app.on('window-all-closed', () => { //なにもしない //これをいれておかないと、全部のウィンドウが閉じられるとアプリまで閉じてしまう、。 }); } //Box認証実行 function boxauth(){ // メイン画面の表示。ウィンドウの幅、高さを指定できる mainWindow = new BrowserWindow({ 'width': 800, 'height': 600, 'autoHideMenuBar':true, //nodeIntegrationを有効にしないとrenderProcessでrequireを使えない。v5.0.0ではデフォルトで廃止 webPreferences: { nodeIntegration: true }, 'resizable':true, 'fullscreenable':true, 'fullscreen':false }); //express側の初期ページを表示 mainWindow.loadURL('http://localhost:3000') // ウィンドウが閉じられた場合の処理 mainWindow.on('closed', () => { electron.session.defaultSession.clearCache(() => {}) mainWindow = null }) // 全てのウィンドウが閉じたときの処理 app.on('window-all-closed', () => { //なにもしない //これをいれておかないと、全部のウィンドウが閉じられるとアプリまで閉じてしまう、。 }); } //ipc通信関係 //データ通信関係 ipcMain.on('async', function( event, args, args2){ //コマンド名によって処理を開始 switch(args){ case "init": //レンダラー側に送りつける設定を集める var array = []; array.push(store.get("folpath")); array.push(store.get("boxid")); array.push(store.get("interval")); array.push(store.get("clientid")); array.push(store.get("secret")); //レンダラー側に値を送信 event.sender.send('init', array); break; } }); //Windowのコントロール ipcMain.on('closeset', function( event, args ){ //コマンド名によって処理を開始 switch(args){ case "setting": //セッティングウィンドウを非表示にする setWindow.close(); break; case "open": //セッティングウィンドウを表示する setwindowopen(); break; default: break; } }); //フォルダ選択ダイアログ ipcMain.on('folderdialog', function( event ){ //メッセージを表示 //メッセージオプション var options ={ properties: ['openDirectory'], title:"フォルダの選択", defaultPath: '.' } //表示する dialog.showOpenDialog(setWindow,options,(items)=>{ //レンダラー側に値を送信 event.sender.send('folderpath', items); }); }); //設定値関係をコントロールする ipcMain.on('setstore', function( event, args ){ //配列データを受け取る var array = args; //他の情報はelectron-storeで保存する store.set("folpath",array[0]); store.set("boxid",array[1]); store.set("interval",array[2]); store.set("clientid",array[3]); store.set("secret",array[4]); //セッティングウィンドウを非表示にする setWindow.close(); //reloadの値が1~24時の指定があれば、cronを設定 if(array[1]>=1 && array[1]<=24){ setCronJob(array[2]); } //メッセージを表示 //メッセージオプション var options ={ type:'info', title:"設定の保存", button:['OK'], message:'セット完了', detail:'アプリに設定が保存されました。' } //表示する dialog.showMessageBox(null,options); }); //フォルダ同期メインルーチン function updatefolder(){ }
- setCronJobにて今回は設定値にある時間を持って、何時間毎に実行するかを指定します。
- Electron自体をプロキシ越えられるようにする為のapp.commandLine.appendSwitchを入れてあります(今回はpacファイルを指定する)
- タスクトレイ常駐型アプリなので、その為の一連の設定を入れてあります。
- boxauthにて、express側のlocalhost:3000番ポートをelectronで表示させています。これが認証用の入り口になります。
- 今回はフォルダダイアログ(openDirectory)にて、バックアップするフォルダを指定する機能を付け加えています。
- updatefolderは実際にバックアップするルーチンですが、次回以降作成します。
setting.html側コード
各種設定を保存する為のセッティング用ウィンドウのファイルになります。今回は、同期するフォルダ・BoxフォルダID・同期間隔・Client ID・Client Secretの5項目(これに次回暗号化用のパスワード欄も)を保存できるようにします。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> var $ = jQuery = require("jquery") </script> <script type="text/javascript" src="js/jquery-ui.min.js"></script> <script src="js/jquery.touch-punch.min.js"></script> <link rel="stylesheet" href="css/jquery-ui.css"> <link rel="stylesheet" href="css/setting.css"> <script src="index.js"></script> <script> $(function() { $( "input[type=submit], a, button" ) .button() .click(function() { }); }); // IPC通信を行う var ipcRenderer = require( 'electron' ).ipcRenderer; window.onload = function () { //受信レンダラーの準備 testAsync(); }; //メインプロセス側からの非同期に通信を受信待機する(1回だけ実行) function testAsync() { //各種雑多なメッセージを受け取る ipcRenderer.on('msg', function(event,arg) { alert(arg); }); //保存済みデータを受け取る ipcRenderer.on('init',function(event,arg){ //受け取ったデータをHTMLのボックスに反映する var array = arg; //配列データを各項目に入れてあげる document.getElementById("folpath").value = array[0]; document.getElementById("boxid").value = array[1]; document.getElementById("interval").value = array[2]; document.getElementById("clientid").value = array[3]; document.getElementById("clientsecret").value = array[4]; }); //ファイルのパスを取得する ipcRenderer.on('folderpath',function(event,arg){ //受け取ったデータをHTMLのボックスに反映する var array = arg; //ファイルのパスを入れてあげる document.getElementById("folpath").value = array[0]; }); //設定値をelectron-storeから取得する ipcRenderer.send('async', "init"); } //キャンセル時に設定ウィンドウを閉じる function notsetting(){ ipcRenderer.send('closeset', "setting"); } //セッティング項目を保存する function savesetting(){ //入力値のvalidation var validata = ""; var array = []; //フォルダのパス validata = document.getElementById("folpath").value; if(validata == ""){ alert("フォルダのパスが指定されていません"); document.getElementById("folpath").focus(); return; }else{ array.push(validata); } //BoxフォルダID validata = document.getElementById("boxid").value; if(validata == ""){ alert("Boxの保存先フォルダIDが指定されていません"); document.getElementById("boxid").focus(); return; }else{ array.push(validata); } //同機間隔 var regex = new RegExp(/^[0-9]+$/); validata = document.getElementById("interval").value; //数値かどうかチェック if(regex.test(validata)){ //1~59の値かどうかをチェック if(validata >= 1 && validata <= 24){ array.push(validata); }else{ //エラーを返す alert("1~24の範囲の値を入力してください。"); return; } }else{ //空白かどうかをチェック if(validata == "" || validata == undefined){ //空白はよしとする array.push(""); }else{ //エラー表示 alert("数値と空文字以外の値が入っていますよ。"); return; } } //クライアントID validata = document.getElementById("clientid").value; if(validata == ""){ //エラー表示 alert("クライアントIDが入っていません"); return; }else{ array.push(validata); } //クライアントシークレット validata = document.getElementById("clientsecret").value; if(validata == ""){ //エラー表示 alert("クライアントシークレットが入っていません"); return; }else{ array.push(validata); } //メインプロセスに処理を送る ipcRenderer.send('setstore', array); } //フォルダ選択ダイアログを表示 function filedialog(){ //メインプロセスに処理を送る ipcRenderer.send('folderdialog'); } </script> </head> <body> <!-- セッティング項目を表示 --> <form class="contact_form" action="#" method="post" name="contact_form"> <ul> <li> <h2>アプリの設定</h2> <span class="required_notification">*印は、必須入力項目です</span> </li> <li> <label for="uid">同期するフォルダ:</label> <input type="uid" name="uid" placeholder="" style="width:150px" id="folpath" required /> </li> <li> <label for="uid">BoxフォルダID:</label> <input type="uid" name="uid2" placeholder="" style="width:150px" id="boxid" required /> </li> <li> <label for="name">同期間隔(時):</label> <input type="number" name="cron" placeholder="1" style="width:50px" id="interval" required /> <span class="form_hint">リストをリロードする時間(1~24時間を指定)</span> </li> <li> <label for="uid">Client ID:</label> <input type="text" name="clientid" placeholder="" style="width:150px" id="clientid" required /> </li> <li> <label for="uid">Client Secret:</label> <input type="password" name="clientsecret" placeholder="" style="width:150px" id="clientsecret" required /> </li> </ul> </form> <p> <center> <button onClick='savesetting()' id="saveman" class="action" title='設定を保存する'>設定保存</button> <button onClick='notsetting()' id="cancelman" class="action" title='キャンセル'>キャンセル</button> <button onClick='filedialog(0)' id="fileman" class="action" title="フォルダの選択">フォルダの選択</button> </center> </p> </body> </html>
図:アプリの設定ダイアログ
関連リンク
- nodeでシンプルにOAuth2認証(google, facebook)
- Node.js+Passport+Google 認証を使ってみた
- 【Node.js】node-oauthを使ってTwitterのOAuth認証する方法
- Node.jsでGoogle APIをOAuth2.0認証してAPIを使う方法
- 【連載第1回】passportを使ってoauth認証を実装してみよう【Twitter編】
- Node.jsで指定したファイルやフォルダをZIP圧縮する方法
- 続・Electronでファイルやフォルダの選択(保存もあるでよ)
- Failed to obtain access token using Github Authentication