Cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

fetch() POST text file not working in Simulator or watch

ANSWERED

I tried numerous ways to get a text file to my server from the watch/companion. There is no problem getting it to the companion, but it does not get to the server. I have verified the server code using fetch from a different WEB site and JS. The simulator sends the fornData appended email address, but not the appended file. The following is the code for the watch, companion, .htaccess and uploadFile.php:

 

 

//device code to create and send txt file to companion
function createSendFile() {
if (appbit.permissions.granted("access_internet")) {
var todaydate = new Date();
var hour = todaydate.getHours();
var fn = "SSData_" + (todaydate.getYear() + 1900) + "-" + util.zeroPad(todaydate.getMonth() + 1) + "-" + util.zeroPad(todaydate.getDate()) + "_"
+ util.zeroPad(todaydate.getHours()) + "-" + util.zeroPad(todaydate.getMinutes()) + "-" + util.zeroPad(todaydate.getSeconds()) + "_" + display_colors[6];
if (fn.length > 59) {
fn = fn.slice(0, 59); //only keep 0-60
fn += "."; //add extra . to show it was cut
}
fn += ".txt"; //full file name up to 64 chars
var Data = ("Filename: " + fn
+ "\nEmail: " + display_colors[6]
+ "\nDate: "
+ ((todaydate.getMonth() + 1) + "/" + util.zeroPad(todaydate.getDate()) + "/" + (todaydate.getYear() + 1900))
+ "\nTime: "
+ ((preferences.clockDisplay === "12h")
? (((hour % 12) || 12) + ":" + util.zeroPad(todaydate.getMinutes()) + ":" + util.zeroPad(todaydate.getSeconds()) + (hour >= 12 ? 'pm' : 'am'))
: (hour + ":" + util.zeroPad(todaydate.getMinutes()) + ":" + util.zeroPad(todaydate.getSeconds())))
+ "\n");
Data += "\n*** Stats Data ***\n";
calc_hr_auto_level();
for (mypage = 1; mypage < 25; mypage++) {
Data += get_stats();
}
Data += get_stats();
Data += "\n*** Setup Data ***\n";
for (mypage = 1; mypage < 11; mypage++) {
Data += get_setup();
}
Data += get_setup();
Data += "\n*** Time/Exercise Selection Flag Definitions ***\n";
Data += "Hex values: 0x400=Font or Pace, 0x200=Time Dim or GPS, 0x100=Caloriess, 0x80=Active Minutes, 0x40=Distance,\n0x20=Events, 0x10=Resting HR, 0x8=Floors, 0x4=Steps, 0x2=Battery, 0x1=Date or Time\n";
Data += "*Current Time Selection Flag = 0x" + display_data[4].toString(16).toUpperCase() + "\n";
Data += "*Exercise Selection Flag = 0x" + display_data[5].toString(16).toUpperCase() + "\n";
fs.writeFileSync(fn, Data, "ascii");
if (fs.existsSync("/private/data/" + fn)) {
/* let ascii_read = fs.readFileSync("/private/data/" + fn, "ascii");
console.log(ascii_read);*/
ctrl[22] = "WMR"; //Waiting or Missing Response
sendFileOutbox("/private/data/" + fn);
/* fs.unlinkSync("/private/data/" + fn);
console.log("Successful delete");*/
}
}
else {
ctrl[22] = "NIP"; //No Internet Permission
console.log("No Internet Permission");
}
}

async function sendFileOutbox(filename) {
// console.log("Start sending");
outbox
.enqueueFile(filename)
.then((ft) => {
// console.log("File " + filename + " successfully queued.");
fs.unlinkSync(filename);
// console.log("Success delete");
})
.catch((error) => {
// console.log(`Failed to schedule transfer: $?{error}`);
fs.unlinkSync(filename);
// console.log("Error delete");
})

/* fs.unlinkSync(filename);
console.log("Successful delete");*/
}

function checkOutbox() {
outbox
.enumerate()
.then((files) => {
outbox_files = files.length;
// console.log(files.length);
})
}





//Entire Companion code to receive file and fetch() POST to WEB site, then acknowledge
import * as messaging from "messaging";
import { me as appbit } from "appbit";
import { settingsStorage } from "settings";
import { inbox } from "file-transfer";
import { outbox } from "file-transfer";
import { Image } from "image";
import { device } from "peer";


settingsStorage.addEventListener("change", evt => {
/* if (evt.key === "ubg") {
compressAndTransferImage(evt.newValue);
}
else {*/
// console.log(evt.key + " " + evt.newValue); //test for data
// if (evt.oldValue !== evt.newValue) {
sendValue(evt.key, evt.newValue);
// }
// }
});

setDefaultSetting("dimstart", JSON.stringify({ selected: [23] }));
setDefaultSetting("dimstop", JSON.stringify({ selected: [5] }));
setDefaultSetting("dimlevel", JSON.stringify({ selected: [3] }));
setDefaultSetting("normallevel", JSON.stringify({ selected: [3] }));
setDefaultSetting("hrtriplevel", JSON.stringify({ selected: [2] }));
setDefaultSetting("dhtriplevel", JSON.stringify({ selected: [2] }));
setDefaultSetting("dh_mult", JSON.stringify({ selected: [2] }));
setDefaultSetting("dh_exer_mult", JSON.stringify({ selected: [2] }));
setDefaultSetting("hhrtriptime", JSON.stringify({ selected: [2] }));
setDefaultSetting("hhrmintime", JSON.stringify({ selected: [2] }));
setDefaultSetting("hhrtimeonly", JSON.stringify({ selected: [2] }));

setDefaultSetting("hrcolormode", JSON.stringify({ selected: [1] }));

setDefaultSetting("heartrateColor", JSON.stringify( "lawngreen" ));
setDefaultSetting("timeColor", JSON.stringify( "linen" ));
setDefaultSetting("statsIconColor", JSON.stringify( "aquamarine" ));
setDefaultSetting("statsTextColor", JSON.stringify( "bisque" ));
setDefaultSetting("statsDeltaTextColor", JSON.stringify( "gold" ));
setDefaultSetting("bgColor", JSON.stringify( "#000000" ));

function sendValue(key, val) {
if (val) {
// console.log("Key=" + key + " Val=" + val);
sendSettingData({
key: key,
value: JSON.parse(val)
});
}
}

function sendSettingData(data) {
if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
messaging.peerSocket.send(data);
}
}

function setDefaultSetting(key = "", value = "") {
let extantValue = "";
extantValue = settingsStorage.getItem(key);
// console.log ("Get="+extantValue+" key="+key+" value="+value);
if (extantValue !== null) {
// console.log("!null");
// settingsStorage.setItem(key, extantValue);
}
else {
// console.log("null");
settingsStorage.setItem(key, value);
}
}

settingsStorage.setItem("screenWidth", device.screen.width);
settingsStorage.setItem("screenHeight", device.screen.height);



async function processAllFiles() {
let file;
while ((file = await inbox.pop())) {
const data = await file.text();

let email = JSON.parse(settingsStorage.getItem("email_addr")).name;
let url = 'https://REDCACTED.com/REDCACTED/uploadFile.php';
const formData = new FormData();

formData.append('upEmail', email);
formData.append('upFile', file);

console.log(file.name);
console.log(email);
console.log(data);

fetch(url, {
method: 'POST',
// mode: 'cors', // no-cors, *cors, same-origin
/* headers: {
      'Content-type': 'multipart/form-data'
    },*/
// credentials: 'same-origin', // include, *same-origin, omit
body: formData,
})
.then(function (response) {
console.log("Response Status: " + response.status);
console.log("Response Ok: " + response.ok);
// console.log("Response Headers: " + response.headers);
// console.log("Response url: " + response.url);
response.text()
.then(function (text) {
console.log(text);
if ((response.ok) && (response.status >= 200) && (response.status < 300)) {
// console.log(JSON.parse(text).message);
sendValue("send_error",text); //tell watch fetch ok, but check message
}
else
sendValue("send_error", JSON.stringify({message:response.status})); //tell watch fetch failed
})
})
.catch(function (error) {
console.log(error.name + ":" + error.message);
sendValue("send_error", JSON.stringify({message:"IE"})) //tell watch fetch failed
})
}
}

inbox.addEventListener("newfile", processAllFiles);
processAllFiles();



//Companion Inbox File: Console.log Results
[1:20:41 PM]SSData_2021-02-07_13-20-41_REDCACTED@REDCACTED.com.txt companion/index.js:88,5
[1:20:41 PM]REDCACTED@REDCACTED.com companion/index.js:89,5
[1:20:41 PM]Filename: SSData_2021-02-07_13-20-41_REDCACTED@REDCACTED.com.txt companion/index.js:90,5
Email: REDCACTED@REDCACTED.com
Date: 2/07/2021
Time: 13:20:41

*** Stats Data ***
*Events = 0
*HHR = 0
*LDHR = 0
*HR Data Gaps = 138
*HR Gap Filtered = 473
*HR dt<0.9 = 140
*HR Valid Pts = 7.00000e+1
*HR Pts / Day = 4
*HR dt Avg = 36.380
*HR dt Min = 1.024
*HR dt Max = 59.008
*HR Avg = 65.1
*HR Min = 60
Date: 1/23/21
Time: 11:22:55
*HR Max = 220
Date: 1/25/21
Time: 14:25:35
*dh Min(41.47) = 0 (60->60)
Date: 1/23/21
Time: 11:22:55
*dh Max(48.58) = 48.8 (60->220)
Date: 1/25/21
Time: 14:25:35
*dh Normal
*+dh = 1
*+dh Trip = 1
*-dh = 0
*-dh Trig = 0
*HHRLevelTime = 0
*HHRTimeOnly = 0
*Battery = 100%
*On Battery = 0:00:21
*%/hr = (100% - 100%) / 0:00:00
*%/hr = 0.000
*D on = 0:00:21 / 0 (0, 0) = 0.00
(100.00%*0.85)

*** Setup Data ***
*Version 5.53
*BusyBrainConsulting.com for Clock Manual pdf file
*HHR/LDHR = Show Events
*Font = Light
*Exercise = pickleball
*Distance = km
*User Age = 30
*Rest HR = 56
*Sleep Dim = On
*Email for Send:
REDCACTED@REDCACTED.com
*Time Dim = On
(23 to 6)
*Dim = 20%
*Bright = 85%
*HHRTripLevel = 100% Calc = 136.0 BPM
*HHRDropout = 85% = 115.6 BPM
*dhTripLevel = 100% Calc = 19.6 BPM
*LDHR = 130% = 25.5 BPM
*dh*Exer = 130% = 25.5 BPM
*LDHR*Exer = 169% = 33.2 BPM
*HRDatagap = 1min
*HRGapFilter = 15sec/30sec
*HHRTripTime = 2min
*HHRMinTime = 15min
*HHRTimeOnly = 12hr
*HR Color = Fixed

*** Time/Exercise Selection Flag Definitions ***
Hex values: 0x400=Font or Pace, 0x200=Time Dim or GPS, 0x100=Caloriess, 0x80=Active Minutes, 0x40=Distance,
0x20=Events, 0x10=Resting HR, 0x8=Floors, 0x4=Steps, 0x2=Battery, 0x1=Date or Time
*Current Time Selection Flag = 0x627
*Exercise Selection Flag = 0x5E5
[1:20:41 PM]The 'permission' API is not yet supported in the Fitbit OS Simulator. Behavior may not match the documentation or real devices.
[1:20:42 PM]Response Status: 200 companion/index.js:102,9
[1:20:42 PM]Response Ok: true companion/index.js:103,9
[1:20:42 PM]"message":"MFT","code":43,"status":"Missing File\/TmpFile","email":"REDCACTED@REDCACTED.com","tmpfile":null,"file":null,"debug":[]}
companion/index.js:108,11

 

 

 


//https://REDCACTED.com/REDCACTED directory entire .htaccess file
#Protect .htaccess
<Files .htaccess>
order allow,deny
deny from all
</Files>

RewriteEngine On
RewriteCond %{ENV:HTTPS} !on
RewriteRule (.*) https://REDCACTED.com/$1 [R=301,L]


#FreeScript
AddDefaultCharset UTF-8


# Only the following file extensions are allowed
<FilesMatch "\.(txt)$">
Order Deny,Allow
Allow from all
Header add Access-Control-Allow-Origin: "*"
Header add Access-Control-Allow-Methods: "GET,POST,OPTIONS,DELETE,PUT"
Header add Access-Control-Allow-Credentials: true
</FilesMatch>

 

 

 

//https://REDCACTED.com/REDCACTED directory entire uploadFile.php file
<?php
#version 1.02
#  leaving this on for now, could help us identify some php wierdness that could cause issues, change to 0 to disable
define('DEBUG_MODE', 1); //0=Off, 1=code line,status,filename, >1 add file data
#  this might need to be 'upfile'
define('FILE_CONST', 'upFile');
define('EMAIL_CONST', 'upEmail');
define('TARGET_DIR', './uploads/');
#  this can theretically change, but practically 1000000 has been the same for 15 years
define('MAX_UPLOAD', 5000);

$response = array(
'message' => "ERR",
);
DEBUG_MODE && $response['code'] = __LINE__;
DEBUG_MODE && $response['status'] = 'Undefined Error';

header('Content-Type: application/json; charset=utf-8');
header("Access-Control-Allow-Origin: *");
header('Access-Control-Allow-Credentials: true');

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "MPT";
DEBUG_MODE && $response['status'] = 'Missing POST Type';
finish($response);
}

$email = $_POST[EMAIL_CONST];
$file = $_FILES[FILE_CONST];
$tmpfile = $_FILES[FILE_CONST]['tmp_name'];
DEBUG_MODE && $response['email'] = $email;
DEBUG_MODE && $response['tmpfile'] = $tmpfile;
DEBUG_MODE && $response['file'] = $file;

if (((empty($file)) || (empty($tmpfile))) && (empty($email))) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "MFE";
DEBUG_MODE && $response['status'] = 'Missing File and Email Address';
finish($response);
}
else if ((empty($file)) || (empty($tmpfile))) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "MFT";
DEBUG_MODE && $response['status'] = 'Missing File/TmpFile';
finish($response);
}
else if (empty($email)) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "MEA";
DEBUG_MODE && $response['status'] = 'Missing Email Address';
finish($response);
}

$filename = $_FILES[FILE_CONST]['name'];
$ext = strtolower(end(explode('.',$filename)));
DEBUG_MODE && $response['Target_Dir'] = TARGET_DIR;
DEBUG_MODE && $response['filename'] = $filename;
DEBUG_MODE && $response['ext'] = $ext;

if (!isset($file['error']) || is_array($file['error'])) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "IFS";
DEBUG_MODE && $response['status'] = 'Invalid File Structure';
finish($response);
}

if (!empty($file['error'])) {
#  these are PHP constants defined somewhere else, treat it as JFM for now
switch($file['error']) {
case UPLOAD_ERR_NO_FILE:
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "UNF";
DEBUG_MODE && $response['status'] = 'UPLOAD_ERR_NO_FILE';
break;

case UPLOAD_ERR_INI_SIZE:
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "UIS";
DEBUG_MODE && $response['status'] = 'UPLOAD_ERR_INI_SIZE';
break;
case UPLOAD_ERR_FORM_SIZE:
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "UFS";
DEBUG_MODE && $response['status'] = 'UPLOAD_ERR_FORM_SIZE';
break;

default:
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "UND";
DEBUG_MODE && $response['status'] = 'UPLOAD_ERR_UNDEFINED';
}

finish($response);
}

if ($file['size']==0 || $file['size'] > MAX_UPLOAD) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "IFS";
DEBUG_MODE && $response['status'] = 'Invalid File Size (' . $file['size'] . ')';
finish($response);
}

$valid_mime_types = [
'text/plain',
];

$mime_type = get_file_mime($file['tmp_name']);

if (!in_array($mime_type, $valid_mime_types)) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "IMT";
DEBUG_MODE && $response['status'] = 'Invalid Mime Type (' . $mime_type . ')';
finish($response);
}

/*if (!empty($_POST[FILE_CONST])) {
$t = preg_replace('/[^A-Za-z0-9_.-]/' , '', $_POST[FILE_CONST]);
$t = explode('.', $t);
$filename = reset($t);
$ext = end($t);
}*/

$valid_ext = [
'txt',
]; //comma seperated values
if (!in_array($ext, $valid_ext)) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "IET";
DEBUG_MODE && $response['status'] = 'Invalid Ext Type (' . $ext . ')';
finish($response);
}

$perm_filename = save_uploaded_file($file['tmp_name'], $ext, basename($filename, '.txt'));

if (empty($perm_filename)) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "FSF";
DEBUG_MODE && $response['status'] = 'Failed Save File';
finish($response);
}

$data = get_file_contents($perm_filename);
(DEBUG_MODE > 1) && $response['data'] = $data;

$subject = "Fitbit Watch Data File: " . $filename;
$headers = array(
"From" => "REDCACTED@REDCACTED.com",
"ByWayOf" => "REDCACTED.com",
"Reply-To" => "REDCACTED@REDCACTED.com",
"X-Mailer" => "PHP/" . phpversion()
);
$success = mail($email, $subject, $data, $headers);
if (!$success) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "FSE";
DEBUG_MODE && $response['status'] = 'Failed Send Email';
DEBUG_MODE && $response['mail_error'] = error_get_last()[message];
finish($response);
}

DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "Ok";
DEBUG_MODE && $response['status'] = 'success';

finish($response);

#########

function finish(array $input = []) {
DEBUG_MODE && $input['debug'] = $_FILES;
echo json_encode($input);
exit;
}

function get_file_contents($filename) {
#  Don't know if you need this, used it in some debugging so now you have it
if (empty($filename) || !is_readable($filename)) {
return false;
}

$data = file_get_contents($filename);

// return explode(PHP_EOL, $data);
return $data;
}

function get_file_mime($filename) {
$finfo = new finfo(FILEINFO_MIME_TYPE);
return $finfo->file($filename);
}

function save_uploaded_file($tmpfile, $ext, $filename = '') {
#  if you want a randomized filename, don't pass 3rd parameter
if (empty($filename)) {
$filename = sha1_file($tmpfile . time());
}

$success = move_uploaded_file($tmpfile, TARGET_DIR . $filename . '.' . $ext);

return $success ? TARGET_DIR . $filename . '.' .$ext : false;
}
?>

 

 

Best Answer
0 Votes
1 BEST ANSWER

Accepted Solutions

Thanks for the information. I already determined that file transfer is not supported in the Fitbit fetch() function. I did upload the file content using JSON object and reconstructing the file in the php receiving script. All the examples that I was directed to did show how to upload a file and I was successful in uploading a file using fetch() in Javascript in a browser. The Fitbit fetch() API and Guide should indicated that file transfer is not currently supported. The ability to even do this is amazing. The user can press a button on the watch and receive an email with the requested data.

Thanks again.

View best answer in original post

Best Answer
0 Votes
2 REPLIES 2

I don't think you can transfer the file directly in the companion like that. I think you need to embed the file contents as the payload,

 

file.text()

 

See https://dev.fitbit.com/build/guides/communications/file-transfer/#companion-inbox

 

I removed some of the URLs from your post for privacy reasons.

Best Answer
0 Votes

Thanks for the information. I already determined that file transfer is not supported in the Fitbit fetch() function. I did upload the file content using JSON object and reconstructing the file in the php receiving script. All the examples that I was directed to did show how to upload a file and I was successful in uploading a file using fetch() in Javascript in a browser. The Fitbit fetch() API and Guide should indicated that file transfer is not currently supported. The ability to even do this is amazing. The user can press a button on the watch and receive an email with the requested data.

Thanks again.

Best Answer
0 Votes