Ruby
JavaScript
Opal
PWA

極小WebアプリをURLに埋め込んで超ポータブルなWebアプリを作りたい!

やりたいこと

データを変形・加工するスクリプトをURL上にホストをして、
ポータブルで永続的でブラウザで完結するWebアプリを作れるようにします!

Rubyはデータ加工に最適!

Rubyは豊富なメソッドが標準で用意されていて、特にArray, Enumerator, Stringなどよくデータを加工したいときに使えるメソッドがとても豊富です。
メソッドチェーンでつなげていけるところが、人の思考にあっていて、どんどんデータを変えていけるところがRubyの良さだと思います。
そのためデフォルトの言語はRubyです。JavaScriptもオプションで選べるようになってます。

そこで、 データ加工に便利なRubyで書いた小さいWebアプリをURL上にホストします!
URLにすべてのコードを載せてしまえば究極的にポータブルなWebアプリになるのではないかという発想です。Itty.bittyにインスパイアされてます!
ブラウザ上でRubyを動かす技術に関しては後述します。

なにを解決したいか

サーバーサイドで実行するわけではなくクライアントサイドで完結かつURLにコードをホストさせるため、
以下のような心配ごとが解消するはずです。

  • サービス終了でコードがなくなることがない
    • 大体のオンライン実行環境で与えられるIDのようなものではなくコード自体がURLに残る
    • すべてのコードがURLに刻まれるため、コードが失われる心配はないです
  • コードがサービスに漏れない1
    • 大体のオンライン実行環境はコードをサーバに転送して実行する
    • コードの実行環境がサーバでないため、コードを送信する必要がない
    • URLの#以降にコードを埋め込むため、Webサーバのログにも残らないはず
  • 完璧に静的にホストできる
    • GitHub Pagesで動く
    • AWS S3でも動く
    • コスト0 - 今の世の中だと無償で提供し続けられる

Webアプリにすることで、pry(=irbの便利版)のときに少し不便に感じてた以下のようなことが解決できると思います。

  • 複数行をヒアドキュメント<<EOSで貼り付ける手間
  • 文字列を変更したくなったらもう一度貼り付け直す手間
  • 加工したい文字列に対しての結果が、リアルタイムに反映される

アプリケーション

アプリケーション(GitHub Pages): https://nipp.cf
リポジトリ(GitHub): https://github.com/nwtgck/nipp
nipp.cfnwtgck.github.io/nipp のカスタムドメインです)

使い方や例などは後述します。

Nippの名前の由来は 「Mi ni A pp」からです。
小さめのWebアプリをホストするところから来ました。ですが、Rubyの枠を超えてJavaScriptを動かせるので、結構いろいろできます(後述します)。

使い方

の欄にRubyのスクリプトを入力して、
の欄に文字列を入力して、
の欄にRubyが実行後の出力が出る

に入力した文字列はsっていう変数に入ってます。sはStringのsのつもりです。

入力したコードは圧縮されURLに載ります。デフォルトだとdeflateのレベル9で圧縮され、リアルタイムにURLが変わっていきます。
URLがリアルタイムに変わるおかげで、間違ってページをリロードしても書いたコードは保持されます。
出来上がったところで、ブックマークしたりツイートしたりして、共有できます。

Vueとかのライブラリも動く!

(追記:ES2017が使えることを上の方にも追記しました)

言語はRuby以外にJavaScript(ES2017)が使えます。
<script src="...">動的に追加できるので、実はCDN経由などで、他のライブラリも使えます!
以下は、Vue製のシンプルなTodoリストです。

vue-todo.gif

Nipp: https://nipp.cf/#Vue_Todo_List/func_es20...

一瞬だけNippのページが見えるのが、Nippで動いている証拠です。詳しくは後述します。

使用例

実際に作ってみて、使ったものの中で他の人も使えそうなものを選びました。
NippのURLを開いて、入力例を打ち込んで色々試したりコードを改良したりしてみてください。

複数行文字列 ==> "....\n" +  <改行> "...\n" + <改行>...

ヒアドキュメントがない言語のときに便利かもしれません。

manual-heredoc.gif

入力
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed 
do eiusmod tempor incididunt ut labore et dolore magna 
aliqua. Ut enim ad minim veniam, quis nostrud exercitation 
ullamco laboris nisi ut aliquip ex ea commodo consequat. 
Duis aute irure dolor in reprehenderit in voluptate velit esse 
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat 
cupidatat non proident, sunt in culpa qui officia deserunt 
mollit anim id est laborum.

==>

出力
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed \n" +
"do eiusmod tempor incididunt ut labore et dolore magna \n" +
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation \n" +
"ullamco laboris nisi ut aliquip ex ea commodo consequat. \n" +
"Duis aute irure dolor in reprehenderit in voluptate velit esse \n" +
"cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat \n" +
"cupidatat non proident, sunt in culpa qui officia deserunt \n" +
"mollit anim id est laborum."

Nipp: https://nipp.cf/#Manual_Heredoc//K9ZLTUzOiM/JzEvVy00sqK7JqVHI0cvMKy5ITS6p1cvKz8zTUFLQjslT0gQA

Markdownの表からCSV

CSV => Markdown は調べるとよく見つかるのですが、
Markdown => CSV はあんまりないので、
Markdown => CSVの変換器です。2

Markdownの表だと、編集しにくいなって思ったとき使えるかもしれません。

markdown-to-csv.gif

入力
| Item   | Count |
|--------|------:|
| apple  |    12 |
| orange |    23 |
| tomato |     3 |

==>

出力
Item,Count
apple,12
orange,54
tomato,3

Nipp: https://nipp.cf/#Markdown_to_CSV//LYxBCoAgEEX3nSJmERo10barNBIhgoaZpLumu2fU7sP77yU0q7aLd8HgvkbRTNoee5R4ms3ofLHn2heStRUD8czUT5RUSzzI+zX+R4reZQEMch4R+1H9tZRPV2rb4YKADorzTQogqwc=

とりあえず、小数第2位にしたいとき

文字列中に現れる長い小数を、とりあえず全部小数第2位にしたいときとかに使えます3

shorten-floating-number.gif

入力
\begin{tabular}{llr}
    Animal    & Price    \\
    \hline
    Gnat      & 13.65434 \\
              & 0.018492 \\
    Gnu       & 92.44325 \\
    Emu       & 33.23443 \\
    Armadillo & 8.919998 \\
\end{tabular}

==>

出力
\begin{tabular}{llr}
    Animal    & Price    \\
    \hline
    Gnat      & 13.65 \\
              & 0.02 \\
    Gnu       & 92.44 \\
    Emu       & 33.23 \\
    Armadillo & 8.92 \\
\end{tabular}

Nipp: https://nipp.cf/#Shorten_Floating_Number//K9ZLLy5N0tDXiEnRjtEDEpr6mtU1qTUKqXol+fFpekX5pXkpGkaatVwA

例の入力はLaTeXの表みたいなものですが、小数を含むどんな文字でも切り捨てをします。

長い桁の数を数えてほしいとき

無量大数まで数を日本語で読める形に変えてくれます。

kazoekata.gif

Nipp: https://nipp.cf/?#数の数え方//y1awVVAN14hRUHiyo13haUuLwtPWNoUnu9YoPJ03WeHD0rkbFZ6vXKzwbNdchWf7pis8W7tY4cWeToVnS7cqPGuc9GzT5mebZiq8nLH/afPy50vbFV42LX4+ZeOT3dOAxvU+7V//rGHui7UbFJ63LHzZ3v90yfJnUzdochXrJWckFhXrFaWWpRYVp+qlJiZnxBfnZCanapho6uUmFmioWUHlNPWqMgs0sjXhaoGy1TUaiTpJmjUKCnlAxyfqZeVn5umV5MdnWoMEbBUMFOwV1NUVrBSUlKvzapWrk2qVasGKAA==

numpyの行列をコピペしやすくする

numpyの行列の出力が以下ようになって、このままだとコピペしてもPythonのコードにならなくて不便だったりします4
pastable-numpy-matrix.gif

入力
[[1 2 3]
 [4 5 6]
 [7 8 9]]

==>

それを以下のように変えてコピペが捗るようになります。

出力
np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

Nipp: https://nipp.cf/#Pasteable_numpy_matrix//JYtRCoAgEAWvIhukC7IH6CoiIWFkbCrqT5R3r+jzzcyDmMmV4k5lhqtSK0oaK7WQEsm7ZZs5RE+Hy9fNt4A3YqqZQ/uYGqeW5hVpTyGq74XdQv8naAFIr6/dIjw=

単語数を数えたいとき

他にも単語ごとに数を数えたいときとか、

word-frequency.gif

Nipp: https://nipp.cf/#Word_Frequency//JcrBCoMwDADQXwkeZsNqvPsrU6TWIgprxTjCNP13BW/v8JjYu2jqVt410rSl39oPf/NqxiTROw5IX7eeasQKo8JHLAgTz0foMnHa9ruf2luvUPn85KAQaElzNGUDJebHRRsLvAA=

pry入力をコピペしやすくしたり

これもまた、pryでインタラクティブに打った内容をちゃんとコピペしても使えるようにしたり、
(これはpry機能でありそうな気もしますが)

pry-to-ruby.gif

入力
[14] pry(10):1> a = 10
=> 10
[15] pry(10):1> b = a * 3
=> 30
[16] pry(10):1> class MyClass
[16] pry(10):1*   def my_method(n)
[16] pry(10):1*     n * 8
[16] pry(10):1*   end  
[16] pry(10):1* end  
=> :my_method

==>

出力
 a = 10
 b = a * 3
 class MyClass
   def my_method(n)
     n * 8
   end  
 end  

Nipp: https://nipp.cf/#pry_to_Ruby//K9ZLTUzOiM/JzEvVK0rNSk0uqa7JqVHI0SsuSSwqiS/PLMmw11CytVPSrNXLTSyASqYXlyZpqBZVx0THpGjHxOpp20fbacXW6iioqwPVZeVn5nEBAA==

Pythonのもコピペしやすくしたり

(標準でなんかしらの機能でできそうな気もするのですが...)

pastable-python.gif

入力
>>> a = 10
>>> b = a * 3
>>> class MyClass:
...   def my_method(self, n):
...     return n * 8
... 

==>

出力
a = 10
b = a * 3
class MyClass:
  def my_method(self, n):
    return n * 8

Nipp: https://nipp.cf/#Python%E3%82%B3%E3%83%94%E3%83%9A%E3%81%97%E3%82%84%E3%81%99%E3%81%8F//K9ZLTUzOiM/JzEvVK07NSU0uqa7JqVHI0ctNLEnO0NC3s7OridEDQX3NWqBgAVQ6vbg0SUNfAyGtqWCvr6Ogrg5UlZWfmccFAA==

Markdownで引用しやすくしたいとき

先頭に>をつけるだけです。

citable.gif

Nipp: https://nipp.cf/#引用しやすくする//K9ZLTUzOiM/JzEvVy00sqK5JrVFQslNQUtBWSK3Vy8rPzAMA

厳密にはOpal

便宜的にRubyといっていますが、ブラウザ上でRubyをJavaScriptにトランスパイルするためにOpalを使っています。

Opal is a Ruby to JavaScript source-to-source compiler.
It comes packed with the Ruby corelib you know and love.
It is both fast as a runtime and small in its footprint.

(from: Opal)

JavaScript(ES2017)も動く!

NippはRuby以外にJavaScript(ES2017)も動かせます!(Babelでトランスパイルしてます)

以下みたいにして、JSでも文字列長さをはかるNippも作れるのですが...
https://nipp.cf/#文字列の長さ/es2017/K9bLSc1LL8kAAA==

JSが使えるになると、こういった用途よりももっと他のことがしたくなってきます...(最後の方に少し紹介します)

プログレッシブウェブアプリ(PWA)対応

ポータビリティがとても大事なため、プログレッシブウェブアプリ(PWA)対応しました。

そのため一度読み込まれるとページがキャッシュされるため、ネット環境がないところでも動作したり、Androidとかだとアプリぽくインストールすることができます。ネットから遮断されたローカル環境でもRubyが動くようにNippが動きます!

ネット環境がないフライトモードでも動作したり、
nipp-pwa-offline.gif

アプリみたいにインストールすることもできます。
nipp-pwa-as-app.gif

使用されている技術

Opal

OpalはNippの要のようなプロジェクトです。
リポジトリ:https://github.com/opal/opal

JavaScript上で、Ruby(Opal)コードをトランスパイルするために、opal.jsopal-parser.jsを使います。
JS上で動かす方法は公式ドキュメントのCompiling Ruby code from HTMLに動作するコードが書かれています。このドキュメントにはインラインに<script type="text/ruby">としてJSのようにRubyコードを埋め込む例が紹介されていますが、Nippではユーザー入力の文字列をトランスパイルしたいので、Opal.compile(Rubyのスクリプトの文字列)でトランスパイルしています。

Opalはあまりnpmから使うのが向いてなく、gemでインストールするようになっている感じがあります(npmの更新が止まっています)。Bowerの更新は生きていたのでNippではbower経由でOpalを管理しています(その他のライブラリはnpmで管理しています)。実際にはNippを管理するpackage.jsondevDependenciesbowerを入れて、npm run buildとかとすると、bower installなどをやるようにして、npmグローバルにインストールしていれば、Nippをビルドできるようにしました。

@babel/standalone

BabelはWebアプリをビルドするマシンだけで使うのが通常の利用だと思います。しかしNippはトランスパイルをブラウザ上で行います。そのため、普通のBabelではなく@babel/standaloneを利用しています。

リポジトリ:https://github.com/babel/babel/tree/master/packages/babel-standalone
babel/babel-standaloneというプロジェクトがググるとトップに出てきますが、上に書いたリポジトリがメンテナンスされているものなのでこちらを使います)

以下のように実行すると、トランスパイルされたJSが文字列として返ってきます。実行するにはこれをeval()をするか(new Function(トランスパイル文字列))()をします。

Babel.transform(ES2017で書かれたJSのスクリプト文字列, {presets: ["es2017"]}).code;

リアルタイムな実行とトランスパイル

入力欄の文字列をもとに、出力欄に反映するとき話です。
コードの入力欄に変更があるときだけに、トランスパイルします。そしてRuby(Opal)の場合は、トランスパイルされたFunctionオブジェクトを保持します。
入力欄が変更されたときは、そのFunctionオブジェクトを実行します。入力欄によって出力が変わるのは、swindow.INPUTを参照するにしているからです。
window.INPUTは入力に変化があった場合に再代入されて、そのあとにFunctionオブジェクトが実行されます。
入力ごとにトランスパイルされることはないため、そこそこ高速に動き、モバイル端末でもリアルタイムな実行ができています。

deflate/LZMA (圧縮技術)

Nippでは、deflateもしくは、LZMAを使ってコードを圧縮します。Itty.bittyでもLZMAを使って圧縮しています。

自分の理解では、deflateはアルゴリズムの名前で、zipやgzipで実際に使われているアルゴリズムです。LZMAは圧縮率が高くて有名(多分)な.xzなどで使われているアルゴリズムです。2つ用意しているのは、場合によってdeflateの方が短くできたり、LZMA方が短くできたりがあるからです。

経験的には、元々短いコードはdeflateの方が短いが、長いコードはLZMAのほうが短くできるという印象です。NippのURLを最適な短さにするときは、各アルゴリズムを各圧縮レベルで試します(低いレベルの方が短くなるケースがあることもありました)。以下は、この最適化もNipp上で作れます。以下がそのNippへのリンクです。
URL最適化: https://nipp.cf/#⚡Optimize_URL/es2017/dU...
使い方は、Input欄にNippのURLを入れるとOutputに最適なURLを吐き出します。

deflateのヘッダとチェックサム

なるべくURLを短くしたいので、deflateはヘッダとチェックサムを取り除いています。この取り除く機能は標準にあり、windowBitsに負の数を入れると取り除けます。@7shi さんの ShideShare Deflate で知りました。このスライドではDeflateに関して詳しくまとまっていて勉強になります。

JavaScript/CSS ライブラリ

JavaScriptのフレームワークは少し古めの技術ですが、AngularJS(1.x系)を使ってます。いつもちょこっとしてWebアプリを作るときは、AngularJSを使うのが慣れているので使ってます。
もう少ししっかりと開発したいときは、React/TypeScriptを使っていますが、AngularJSからReactよりも、Vueへの移行の方がしやすそうなので、Vueになるかもしれません。

CSSはモバイル端末で見やすくするために、レスポンシブデザインにしたかったので、Pure.cssを使ってます。レスポンシブデザインさえできたら良かったのミニマムなものを選びました。またCSSのクラス名にプレフィックスがあるおかげで名前が汚染されない感じが気に入って選んでます。

NippのURLの構造

以下がURLの構造です。

nipp.cf/#ページタイトル/オプションたち/圧縮されたBase64 encodeされたコード

ページタイトルに半角スペースは_に置換されます。Itty.bittyでも置換されています。
オプションたちはオプションをカンマ区切りで並べます。

Nippのオプション

現在利用可能なオプションは以下の4つです。

  • es2017: ES2017を使う(デフォルト:Ruby(Opal))
  • func_es2017: (new Function(...))()を使って、ES2017を実行する(デフォルト:eval()で実行) (速度が向上します)
  • lzma: LZMAを使ってコードを圧縮します(デフォルト:defalte)
  • click_run: ボタンをクリックして実行するようにします(デフォルト:リアルタイム実行)

オプションをクエリパラメタみたいにkey=valueにしなかったのは、すこしでもURLを短くするためです。
オプションの区切りが-ではなく_なのは、マウスでダブルクリックして選択するときにオプションを選択しやくしたかったからです。

DOMをいじる!

Nippでテトリス

JSが動かせるので、DOMなども普通にいじれます。そこでだいぶ前に話題になったわずか565バイトテトリスのプログラミング解説をNipp上で動くようにしてみました!

tetris.gif

Nipp: https://nipp.cf/#Tetris/es2017/VY9RjtowFEW3ApYY2dimBAIzYB4o0EqV0pGQmA8g8gckjmIIcZQxU6EJ0uygi+jSqu6jjuarX75PPu/de98OVctAYuLrRRW2F1fqYNW3XDUTRiUiIr0WsdWmaO0weU9NhVcQJRDy4ZNkMfSZBl9ozruhCLk3XMQU2vsooyVN5DTmnwNeRVpC2Q2454+6zweb9SpzLRJcfgkIkaSEpQNEc71t65j6izaOuU8eHnBGISHTJaxYCiFo6As980VKYR8FtJRkG5WQUbdOqZTgCZ1iBW1F3p1I6+Vn5BwGg6cmpSBNnMZMwtnRzecSoog/Mj7oszHznJiwIRvLyAKltvMouc/6zGOWjxc8mHJPitwdymBEnEXeCUjOwT1d1zSXQuWvqtWcXVM4U8pOkFMIBOenedC4n6RLfuKBvLtmwb0hNWwAIddr4PfF1mVzhJa1s9edYDao9dylJ07XNd5QQLNjNUeEOdnAC/T31280RX8+PpAwPV0Uqvr+8vwDNnTN9tFI1vWrsi/6oszV4h2bTPia3BMTXy+qsL2jSW49BwTWVvp4tQojU4Tq9tX8LBBDIai3Bjur28okytn+v3koS1Ukq0znCTaEbSHaQ7SEALyBlCwDBSFYWEOf7TAR/wA=

(オリジナルのコードのとの変更点は、直接document.bodyを書き換えていたのを、一つ要素をJSで作ってその要素を書き換えるように変更しただけです)

UIを完全に置き換える!

document.body.innerHTMLなどをいじれば、Nippの本来のUIを置き換えることができます。
自分でもUI置き換えをしたい人向けのTipsは後述します。

NippでVue

VueをCDNから使います。
Vue製のTodoリストがNipp上で動きます。JSFiddleにあったソースコードを元にしました。

vue-todo.gif

Nipp: https://nipp.cf/#Vue_Todo_List/func_es20...

JSが使えれば、動的にscriptタグを生成して好きなライブラリをCDN経由で持ってくれます。
好きにjQueryでもVueでも他のJSライブラリでも使えてしまいます!

Nippでサマー・ウォーズ

サマー・ウォーズのワールドクロックです!
動的にd3.jsなどのライブラリを読み込んだりしています。

summer-wars-world-clock.gif

Nipp: https://nipp.cf/#World_Clock/es2017,lzma...

SHIMIZUさんのブログから元になっています: https://shimz.me/blog/d3-js/4360
ありがとうございます!
SHIMIZUさんブログは他にもいろんな技術を使っていて面白いのでおすすめです。
GUNMA GIS GEEK – 地図と視覚化についての技術情報

Nippで2048で遊ぶ

https://github.com/gabrielecirulli/2048 のコードをもとに、Lebab(Babelの逆)したりuglifyしてコードを小さくしたものをホストしました。
この2048は標準ライブラリだけで書かれているので、完璧にポータブルです! PWAでローカルにキャッシュされていれば、オフラインでも遊べます。

2048.gif

Nipp: https://nipp.cf/#2048/func_es2017,lzma/XQAAA...

UI置き換えのTips

UI置き換えをしたい人向けのTipsです。
基本形は以下のようになります。<head><body>内を好きに変更できて、描画後に好きにJavaScriptを動かせました。
(なるべく一般的に使える形を模索した結果です)


追記:

以下のようにdocument.writeを使ったほうがよりシンプルになりそうです。

document.write(`
<html>
...
<html>
`)

` が文字列内に混じっていいてヒアドキュメントが作りづらい場合は、
文字列エスケープ: https://nipp.cf/#Escaping_String//K9bLzCsuSE0uAQA=
を使って、文字列をエスケープすると楽かもしれません。


document.head.innerHTML = `
... <head>内の内容 ...
`;

document.body.innerHTML = `
... <body>内の内容 ...
`;

const a = document.createElement("a");
a.onclick = ()=>{
  ... 読み込まれたときに実行したいJavaScript ...
}
a.click();

この方法のいいところは、

  • onclick = ()=>{}でES2017が使えるところと、
  • 全体をminifyしやすい
    • JavaScriptを""内に書くとminifyされないため

 UI置き換えの具体例

<head>にCSSを書いたり、好きにHTMLのbodyを作って、JavaScriptで置き換え後のNippのDOMを動的にいじってます。
Nipp: https://nipp.cf/#Dynamic_Replacement/es2...

// <head>内を置き換える
document.head.innerHTML = `
<title>Hello World</title>
<style>
h1 {
  color: #ff8000;
}
</style>
`;

// <body>内を置き換える
document.body.innerHTML = `
<h1>hello, world</h1>
<div id="time"></div>
`;


const a = document.createElement("a");
a.onclick = ()=>{
  // 書き換えたい要素を取得
  const timeElem = document.getElementById("time");
  // 毎500msで繰り返す
  (function loop(){
    // 現在時刻で要素を書き換える
    timeElem.innerText = new Date();
    setTimeout(loop, 500);
  })();
}
// スクリプトを実行
a.click();

ライブラリを使いたいとき

以下のような動的に<script src="...">タグを生成する関数を作っていおいて、

function js(url, onload) {
    const s = document.createElement("script");
    s.src = url;
    s.onload = onload;
    document.head.appendChild(s);
}

以下ように、CDN経由でライブラリを読み込んで使うことができます。

js("https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js", () => {
    js("https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.3/moment-with-locales.min.js", () => {
      // 全ライブラリが読み終わった
      // ... DOMをいじったり、好きなJavaScriptを書く ...

この場合はjs関数がのコールバックがあるため、a.onclick = ()=>{}を使う必要がなくなります。
UI置き換えのTipはこんな感じです。コールバックが深くなるので、適宜Promiseなどを使って平らにするといいかもしれません。

ブラウザのURLの文字制限

Chrome/Safari/Firefoxあたりをお使いなら、長さ1万のURLぐらいなら対応しているということがわかりました。
詳しくは、別記事にまとめました。
各種OS/ブラウザでのURLの長さの上限を調べてみました - Qiita

ツイートのURLの文字制限

長さ4088のURLならツイートできることが分かりました。すこしブラウザ版とモバイル版で差があります。
こちらも詳しくは、別記事にまとめました。
ツイートで使えるURLの長さの制限 - ブラウザ版、iOS版、Android版で違いがありました - Qiita

4088文字以下なら大丈夫です。

セキュリティに関して思うこと

結論としては、信用のできない人のNipp URLは開かないほうがいいと思ってます。
UIを置き換えで分かる通り、NippのURLが違えば、別のサイトと同じ力があります。
よく分からないメールにあるリンクを踏んだらまずいのと同じように、どんなサイトが待ち構えているか分からないため避けるのが無難だと思っています。
普通のサイトと違うのは、違うサイトは別のドメインなのが普通ですがNippだと同じドメインになるため、CookieやLocalStorageにアクセスできます。
そのため、漏れるべきではない情報をCookieなどに保存するNippを作るのは、避けたほうがいいと思っています。
あと、PWAを使った攻撃方法がある場合、XSSがキャッシュがクリアされるまで残るので、とても恐ろしいです。
PWAのService Workerを任意のものする場合同じオリジンのscriptしか登録できないため、基本的には問題ないと思っています。ただ、セキュリティ関連の攻撃な自分の知らない技術を使ったり、トリッキーな発想がよくあるので正直わかりません。PWAに関する攻撃手法は、Masato Kinugawaさんの攻撃者視点で見る Service Worker / PWA Study SW - Speaker Deckを参考にしました。

上記のようなことを考えた結果、「信用のできない人のNipp URLは開かないほうがいい」という結論になりました。

以下は、セキュリティ対策に多少は貢献するかもしれないNippです。
コードを実行せずに取り出す:https://nipp.cf/#Code_Extractor/es2017/r...
PWAのキャッシュクリア:https://nipp.cf/#PWA_Cache_Clear/es2017/...

Nipp URLを開いたあとにJSを実行すれば、Nippの入力欄やNipp URLは書き換えができるので、
URLを開く前にコードを取り出して、コードを確認する用途に作りました。
PWAキャッシュクリアは、PWAのService Workerが汚染されたときにクリアするためのNippです。ブラウザの開発者ツールでもできます。

Nippで確認するのも怖い場合は、以下のRubyスクリプトでローカルでコードを確認するこができます。

require 'zlib'
puts Zlib::Inflate.new(-8).inflate("K9YrzqxKBQA=".unpack('m').first)
=> "s.size"

Nippのサイトを自分で立ててすこし安全に

あと、自分用にNippのサイト自体を別に作るのも手だと思います。ドメインなどが変われば、Cookieが取り出せる話などが関係なくなると思います。NippはHTML/JS/CSSをファイルを置くだけでいいので、
手軽な方法は

  1. https://github.com/nwtgck/nipp をforkする
  2. fork後にgh-pagesブランチからCNAMEファイルを削除5

です。

絶対に起こるはずのないこと

絶対に起こるはずのないことは、

  • Nippのアカウントが乗っ取られる
  • NippのサーバがSQLインジェクションされる

です。

Nippにアカウントという機能自体がないので、ありえません。
Nippのサーバは静的にHTML/JS/CSSのファイルを置いているだけで、SQLとかデータベースとか使っていないので、SQLインジェクションはありえません。
任意のコードが実行できるからといって、Nippのセッションなどが盗まれるとかということはありません。
Nippはアカウントやログインやセッションなどの機能がそもそもないので、起こりえません。

便利さと危険を知ってこそだと思っているので、ネガティブなことですがセキュリティについて思うことを書きました。

[おまけ] Tips

Ruby(Opal)内でJavaScriptを動かしたい

Opalだと、` `%x{ }の中にJavaScriptを書くことができます

%x{document.title = s} なども有効なOpalになります。

JavaScriptのオブジェクトをRuby(Opal)で扱う

Ruby(Opal)らしくJavaScriptを使いたいときの方法です。

以下のように、OpalでNativeを使うことで可能になります。

require 'native'

# Googleを開く
win = Native(`window`)
win.open("https://google.com")

# setIntervalを使ってみる
interval = Native(`window.setInterval`)
interval.call(->{
    puts("Current time: #{Time.now}")
}, 1000)

Nipp: https://nipp.cf/#Native_Usage/click_run/...

console.logの値を見たい

RubyのputsやJavaScriptのconsole.logは開発者ツールを開いて、ログを見ることで確認できます。
しかし、モバイル端末上で開発したときにも確認したくなったら、以下のNippが便利かもしれません。

console.logを上書きして、DOM上のTextareaにもconsole.logの内容が追記されるようにします。
もともとのconsole.logwindow.consoleLogに退避されていて、上書き後のconsole.logも元々のconsole.logを呼ぶようになっているため、
開発者ツールでのログが確認できます。

  if (typeof window.consoleLog === 'undefined') {
    // Backup real console.log
    window.consoleLog = console.log;
    // Create log element
    var logElem = document.createElement('textarea');
    logElem.rows = "10";
    logElem.cols = "50";
    logElem.placeholder = "console.log";
    // Create clear button
    var clearButton = document.createElement("button");
    clearButton.innerText = "Clear log";
    clearButton.onclick = function(){
     logElem.value = "";
    };
    // Append the textarea and the button
    document.body.appendChild(logElem);
    document.body.appendChild(clearButton);

    // Replace console.log
    console.log = function(){
     // Output the arguments to the textarea
     logElem.value += Array.prototype.slice.call(arguments).join(" ") + "\n";
     // Pass the arguments to real console.log
     window.consoleLog.apply(null, arguments);
    }
  }

(開発中はプラグインのようにコードの先頭に書く感じを想定してます)

Nipp: https://nipp.cf/#Virtual_Console_Log//dU...

最後までありがとうございました!


  1. NippのサイトをホストしているWebサーバーに漏れないという意味です。もちろん悪意のある人がJavaScriptを駆使して、コード入力欄をどこか別のサーバーに送るようなプログラムを書くことはできます。そのため信用できない人のNipp URLを開いて使うことには慎重になったほうがいいと思います。 

  2. 正規表現があまいです 

  3. これも正規表現があまいです 

  4. 新しいnumpyだと問題だと、コピペしやすく出力されます。 

  5. CNAMEはGitHub Pagesでnipp.cfのカスタムドメインを使いたいので作ったファイルです。