Electron + WordPress =
Tomasz Dziuda
❤
Dlaczego?
Umiem w Electrony...
Źródło: https://getpublii.com
w WordPressy trochę też ...
coraz bardziej cenię swoją prywatność
Czego użyjemy?
Przyśpieszony Kurs Electrona
Źródło: https://atom.io/
Źródło: https://desktop.github.com/
=
Przyśpieszony Kurs Electrona
=
Przyśpieszony Kurs Electrona
= +
Przyśpieszony Kurs Electrona
= + +
Przyśpieszony Kurs Electrona
Przyśpieszony Kurs Electrona cz.2
Wątek główny
Przyśpieszony Kurs Electrona cz.2
Wątek główny
API systemu
Przyśpieszony Kurs Electrona cz.2
...
Wątki renderujące
Wątek główny
API systemu
Przyśpieszony Kurs Electrona cz.2
...
Wątki renderujące
Wątek główny
API systemu
IPC IPC IPC
Przyśpieszony Kurs Electrona cz.3
Build cross platform desktop apps with
JavaScript, HTML, and CSS
Przyśpieszony Kurs Electrona cz.3
Build cross platform desktop apps with
JavaScript, HTML, and CSS
XSS
Przyśpieszony Kurs Electrona cz.4
Brak możliwości ukrycia 

kodu źródłowego
Dlaczego WordPress?
Gotowe REST API
{REST API}
GET POST PUT DELETE
Gotowy panel zarządzania
Źródło: https://www.contentful.com/
Połowa pracy za nami a
jeszcze nie zaczęliśmy ;-)
1 WordPress = wiele aplikacji
Dlaczego React?
JSX
<App>
<Header />
<Content />
<Footer />
<Sidebar />
</App>
Ekosystem
Używają go w Automattic ;-)
Źródło: https://developer.wordpress.com/calypso/
Przydatne narzędzia
...mi pomogły ;-)
Postman
Źródło: https://www.getpostman.com/
Devtron
Źródło: https://electron.atom.io/devtron/
Założenia
WP Notes
WP Notes
• Synchronizacja notatek po uruchomieniu/zalogowaniu
• Obsługa wielu użytkowników
• Edytor Markdown
• Możliwość dodawania, edycji i usuwania notatek
• Wyszukiwarka notatek
WP Notes
• Synchronizacja notatek po uruchomieniu/zalogowaniu
• Obsługa wielu użytkowników
• Edytor Markdown
• Możliwość dodawania, edycji i usuwania notatek
• Wyszukiwarka notatek
WP Notes
• Synchronizacja notatek po uruchomieniu/zalogowaniu
• Obsługa wielu użytkowników
• Edytor Markdown
• Możliwość dodawania, edycji i usuwania notatek
• Wyszukiwarka notatek
WP Notes
• Synchronizacja notatek po uruchomieniu/zalogowaniu
• Obsługa wielu użytkowników
• Edytor Markdown
• Możliwość dodawania, edycji i usuwania notatek
• Wyszukiwarka notatek
WP Notes
• Synchronizacja notatek po uruchomieniu/zalogowaniu
• Obsługa wielu użytkowników
• Edytor Markdown
• Możliwość dodawania, edycji i usuwania notatek
• Wyszukiwarka notatek
Implementacja
Składowe
Składowe
Odpowiednie endpointy REST API Plugin + CPT
Składowe
Odpowiednie endpointy REST API
Autoryzacja komunikacji
Plugin + CPT
JWT
Składowe
Odpowiednie endpointy REST API
Autoryzacja komunikacji
Aplikacja napisana w Electronie
Plugin + CPT
JWT
Electron + React
Endpointy i CPT
Plugin dla aplikacji
Plugin dla aplikacji
• Tworzy dedykowany Custom Post Type dla danych
• Tworzy potrzebne endpointy
• Modyfikuje istniejące endpointy
Plugin dla aplikacji
• Tworzy dedykowany Custom Post Type dla danych
• Tworzy potrzebne endpointy
• Modyfikuje istniejące endpointy
Plugin dla aplikacji
• Tworzy dedykowany Custom Post Type dla danych
• Tworzy potrzebne endpointy
• Modyfikuje istniejące endpointy
Dlaczego Custom Post Type?
Dlaczego Custom Post Type?
• Możemy na jednym WordPressie oprzeć kilka
aplikacji
• CPT mogą mieć własne endpointy
• Możemy te endpointy bez obaw dostosować do
swoich potrzeb
Dlaczego Custom Post Type?
• Możemy na jednym WordPressie oprzeć kilka
aplikacji
• CPT mogą mieć własne endpointy
• Możemy te endpointy bez obaw dostosować do
swoich potrzeb
Dlaczego Custom Post Type?
• Możemy na jednym WordPressie oprzeć kilka
aplikacji
• CPT mogą mieć własne endpointy
• Możemy te endpointy bez obaw dostosować do
swoich potrzeb
Endpointy
/wp-json/wp/v2/wp-notes
$args = array(
// ...
'show_in_rest' => true,
'rest_base' => 'wp-notes'
);
register_post_type( 'wp_notes', $args );
/wp-json/wp-notes/v1/notes?author=X
[
{
"id": 36,
"modificationDate": 1495370276000
},
...
{
"id": 13,
"modificationDate": 1495295589000
},
{
"id": 9,
"modificationDate": 1495368831000
}
]
https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/
Posty prywatne są przydatne ;)
Pobieranie prywatnych postów
1) Musimy być autoryzowani jako twórca wpisu
2) W URL-u REST API dodajemy:
wp-json/wp/v2/wp-notes/?status=private
Pamiętajmy o strefach czasowych
CEST UTC
:-(
[
{
...
"date_gmt": "2017-05-21T12:37:56",
...
"modified_gmt": "2017-05-21T12:37:56",
...
}
]
new Date().getTime() // Zwraca czas UTC
Modyfikacje endpointów
function dziudek_wp_notes_use_raw_content( $data, $post, $request ) {
$data->data['content']['plaintext'] = $post->post_content;
$data->data['title']['plaintext'] = $post->post_title;
$data->data['modified_gmt'] =
strtotime($post->post_modified_gmt . ' UTC') * 1000;
return $data;
}
add_filter(
'rest_prepare_wp_notes',
'dziudek_wp_notes_use_raw_content',
10, 3
);
function dziudek_wp_notes_use_raw_content( $data, $post, $request ) {
$data->data['content']['plaintext'] = $post->post_content;
$data->data['title']['plaintext'] = $post->post_title;
$data->data['modified_gmt'] =
strtotime($post->post_modified_gmt . ' UTC') * 1000;
return $data;
}
add_filter(
'rest_prepare_wp_notes',
'dziudek_wp_notes_use_raw_content',
10, 3
);
function dziudek_wp_notes_use_raw_content( $data, $post, $request ) {
$data->data['content']['plaintext'] = $post->post_content;
$data->data['title']['plaintext'] = $post->post_title;
$data->data['modified_gmt'] =
strtotime($post->post_modified_gmt . ' UTC') * 1000;
return $data;
}
add_filter(
'rest_prepare_wp_notes',
'dziudek_wp_notes_use_raw_content',
10, 3
);
function dziudek_wp_notes_use_raw_content( $data, $post, $request ) {
$data->data['content']['plaintext'] = $post->post_content;
$data->data['title']['plaintext'] = $post->post_title;
$data->data['modified_gmt'] =
strtotime($post->post_modified_gmt . ' UTC') * 1000;
return $data;
}
add_filter(
'rest_prepare_wp_notes',
'dziudek_wp_notes_use_raw_content',
10, 3
);
Autoryzacja
Źródło: https://jwt.io/
Źródło: https://jwt.io/
Dane w tokenie NIE SĄ
szyfrowane
Przy komunikacji z REST API
korzystaj z połączenia SSL
Źródło: https://pl.wordpress.org/plugins/jwt-authentication-for-wp-rest-api/
Gdy JWT nie działają dodaj w .htaccess:
RewriteEngine on
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
Czasem może też być potrzebne dodanie:
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
Autoryzacja - krok 1
Wysyłamy zapytaniem POST do endpointa /wp-json/jwt-auth/v1/token 

login i hasło użytkownika, którego chcemy autoryzować:
{
username: 'admin',
password: 'password'
}
Autoryzacja - krok 1
Wysyłamy zapytaniem POST do endpointa /wp-json/jwt-auth/v1/token 

login i hasło użytkownika, którego chcemy autoryzować:
{
username: 'admin',
password: 'password'
}
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ...",
"user_display_name": "admin",
"user_email": "admin@localhost.dev",
"user_nicename": "admin"
}
Gdy dane są poprawne otrzymujemy token i dane użytkownika:
Autoryzacja - krok 2
Do każdego zapytania wymagającego 

autoryzacji dodajemy nagłówek:
Authorization: Bearer WARTOŚĆ_TOKENA
Aplikacja
Struktura aplikacji
<App>
Struktura aplikacji
<App>
<Editor><Sidebar>
Struktura aplikacji
<App>
<Editor><Sidebar>
<Search>
<List>
<ReactMarkdown>
Struktura aplikacji
<App>
<Editor><Sidebar>
<Search>
<List>
<Item>
<ReactMarkdown>
<Item>
<Item>
Komunikacja pomiędzy
komponentami
Wzorzec obserwatora
class EventEmitter extends React.Component {
dispatch(event, data) { ... }
subscribe(event, callback) { ... }
unsubscribe(event, callback) { ... }
}
Wzorzec obserwatora
class EventEmitter extends React.Component {
dispatch(event, data) { ... }
subscribe(event, callback) { ... }
unsubscribe(event, callback) { ... }
}
Wzorzec obserwatora
class EventEmitter extends React.Component {
dispatch(event, data) { ... }
subscribe(event, callback) { ... }
unsubscribe(event, callback) { ... }
}
Wzorzec obserwatora
class EventEmitter extends React.Component {
dispatch(event, data) { ... }
subscribe(event, callback) { ... }
unsubscribe(event, callback) { ... }
}
Wzorzec obserwatora
class EventEmitter extends React.Component {
dispatch(event, data) { ... }
subscribe(event, callback) { ... }
unsubscribe(event, callback) { ... }
}
class App extends React.Component
class App extends EventEmitter
Wzorzec obserwatora
class EventEmitter extends React.Component {
dispatch(event, data) { ... }
subscribe(event, callback) { ... }
unsubscribe(event, callback) { ... }
}
class App extends React.Component
class App extends EventEmitter
<App>
<Editor><Sidebar>
<Search>
<List>
<Item>
<ReactMarkdown>
<Item>
<Item>
<App>
<Editor><Sidebar>
<Search>
<List>
<Item>
<ReactMarkdown>
<Item>
<Item>
<App>
<Editor><Sidebar>
<Search>
<List>
<Item>
<ReactMarkdown>
<Item>
<Item>
subscribe('delete-item', callback)
<App>
<Editor><Sidebar>
<Search>
<List>
<Item>
<ReactMarkdown>
<Item>
<Item>
distpatch('delete-item', itemID)
Przechowywanie danych
Przechowywanie danych
user_ID_1
1.md
2.md
3.md
files.json
user_ID_2
4.md
5.md
files.json
Przechowywanie danych
user_ID_1
1.md
2.md
3.md
files.json
user_ID_2
4.md
5.md
files.json
[
{
id: 10,
title: "Post title",
modificationDate: 123049044
},
{
id: 10,
title: "Post title",
modificationDate: 123049044
},
...
]
Przechowywanie danych
files.jsonuser_ID_1
1.md
2.md
3.md
files.json
user_ID_2
4.md
5.md
files.json
Synchronizacja danych
Pobranie danych z endpointa synchronizacji
dziudek-wp-notes/1/
1.md
2.md
3.md
files.json
[{
"id": 1,
"modificationDate": 102
},

{
"id": 4,
"modificationDate": 105
}]
wp-json/wp-notes/v1/notes?author=1
Porównanie dat modyfikacji postów
[{
"id": 1,
"modificationDate": 102
},

{
"id": 4,
"modificationDate": 105
}]
[{
"id": 1,
"modificationDate": 101
},

{
"id": 2,
"modificationDate": 102
},
{
"id": 3,
"modificationDate": 103
}]
Lokalnie Zdalnie
Usunięcie nieistniejących plików
dziudek-wp-notes/1/
1.md
2.md
3.md
files.json
[{
"id": 1,
"modificationDate": 102
},

{
"id": 4,
"modificationDate": 105
}]
Pobranie i zaktualizowanie zmienionych wpisów
dziudek-wp-notes/1/
1.md
4.md
files.json
[{
"id": 1,
"modificationDate": 102
},

{
"id": 4,
"modificationDate": 105
}]
Zaktualizowanie pliku files.json
dziudek-wp-notes/1/
1.md
4.md
files.json
[{
"id": 1,
"modificationDate": 102
},

{
"id": 4,
"modificationDate": 105
}]
Pobieranie danych: node-fetch
Źródło: https://github.com/bitinn/node-fetch
Fetch API: https://developers.google.com/web/updates/2015/03/introduction-to-fetch
fetch('http://httpbin.org/post', {
method: 'POST', 

body: stream 

})
.then(res => res.json())
.then(json => console.log(json));
Edytor
Źródło: https://github.com/JedWatson/react-md-editor
render: function() {
return <Editor
value={this.state.code}
options={configObject}
onChange={this.updateCode} />
}
Źródło: https://codemirror.net/
Skróty klawiaturowe
import {remote} from 'electron';
const template = [{
label: "Edit",
submenu: [{
label: "Zapisz zmiany",
accelerator: "CmdOrCtrl+S",
click: function() {
// ..
}
]}
];
const menu = remote.Menu.buildFromTemplate(template);
remote.Menu.setApplicationMenu(menu);
DEMO
Co dalej?
Offline mode
Generowanie PDF-ów
let win = new BrowserWindow({width: 800, height: 600})
win.loadURL('file://file-to-load.html')
win.webContents.on('did-finish-load', () => {
win.webContents.printToPDF({}, (error, data) => {
fs.writeFile('/tmp/print.pdf', data, (error) => {
console.log('Write PDF successfully.')
});
});
});
Bezpieczne przechowywanie tokenów
node-keytar
Źródło: http://atom.github.io/node-keytar/
Natywny moduł do przechowywania haseł 

z użyciem Keychain (macOS), 

Credential Vault (Windows) 

i libsecret (Linux)
Do przejrzenia
• https://nodesource.com/blog/fifteen-essential-packages-to-get-started-with-electron/
• https://openfin.co/2016/11/04/openfin-slashes-electron-memory-78/
• http://blog.atom.io/2017/04/18/improving-startup-time.html
• https://openfin.co/2016/02/17/openfin-addressing-security-and-performance-challenges-with-
electron/
• https://github.com/pojala/electrino
• https://github.com/MacGapProject/MacGap1
Plugin: https://github.com/dziudek/WordCampPolska-WP-Notes-Plugin
Aplikacja: https://github.com/dziudek/WordCampPolska-WP-Notes-App
Artykuły warte uwagi:
Pytania?
tomasz@dziuda.com
@dziudek
http://dziudek.pl
http://www.slideshare.net/dziudek
Tomasz Dziuda

Electron + WordPress = ❤