Как то раз мне захотелось сделать "Contact us" виджет и возникла дилемма, как задать настройки кнопки?
Хотелось чтобы:
Но также хотелось бы выложить код на github без использования серверной части... Я задал вопрос наToster QNA Habr
<head>
...
<script defer src="https://mysite.github.io/script.js?color=fff&text=helloWorld"></script>
</head>
let selfElement = document.currentScript;
if (selfElement?.src) {
let urlR = selfElement.src,
url = new URL(urlR),
params = url.searchParams
if (params.has("color")) {
mycustomelement.setColor(params.get("color"))
}
if (params.has("text")) {
mycustomelement.setText(params.get("text"))
}
...
}
Это успех осталось только написать логику дальше... НО меня всегда интересовала одна штука. А точнее тег <script> если указать параметр src то эго содержание не будет выполняться. Но поскольку мы уже смогли получить свой родительский элемент мы можем исправить это недоразумение
eval(selfElement.innerText);
<head>
...
<script defer src="https://mysite.github.io/script.js">
{
"color":"#fff",
"text":"Hello world!"
}
</script>
</head>
И теперь всё очень просто
let config = JSON.parse(selfElement.innerText);
Всё бы было хорошо если бы VScode не ругался на JSON между тегов script В принципе это не столь критично, но мне не хотелось лесть в настройки чтобы оно игнорировалось. Было решено как-то сделать так, чтобы IDE думало что это javascript
Можно бы было добавить кавычки и регуляркой парсить значения внутри. Но это ужасный вариант и я решил сделать так
<head>
...
<script defer src="https://mysite.github.io/script.js">
return {
color: "#fff",
text: "Hello JS world!",
}
</script>
</head>
Да использовать js внутри тега script кто бы мог подумать) Решил не использовать eval поскольку это не очень безопасно
let config = new Function(selfElement.innerText)();
Супер мы в шоколаде?! Не совсем( поскольку теперь помимо нашего return можно выполнить любой js код, это никуда не годиться... Что делать? В голову сразу же приходит SandBox. Но как изолировано запустить js код в js? Желания тянуть какую-то библиотеку нет, надо найти какое-то элегантное решение в несколько строк. После полчаса поисков нашёлся один gist
function construct(constructor, args) {
function F() {
return constructor.apply(this, args);
}
F.prototype = constructor.prototype;
return new F();
}
// Sanboxer
function sandboxcode(string, inject) {
"use strict";
var globals = ["Function"];
for (var i in window) {
// <--REMOVE THIS CONDITION
if (i != "console")
// REMOVE THIS CONDITION -->
globals.push(i);
}
// The strict mode prevents access to the global object through an anonymous function (function(){return this;}()));
globals.push('"use strict";\n'+string);
return construct(Function, globals).apply(inject ? inject : {});
}
sandboxcode('console.log( this, window, top , self, parent, this["jQuery"], (function(){return this;}()));');
// => Object {} undefined undefined undefined undefined undefined undefined
sandboxcode('return this;', {window:"sanboxed code"});
// => Object {window: "sanboxed code"}
Мне не сразу стало понятно как оно работает, но сейчас поясню. Мы видим 2 функции construct и sandboxcode Первая на вход принимает какую-то функцию и массив аргументов, создает новою функцию F которая принимает все аргументы с массива и переопределяет прототип, на выходе получаем анонимную функцию с кучей аргументов
function anonymous(top,window,location,external,chrome,document,...) {
"use strict";
return this;
}
В этом и заключается вся магия мы просто переопределяем все существующие глобальные объекты и переменные.
Но код прийдётся немножко подредактировать потому что мы все же сможем получить доступ во вне используя eval(); или globalThis
//https://gist.github.com/gornostay25/3ea24d743c90b2cd6b2aaadb9241fec9
function sandboxcode(s) {
function construct(c, a) {
function F(){return c.apply(this, a)}
F.prototype = c.prototype;
return new F()
}
let g = ["Function","globalThis","eval"]
for (let i in globalThis){g.push(i)}
g.push(s);
return construct(Function, g).apply({});
}
Я убрал передачу своих глобальных переменных поскольку это мне не было нужно, также немного укоротил код. В массиве g находятся все объекты которые нужно перезаписать:
//function F(){return c.apply(this, a)}
Function.apply({},["test","ttest","ttest2","alert(123);"])
/*
ƒ anonymous(test,ttest,ttest2
) {
alert(123);
}
*/
Теперь момент истины:
<head>
...
<script defer src="https://mysite.github.io/script.js">
alert("bad code"); //Uncaught TypeError: alert is not a function
console.log(window,this,globalThis,Function,eval);
// => undefined {} undefined undefined undefined
return {
color: "#fff",
text: "Hello JS world!",
}
</script>
</head>
let config = JSON.parse(JSON.stringify(sandboxcode(selfElement.innerText)));
// => {color: "#fff", text: "Hello JS world!"}
Чтобы каким то образом не передалась функция или гетер класса делаем фильтр через JSON И всё мы справились!!!
Надеюсь это кому-то пригодится. Мне будет приятно гуляя по github увидеть что кто-то сделал свою библиотеку или виджет по этому принципу. Всем всего хорошего!
Хотелось чтобы:
- Всё было понятно для не(
до)программистов - Легко было написать генератор
- Всё работало сразу же
Иметься ввиду что нужно только подключить скрипт. Без создания экземпляра класса и вызова где-то там в коде. Мне разу же пришла в голову идея передавать параметры в GET параметрах URL.Всё работало сразу же
Но также хотелось бы выложить код на github без использования серверной части... Я задал вопрос на
Но после 5 минут поиска в интернете я нашел интересное свойство объекта document. document.currentScriptКак получить GET параметры ссылки по которой был загружен скрипт? Что я имею в виду например есть какой-то скрипт залитый на github httрs;//mуsitе.github.iо/script.js На сайте example.com мы его загружаем <script defer src="https://mysite.github.io/script.js?param1=1¶m2=2"></script> <!-- в ссылке передаются статические параметры --> Github был выбран просто для примера, что нет возможности на стороне сервера отобразить параметры в скрипте. Возможно ли из js узнать из какого элемента script он был загружен? Или каким то другим образом получить параметры из URL?
Получения параметров из адреса скрипта
Дальше дело за малым, получаем ссылку, парсим и радуемся что не пришлось писать backend логику<head>
...
<script defer src="https://mysite.github.io/script.js?color=fff&text=helloWorld"></script>
</head>
let selfElement = document.currentScript;
if (selfElement?.src) {
let urlR = selfElement.src,
url = new URL(urlR),
params = url.searchParams
if (params.has("color")) {
mycustomelement.setColor(params.get("color"))
}
if (params.has("text")) {
mycustomelement.setText(params.get("text"))
}
...
}
Это успех осталось только написать логику дальше... НО меня всегда интересовала одна штука. А точнее тег <script> если указать параметр src то эго содержание не будет выполняться. Но поскольку мы уже смогли получить свой родительский элемент мы можем исправить это недоразумение
eval(selfElement.innerText);
Новая эра конфигов
И тут меня осенило можно же использовать это пространство, чтобы задавать конфигурацию плагина<head>
...
<script defer src="https://mysite.github.io/script.js">
{
"color":"#fff",
"text":"Hello world!"
}
</script>
</head>
И теперь всё очень просто
let config = JSON.parse(selfElement.innerText);
Всё бы было хорошо если бы VScode не ругался на JSON между тегов script В принципе это не столь критично, но мне не хотелось лесть в настройки чтобы оно игнорировалось. Было решено как-то сделать так, чтобы IDE думало что это javascript
Можно бы было добавить кавычки и регуляркой парсить значения внутри. Но это ужасный вариант и я решил сделать так
<head>
...
<script defer src="https://mysite.github.io/script.js">
return {
color: "#fff",
text: "Hello JS world!",
}
</script>
</head>
Да использовать js внутри тега script кто бы мог подумать) Решил не использовать eval поскольку это не очень безопасно
let config = new Function(selfElement.innerText)();
Супер мы в шоколаде?! Не совсем( поскольку теперь помимо нашего return можно выполнить любой js код, это никуда не годиться... Что делать? В голову сразу же приходит SandBox. Но как изолировано запустить js код в js? Желания тянуть какую-то библиотеку нет, надо найти какое-то элегантное решение в несколько строк. После полчаса поисков нашёлся один gist
function construct(constructor, args) {
function F() {
return constructor.apply(this, args);
}
F.prototype = constructor.prototype;
return new F();
}
// Sanboxer
function sandboxcode(string, inject) {
"use strict";
var globals = ["Function"];
for (var i in window) {
// <--REMOVE THIS CONDITION
if (i != "console")
// REMOVE THIS CONDITION -->
globals.push(i);
}
// The strict mode prevents access to the global object through an anonymous function (function(){return this;}()));
globals.push('"use strict";\n'+string);
return construct(Function, globals).apply(inject ? inject : {});
}
sandboxcode('console.log( this, window, top , self, parent, this["jQuery"], (function(){return this;}()));');
// => Object {} undefined undefined undefined undefined undefined undefined
sandboxcode('return this;', {window:"sanboxed code"});
// => Object {window: "sanboxed code"}
Мне не сразу стало понятно как оно работает, но сейчас поясню. Мы видим 2 функции construct и sandboxcode Первая на вход принимает какую-то функцию и массив аргументов, создает новою функцию F которая принимает все аргументы с массива и переопределяет прототип, на выходе получаем анонимную функцию с кучей аргументов
function anonymous(top,window,location,external,chrome,document,...) {
"use strict";
return this;
}
В этом и заключается вся магия мы просто переопределяем все существующие глобальные объекты и переменные.
Но код прийдётся немножко подредактировать потому что мы все же сможем получить доступ во вне используя eval(); или globalThis
//https://gist.github.com/gornostay25/3ea24d743c90b2cd6b2aaadb9241fec9
function sandboxcode(s) {
function construct(c, a) {
function F(){return c.apply(this, a)}
F.prototype = c.prototype;
return new F()
}
let g = ["Function","globalThis","eval"]
for (let i in globalThis){g.push(i)}
g.push(s);
return construct(Function, g).apply({});
}
Я убрал передачу своих глобальных переменных поскольку это мне не было нужно, также немного укоротил код. В массиве g находятся все объекты которые нужно перезаписать:
//function F(){return c.apply(this, a)}
Function.apply({},["test","ttest","ttest2","alert(123);"])
/*
ƒ anonymous(test,ttest,ttest2
) {
alert(123);
}
*/
Теперь момент истины:
<head>
...
<script defer src="https://mysite.github.io/script.js">
alert("bad code"); //Uncaught TypeError: alert is not a function
console.log(window,this,globalThis,Function,eval);
// => undefined {} undefined undefined undefined
return {
color: "#fff",
text: "Hello JS world!",
}
</script>
</head>
let config = JSON.parse(JSON.stringify(sandboxcode(selfElement.innerText)));
// => {color: "#fff", text: "Hello JS world!"}
Чтобы каким то образом не передалась функция или гетер класса делаем фильтр через JSON И всё мы справились!!!
Надеюсь это кому-то пригодится. Мне будет приятно гуляя по github увидеть что кто-то сделал свою библиотеку или виджет по этому принципу. Всем всего хорошего!
Интересный способ сделать config для web js библиотеки
Как то раз мне захотелось сделать "Contact us" виджет и возникла дилемма, как задать настройки кнопки? Хотелось чтобы: Всё было понятно для не( до )программистов Легко было написать генератор Всё...
habr.com