Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Спортивная программа fitplotter (js) (https://javascript.ru/forum/project/81934-sportivnaya-programma-fitplotter-js.html)

karaul 17.02.2021 03:37

Спортивная программа fitplotter (js)
 
Прошу высказаться физкультурников-программистов

https://github.com/karaul/fitplotter

По ссылке выше текст на английском, а по ссылкам ниже по-русски в свободной форме
Мотивация и начало работы
http://maraforum-2.ru/viewtopic.php?f=6&t=3477
Развитие проекта
http://maraforum-2.ru/viewtopic.php?...=3301&start=20

Спасибо.

рони 17.02.2021 07:48

karaul,
:) по программе высказаться не могу, но дело хорошее.

karaul 17.02.2021 19:05

Похоже, что марафонцев среди js программистов столько же, сколько программистов среди бегунов. Мало.

Но вот Искандер Ядгаров - звезда Московского Марафона - он же программист.

Если никто по существу не выскажется, можно тешить себя тем, что среди марафонцев я самый знающий программист, а среди программистов - лучший марафонец.

karaul 17.02.2021 19:09

Цитата:

Сообщение от рони (Сообщение 533843)
karaul,
:) по программе высказаться не могу, но дело хорошее.

у меня в программе есть связанные списки. Поля списка оси y зависят от того выбора списка оси х. Правда, такие вещи я писал еще в 1993, на ДОСовсом FoxPro

karaul 24.02.2021 00:01

Вложений: 1
Никто не пишет, а хочется поговорить

(1) При входе на форум глюки при автоподстановке пароля из гугл-хромовского менеджера паролей. Пришлось запрашивать воостановление пароля. И так уже второй раз.

(2) Хочется получить совет по реализации программы. Делается для себя, вспомнить навык и выучить новое, чтобы другим было не стыдно показать и предложить для работы. Коммерческого интереса нет.

Подробно. В моем спортивном архиве данные о 10 лет занятий, 37500км, несколько тысяч файлов. Каждый файл - это трек пробежки с записью GPS координат, пульса, темпа бега, частота шагов (каденс) и прочее из спортивных часов. На рисунке во вложении пример того что в файлах

Я сделал форму для анализа данных записанных в файле, см. стартовое сообщение. Вот кстати, онлайн версия

https://karaul.github.io/fitplotter/

если интересно поиграть с онлайн-версией, FIT файл для анализа можно взять из https://github.com/karaul/fitplotter/tree/main/examples

файл проекта
https://github.com/karaul/fitplotter

Теперь мне хочется прикрутить к этому делу оболочку - таблицу из имен файлов и реперных данных из каждого файла (день, расстояние, средние и проч). Все файлы хранятся в одной директории на моей машине. Эта директория почти ежедневно пополняется. Мне хочется иметь таблицу вроде этой
https://www.runningahead.com/logs/4e...1eca8/workouts
Так чтобы щелкнув по строке таблицы я мог открыть из директории соответствующий файл и посмотреть график. Потом щелкнуть по другой строке и открыть на том же графике 2ой файл и сравнить. (Или на новом поле, но это неважно)

Непонятно как сделать таблицу. Прикручивать базу данных? Я сделал подобное на питоне, там у меня просто за каждый год csv файл, который регулярно дописывается, все работает. Теперь хочется на JS, но чтобы было не кустарно, как на питоне, а согласно имеющимся практикам, и практики заодно выучить. Предполагается что программа работать будет локально, если удастся потом сделать выход в внешний мир, как получилось с fitplotter, то совсем хорошо.

По моему разумению - это стандартная задача. Я нашел несколько описаний как это делается, например
https://closebrace.com/tutorials/201...ss-and-mongodb

Хочется знать - это правильное решение когда элементы хранения - файлы на диске? Мне кажется можно сделать проще.

Спасибо за обсуждение.

PS: В качестве благодарности могу надавать советов как правильно бегать.

рони 24.02.2021 08:00

karaul,
на всякий случай, пример создания таблицы с возможностью сортировки
https://javascript.ru/forum/misc/777...tml#post509074

voraa 24.02.2021 08:49

Цитата:

Сообщение от karaul
Предполагается что программа работать будет локально

Цитата:

Сообщение от karaul
По моему разумению - это стандартная задача. Я нашел несколько описаний как это делается, например
https://closebrace.com/tutorials/201...ss-and-mongodb

А это уже не локально. Это уже сервер, хоть и установленный на том же компьютере.
Ставить для этого ноду с монго - стрельба из пушки по воробьям. Ну если только хочется поучиться работать с этим.

Но с другой стороны без сервера обойтись трудно. Javascript не может на автомате читать файлы с локального компьютера без явного указания пользователем файла, через стандартное диалоговое окно Open File.
Только если попробовать поизвращаться - подключать эти файлы через <script>, и делать их как JSON, или просто комментарий и потом парсить текстовое содержимое.

karaul 24.02.2021 14:20

Спасибо за обсуждение

Изменил нечитаемый пароль выданную гуглем на более человеческое - гугл-хромовский менеджер начал работалть

рони спасибо за код, да именно это я и хочу. Надо просто добавить контроль для открытия карты и графиков. Как лучше: двойной щелчок по строке или отдельная колонка с кнопкой?

voraa спасибо за идею. Интересно, комментарий на тысячи строк. А есть пример такой реализации?

В качестве главной цели мне хочется чтобы программа прижилась в нашем спортивном клубе. Там не программисты, но про текстовый файл поймут. И возможность в т.ч. самому править файл заголовков (если что пошло не так), дописывая туда имя файла c свежей тренировкой выглядит просто и привлекательно. Человек понимает что делает и чувствует себя хозяином своей машины, программы на ней, и управляет своими данными. А необходимость шаманства с настройкой sqlite или mongodb и настройкой сервера людей оттолкнет. Можно сделать две версию, простую для людей, и правильную с БД (для себя - поучиться работать с БД).

рони 24.02.2021 15:35

Цитата:

Сообщение от karaul
или отдельная колонка с кнопкой?

:yes:

voraa 24.02.2021 15:57

Цитата:

Сообщение от karaul
А есть пример такой реализации?

Ну с комментариями это я поторопился.
Это прокатывало, когда произвольные данные надо было помещать в
<script>
/*
Тут произвольные данные
*/
</script>

Но для внешних файлов набросал такой примерчик
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8" lang="ru">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" >
  <title> LOAD DATA </title>
</head>
<style>
#out {
	position:relative;
	width: 300px;
	height:400px;
	overflow-y:auto;
	border: 1px red solid;
}
</style>
<body >
<pre id=out></pre>
<button id=rd1>read1</button>
<button id=rd2>read2</button>
<button id=rd3>read3</button>

<script>
var data;
const read = (fn) => {
	const uf = './data/'+ fn;
	const sf = document.createElement('script');
	return new Promise (res => {
		sf.onload = () => {
			sf.remove();
			res(data)
		}
		sf.src = uf;
		document.head.append(sf)
	})
}
document.getElementById('rd1').addEventListener('click', async () => {
	const text = await read('file1.dat')
// Тут какая то обработка. Пока так
	document.getElementById('out').textContent += text;
})
document.getElementById('rd2').addEventListener('click', async () => {
	const text = await read('file2.dat')
// Тут какая то обработка. Пока так
	document.getElementById('out').textContent += text;
})
document.getElementById('rd3').addEventListener('click', async () => {
	const text = await read('file3.dat')
// Тут какая то обработка. Пока так
	document.getElementById('out').textContent += text;
})
</script>
</body>
</html>


В директории с этим файлом создайте поддирескторию data
И в нее три файла
file1.dat
data=`
Это файл file1.dat
12345
67890
09876
54321
`;


file2.dat
data=`
Это файл file2.dat
qwert
yuiop
asdfg
hjklz
`;


file3.dat
data=`
Это файл file3.dat
йцуке
нгшщзх
фывап
ролджэ
`;


В этих файлах между первой строкой
data=`
и последней
`;
Могут размещаться произвольные текстовые данные, которые можно получить и потом как то обрабатывать

karaul 24.02.2021 18:10

Здорово, спасибо большое voraa, рони

На 80% стало ясно что делать В качестве упражнения хочу взять кнопки отсюда и файл ajax
https://datatables.net/examples/ajax...ta_source.html
всё это прикрутить его к примеры рони и склеить с кодом voraa

А то ломал головы все эти дни.

Форум не позволяет выдавать кредиты подряд, так бы еще добавил плюсиков

Общение - великая вещь

voraa 24.02.2021 18:13

Цитата:

Сообщение от karaul
В качестве упражнения хочу взять кнопки отсюда и файл ajax

ajax не работает с локальными файлами. Только с сервером.
Если бы можно было с локальными, то и не надо бы было со script возиться.

karaul 24.02.2021 18:44

Я имел в виду текстовое содержание файла Ajax на вкладке скопировать на диск для вашего примера сскриптом и способ вставить кнопки в ряд

karaul 24.02.2021 21:27

Цитата:

Сообщение от voraa (Сообщение 534032)
Но для внешних файлов набросал такой примерчик

Всё заработало, спасибо. Внутрь file.dat можно сразу положить массив c полями как у БД (такой массив я хотел стибрить по сслылке с вкладки Αjax выше), этот массив разобрать на таблицу

А сам файл готовить отдельной утилитой из командной строки, сканируя директорию и собирая в file.dat имена файлов или просто в текстовом редакторе

karaul 25.02.2021 18:31

Сделал другой вариант. Чтобы читать локальные файлы надо скрипт класть в раздел <head>
<head>
	<script src=./main.js></script>
</head>

<body>

<input id="read_table" type="file" value='' style="padding: 0px; size: 120px" /> 

</body>

а потроха скрипта упаковать в функцию

document.addEventListener('DOMContentLoaded', function () {

var fReader = new FileReader();
  var readtable = document.getElementById('read_table');
  
  var filename, data, headers;
  
  readtable.onchange = function (e) {
   var file = this.files[0];
   //console.log(file);
   filename = file.name;
   fReader.readAsText(file);
  }
 
  fReader.onload = function (e) {
	var text = e.target.result;
...
  }

})


работающий пример с такой упаковкой
https://github.com/gfmoore/BackFitCl...ileanalyser.js
В этом примере разборка бинарного файла, но логика та же

Другой пример здесь
https://www.js-tutorials.com/javascr...ascript-html5/
http://js-tutorials.com/demos/read_csv_javascript_demo/
В этом примере много мусора, но главный принцип такой же - упаковать скрипт в функцию. (Интересно, в описании про мусор пишут а про упаковку скрипта в функцию нет. Когда весь Интернет просто криком исходит - как в простом js и html читать локальные файлы)

Остались бантики - стили, форматирование таблицы, прикручивание кнопки для вызова поля с графиками и картой

Интересно, что структурно решение выглядит так же как на питоне

Логика везде одинаковая - здравый смысл - а синтаксис может быть какой угодно

voraa 25.02.2021 21:59

Цитата:

Сообщение от karaul
но главный принцип такой же - упаковать скрипт в функцию.

Я не понял, что вы под этим подразумеваете. Если упаковку в
document.addEventListener('DOMContentLoaded', function () {
то это совершенно не обязательно. Можно и без этого
<head>
</head>
 
<body>
 
<input id="read_table" type="file" value='' style="padding: 0px; size: 120px" />
<script>
var fReader = new FileReader();
  var readtable = document.getElementById('read_table');
   
  var filename, data, headers;
   
  readtable.onchange = function (e) {
   var file = this.files[0];
   //console.log(file);
   filename = file.name;
   fReader.readAsText(file);
  }
  
  fReader.onload = function (e) {
    var text = e.target.result;
...
  }
</script>
</body>

Сработает точно так же

Чтение файла через <input type=file> - это обычный способ. Только пользователь должен каждый раз указывать файл. И не получится прочитать файл по имени, указанному где то в программе или какой-нибудь таблице.

karaul 25.02.2021 22:23

voraa,

Вы правы, действительно в вашем коде файл читается, я проверил.

Когда я писал fitplotter -- ссылка в певом сообщении темы -- у меня в начале постоянно вылезала ошибка нечтения. Возможно из-за того что в теле скрипта был вызов другого скрипта с диска, который занимается разборкой двоичного файла. Заработало только после упаковки в ф-цию.

voraa 25.02.2021 22:35

Просто всякие document.getElementById('read_table');
нормально срабатывают только тогда, когда браузер уже распарсил HTML текст и знает, что это за элемент с id='read_table'.
Когда мы помещаем скрипт в конце body, все элементы уже известны и DOM дерево построено.
Если поместить этот скрипт вначале, то это функция не сработает, т.к этого элемента еще нет.
Но document.addEventListener('DOMContentLoaded', function () {})
говорит - выполни эту функцию, когда произойдет событие "Содержимое DOM готово", т.е все распарсено, все элементы известны.

karaul 25.02.2021 23:14

Цитата:

Сообщение от voraa (Сообщение 534073)
не получится прочитать файл по имени, указанному где то в программе или какой-нибудь таблице.

Cейчас у меня в таблице в каждой строке находятся кнопки, по нажатию на кнопку должен читаться и разбираться на кирпичи двоичный файл с именем указанным в строке. Сама функция для чтения еще прицеплена. Я хотел передавать имя файла из строки таблицы в окно с формой для графиков. В этой форме есть <input type=file>, я намеревался на вход этого контроля записывать имя файла и включать чтение.

Будет ли способ описанный выше работать при однократном нажатии на простую кнопку в внешней таблице без диалога с пользователем?

Или вместо простой кнопки в строку таблицы надо прописывать обязательно <input type=file> чтобы юзер обязательно сам нажимал на input ? И после нажатия input всегда появляется диалог? В моем случае диалог не нужен потому что имя файла и путь к нему уже известны, можно читать сразу.

voraa 25.02.2021 23:38

Цитата:

Сообщение от karaul
Или вместо простой кнопки в строку таблицы надо прописывать обязательно <input type=file> чтобы юзер обязательно сам нажимал на input ? И после нажатия input всегда появляется диалог?

Только так. Никакого другого способа задать имя файла в input нет.
Иначе скрипты читали бы любые файлы на компьютере пользователя, и отправляли бы их куда-нибудь.

karaul 26.02.2021 00:17

Мда, незадача

Написал
const queryString = window.location.search;//href;
const urlParams = new URLSearchParams(queryString);
const fitfilename = urlParams.get('file')
console.log(fitfilename);				
document.getElementById('myfile').value = fitfilename;

и получил (ниже 113 = 5 строка выше)
<main.js:113 Uncaught DOMException: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string.   >


Конечно, о таких вещах надо знать из систематического образования. А не хватая случайные знания из сети

karaul 26.02.2021 03:43

Первая версия fitplotter работала с http server (http-server-static.js) потому что я не понимал как читать файлы с диска и не мог коректно импортировать в скрипт парсер. Сервер нужен для разрешения импорта и работал через node.eхe. Потом я подсмотрел у других, как без сервера можно обойтись, и с радостью выбросил node.exe из инсталляции. Программа стала работать онлайн.

Получается что теперь надо вернуться к тому что было. Имя файла из таблицы посылать на сервер, т.к. только сервер может прочитать этот файл не включая диалога и не вовлекая юзера. А дальше возникает два варианта:

(1) возвращать двоичное содержание файла чтобы его парсил клиент или
(2) возвращать распарсенное содержание в виде объектов, по которым строится график

Как лучше ?

Просто я видел вопросы в сети - люди маялись с получением blob от сервера, он приходил испорченным

voraa 26.02.2021 07:51

Конечно лучше, пусть сервер этим занимается.
Может даже лучше, если заранее распарсить все и хранить на сервере уже текстовый вариант, что бы не приходилось при каждом запросе делать одно и тоже.

karaul 26.02.2021 17:12

Вы правы, если проект получит развитие, то надо так сделать.

Пока решил не есть слона целиком и попробовал простейшее решение. Посадил XMLHttpRequest на кнопку из таблицу, отправил на сервер путь к файлу, там считал в fs, послал назад, получил бинарные внутренности которые тут же распарсил на клиенте для проверки. Заработало.

function plotdata(e) {
	var filename = e.target.id;
	console.log(filename);
	var xhr = new XMLHttpRequest();
	xhr.onload = httpRequestfoo;
	xhr.open('GET', filename, true);
	xhr.responseType = 'arraybuffer'
	xhr.onerror = function (e) {
           console.log(error(xhr.statusText));
        };
	xhr.send(null);
}

function httpRequestfoo() {
  if (this.readyState  === 4) {
	if (this.status === 200) {
   	var blob = new Uint8Array(this.response);
	fitParser.parse(blob, function (error, data) {
	    if (error) {
		console.error(error);
	    } 	else {
            // data - распарсенное содержание
	console.log(data);
	}
   });
  }			
  }		
}

karaul 26.02.2021 22:21

Cделал проект публичным

https://github.com/karaul/fitalyser

скриншоты
https://github.com/karaul/fitalyser/...er/screenshots
mainWindow.JPG - таблица занятий, fitplotterWindow.JPG- окно анализа.

По клику в таблице окно анализа либо открывается, либо дополняется. Все выбранные в таблице дни дописываются в выпадающий список окна анализа, и потом легко выбирая эти дни можно переключаться между графиками.

Визуально надо ещё потрудится над таблицей, но прототип работает как было задумано.

Таблицу должен приготовить сам юзер. Это *.csv файл в котором прописаны пути к файлам тренировок. У меня такая таблицы есть, для в конце проекта может сделаю командную утититу которая бы по маске *.fit сама генерировала такую таблицу в заданной директории.

karaul 04.03.2021 19:22

прошу помощь, кажется опять я не знаю какого то секрета полишинеля

есть главное приложение main

/main/index.html

которое вызывает форму form изнутри main

windowFitplotter = window.open('./form/index.html)/

/main/form/index.html

Все работает, так и должно быть, main - главный, form - подчинененный и внутри main

Теперь я хочу чтобы form и main были на одном уровне

/main/index.html
/form/index.html

и делаю вызов windowFitplotter = window.open('./../form/index.html)/
(добавлено "/../" - чтобы уйти на уровень выше)

Но по прежнему происходит вызов изнутри
/main/form/index.html
и "/../" в игноре

Неправильно (bad practice) помещать form на том же уровне что и main? или что с настройками моего сервера, который игнорирует "/../" ?

Дело в том что form является независимым от main и к нему должен быть доступ сверху , и я не хочу вставлять form в main. Но при изменении кода внутри form, я не хочу каждый раз копировать файлы из независимой form под крышу main
cp form/* main/form/*

Как правильно поступать в таком случае ?


PS: включил подсказку форума changelog программы

Table ordering is implemented. It turns by clicking on the head of the column, thanks to rony from Russian javascript.ru/forum;

https://github.com/karaul/fitalyser/...r/CHANGELOG.md

karaul 04.03.2021 19:44

вроде здесь написанo, сервер игнорирует

https://en.wikipedia.org/wiki/Direct...aversal_attack

Directory traversal in its simplest form uses the ../ pattern.

voraa 05.03.2021 13:14

Цитата:

Сообщение от karaul
Как правильно поступать в таком случае ?

Можно сделать на сервере отдельный алиас для form
Что бы обращаться к нему /form/intex.html

Можно также иметь один каталог /form
но настраивать к нему разные алиасы.

Не знаю, каким сервером вы пользуетесь, но в аппаче это делается сравнительно легко
Например у меня есть сейчас 2 проекта
Они находятся (винда)
d:\works\catalog
d:\works\graph
есть общая библиотека (js)
d:\works\libjs

В конфиге аппача пишу

Alias /catalog/libjs "d:/works/libjs"
Alias /catalog "d:/works/catalog"

Alias /graph/libjs "d:/works/libjs"
Alias /graph "d:/works/graph"

Библиотека одна
Из catalog я обращаюсь к ней ./libjs/
так же из /graph - ./libjs/

karaul 05.03.2021 14:46

voraa,

Я сам слепил сервер из десятка команд по мануалу nodejs. Временным решением был костыль GOUP в имени файлы, котрый я менял его в сервере непосредственно перед чтением файла

pathname = pathname.replace(/GOUP/g, '/../');

Cпасибо за волшебное слово аlias - теперь понятно как делать правильно

karaul 08.03.2021 17:15

Новый улучшайзинг. Чего-то фундаментального не знаю.

Добавлена кнопка загрузки данных с внешнего независимого сайта - это элементы URL и download по ссылке

https://karaul.github.io/fitplotter/

Исполняется такой код (схематично)

fileUrl = "https://connect.garmin.com/proxy/download-service/files/activity/";
// id = xxxxxxxx  mist be provided bу user
var downloadUrl = fileUrl + id;
window.location.href = downloadUrl;
// now there appears save as dialog


Приходит двоичный файл и на его прием появляется системный диалог save as. Я файл сохраняю, а потом открываю его как локальный. Всё работает.

Как перехватить бинарные данные посланные в этот диалог save as и сразу пустить эти данные в обработку, чтобы не сохранять файл и потом не открывать его заново ?

Как сделать так тобы диалог save as не открывать, но данные получить

Я попробовал заменить window.location.href = downloadUrl который приводит к диалогу на XMLHttpRequest типа (сроки 13-25 чтобы создать свой диалог)
let id = document.getElementById('downloadURL').value.split("/").slice(-1);
		let  downloadUrl = fileUrl + id;
		//console.log(downloadUrl);
		// to download the current id 
		//window.location.href = downloadUrl;
		let xhr = new XMLHttpRequest();			
		xhr.onload = function (e) {
			if (this.readyState  === 4) {
				if (this.status === 200) {
					const blob = new Uint8Array(this.response);
					//parseBLOB(blob);
					// [url]https://stackoverflow.com/questions/22724070/prompt-file-download-with-xmlhttprequest[/url]
					let fileName = 'test.zip';
					window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
					window.requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fs) {
						fs.root.getFile(fileName, { create: true }, function (fileEntry) {
							fileEntry.createWriter(function (fileWriter) {
								fileWriter.addEventListener("writeend", function () {
									window.location = fileEntry.toURL();
								}, false);
								fileWriter.write(blob, "_blank");
							}, function () { });
						}, function () { });
					}, function () { });					
				 }			
				 if (this.status === 404) {
					 const  filename = document.getElementById("files").value;
					 errorNoFile("status 404",filename, 1);	
				 }
			}			 
		};


и оказалось что так нельзя
index.html:1 Access to XMLHttpRequest at 'https://connect.garmin.com/proxy/download-service/files/activity/6277409729' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.


Какие еще способы, как получить данные ? Эти данные сайт мне посылает сразу же, когда я просто ввожу в адрес сайта downloadUrl - появляется диалог - но как их перехватить в программе ?

Спасибо

karaul 08.03.2021 17:19

Надо чтото вроде
xhr.setRequestHeader("X-CSRFToken", csrftoken);
но где взять этот csrftoken ?

С другой стороны, если внешний сайт мне посыоает данные, csrftoken уже есть где-то

voraa 08.03.2021 17:36

Я думаю не получится считать файл, если страница берется с локального компьютера используя XMLHttpRequest или fetch.
Там идет проверка протоколов по которым получена страница. А если она локальная (from origin 'null'), то у нее нет протокола и он никогда не будет действительным и подходящим .
То, что она считывается браузером при указании в адресной строке - это совсем другое. Там таких проверок нет. Также как и при считывание с помощью <script>

karaul 08.03.2021 19:33

Спасибо за ответ. Он укрепил мою уверенность что я рою в верном направлении

В итоге нарыл пакет на питоне для соединения с гарминовским сайтом и закачки файлов - т.е. пакет делает то что мне надо. Для закачки он использует тот же URL
https://github.com/petergardfjall/garminexport

при запуске пакета сначала идет авторизация, потом там есть код типа
def _get_csrf_token(self):
        """Retrieves a Cross-Site Request Forgery (CSRF) token from Garmin's login
        page. The token is passed along in the login form for increased
        security."""
        log.info("fetching CSRF token ...")
        resp = self.session.get(SSO_LOGIN_URL, params=self._auth_params())
        if resp.status_code != 200:
            raise ValueError("auth failure: could not load {}".format(SSO_LOGIN_URL))
        # extract CSRF token
        csrf_token = re.search(r'<input type="hidden" name="_csrf" value="(\w+)"',
                               resp.content.decode('utf-8'))
        if not csrf_token:
            raise ValueError("auth failure: no CSRF token in {}".format(SSO_LOGIN_URL))
        return csrf_token.group(1)

karaul 09.03.2021 17:06

Питон делает из своей консоли для авторизации на внешнем сайте Гармине так
headers = {'origin': 'https://sso.garmin.com'}
и там все работает
Если я имитирую это поведение в JS с XMLHttpRequest
xhr.setRequestHeader('Origin', 'https://sso.garmin.com');
то получаю
Refused to set unsafe header Origin
Пишут что подменить заголовоr мешает броузер (хром). Незадача

Но есть еще один пакет для работы с Гармином на JS, в нем для авторизации используется axios вместо XMLHttpRequest и этот пакет работает из nodejs, и там также прописано в 'Origin', 'https://sso.garmin.com', иначе Гарминовский сайт возвращает пустую строку, а должен вернуть Cross-Site Request Forgery (CSRF) token

Если переписать браузерный код с XMLHttpRequest на axios, то получится выставить headers нужные для авторизации или это безнадежно?

voraa 09.03.2021 19:19

Цитата:

Сообщение от voraa
Я думаю не получится считать файл, если страница берется с локального компьютера используя XMLHttpRequest или fetch.

Это я опять жутко поторопился.
На самом деле МОЖНО!
Все дело в заголовках, которые посылает сервер
Вот такой простецкий сервер на nodejs

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.setHeader('Access-Control-Allow-Origin', '*');  // !important
  res.setHeader('Access-Control-Allow-Headers', 'origin, content-type, accept'); // !important
  res.end('Hello World with CORS!');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});


И вот такой html файл, расположенный локально
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8" lang="ru">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" >
  <title>TESTE</title>
</head>

<body id="body">
<button type='button' id='bsend'>Send</button>
<br>
<div id='answ'>
</div>
</div>
<script>
	document.getElementById('bsend').addEventListener('click',
	async () => {
		try {
			const resp = await fetch('http://127.0.0.1:3000', {mode:'cors'})
			let str = await resp.text();
			document.getElementById('answ').innerHTML = str
			console.log(str)
		}catch (err){
			document.getElementById('answ').innerHTML=`Error ${err.name} ${err.code} ${err.message}`;
		}
	}
	);
</script>
</body>
</html>


Совершенно спокойно взаимодействуют.

karaul 10.03.2021 02:25

Забанили

Garmin Express Error 1006 and 1015
After attempting multiple times to log in to Garmin Express, you may receive the following errors:

"Error 1015 error RAY ID 58c1fd3vaa9dd314. You are being rate limited.  The owner of this website has banned you temporarily from accessing this website".
"Error 1006 Ray ID 5c7fda4d8a9a0d12.  Access denied.  The owner of this website (support.garmin.com) has banned your IP address (**.**.***.***).
This is the expected behavior and is displayed when too many attempts have been made to log in.  The account will be unblocked after 24 hours automatically.  Please wait 24 hours before trying to sign in again.


не так то просто отладить код при общении с чужим сайтом

karaul 11.03.2021 17:44

Меня разбанили и я кажется разобрался как правильно надо поступать.

Идея была в том чтобы прикрутить fitplotter к внешнему независимому сайту и скачивать оттда тренировки для анализа. Этот сайт требует аторизациии, считается что каждый пользователь эту автризацию проходит сам.

Если просто скачивать по одному файлу с сайта, то все работает, и каждый раз юзер общается с диалогом save as - куча ненужных кликов.

Если пытаться скачивать автоматически из браузера, то нельзя подменить заголовок и ничего не получается

Решение такое. Ставить свой сервер, на нем предварительно делать авторизацию на чужом сервере. Клиентская программа должна сначала обращаться к своему серверу, на котором прошла авторизация, и уже с него идти за файлами к чужому серверу.

Если чужой сервер такую практику приветствует, то он публикует свой API для доступа. Так сделала strava (и получается что если бы работал с ней, то я бы ломился в открытую дверь.) А Garmin такую практику осуждает и на гитхабе программисты матом исходят пытаясь разобраться что там Garmin придумал чтобы затруднить программную авторизацию.

У меня есть отдельные куски для сервера и клиента, надо ли их сшивать - это теперь больше политический вопрос чем технический

А если работать со strava, то она не выдает сырые файлы, из-за которых собственно весь проект начался.

И ходят слухи что Garmin якобы откроет свой АПИ.

Конечно, такие вещи надо знать до а не после. Неделя головоломки - это я дешево отлелался

karaul 11.03.2021 18:24

Цитата:

Сообщение от voraa (Сообщение 534558)
Все дело в заголовках, которые посылает сервер
Вот такой простецкий сервер на nodejs

да, именно так, нужен свой сервер для подмены заголовков.

Есть некий адрес, который становится доступным после авторизации. Допустим, я авторизовался как человек через окно браузера. До авторизации мне этот адрес недоступен, после я могу открыть его и прочитать содержимое страницы глазами и вручную скопировать, сохранить как файл и проч. Но всё вручную, автоматизировать через браузер не получается, для этого нужны серверные команды.

А если делать через сервер, то надо сначала делать авторизацию через сервер, и все команды для автоматического чтения файла посылать через этот сервер


Часовой пояс GMT +3, время: 05:33.