Added different types of validation in addition to status code checks, including custom JavaScript evaluation.

This commit is contained in:
Aidan Follestad 2016-08-06 00:25:56 -05:00
parent ef8031d848
commit 08a0d42f65
20 changed files with 568 additions and 50 deletions

View file

@ -8,8 +8,8 @@ android {
applicationId "com.afollestad.nocknock"
minSdkVersion 21
targetSdkVersion 24
versionCode 8
versionName "0.1.1.0"
versionCode 9
versionName "0.1.2.0"
jackOptions {
enabled true
@ -34,6 +34,7 @@ dependencies {
compile 'com.afollestad.material-dialogs:core:0.8.6.2'
compile 'com.afollestad.material-dialogs:commons:0.8.6.2'
compile 'com.afollestad:bridge:3.2.5'
compile 'com.afollestad:inquiry:2.0.2'
compile 'com.afollestad:inquiry:3.1.0'
compile 'com.android.support:design:24.1.1'
compile files('libs/rhino-1.7.7.1.jar')
}

BIN
app/libs/rhino-1.7.7.1.jar Normal file

Binary file not shown.

View file

@ -116,7 +116,7 @@ public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.ServerVH>
holder.textStatus.setText(R.string.checking_status);
break;
case ServerStatus.ERROR:
holder.textStatus.setText(R.string.something_wrong);
holder.textStatus.setText(model.reason);
break;
}

View file

@ -27,4 +27,10 @@ public class ServerModel implements Serializable {
public long lastCheck;
@Column
public String reason;
@Column
@ValidationMode.Enum
public int validationMode;
@Column
public String validationContent;
}

View file

@ -0,0 +1,21 @@
package com.afollestad.nocknock.api;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author Aidan Follestad (afollestad)
*/
public final class ValidationMode {
public final static int STATUS_CODE = 1;
public final static int TERM_SEARCH = 2;
public final static int JAVASCRIPT = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATUS_CODE, TERM_SEARCH, JAVASCRIPT})
public @interface Enum {
}
}

View file

@ -17,11 +17,12 @@ public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
Inquiry.init(context, MainActivity.DB_NAME, 1);
ServerModel[] models = Inquiry.get()
final Inquiry inq = Inquiry.newInstance(context, MainActivity.DB_NAME).build(false);
ServerModel[] models = inq
.selectFrom(MainActivity.SITES_TABLE_NAME, ServerModel.class)
.all();
AlarmUtil.setSiteChecks(context, models);
inq.destroyInstance();
}
}
}

View file

@ -17,8 +17,9 @@ public class ConnectivityReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
final boolean hasInternet = NetworkUtil.hasInternet(context);
Log.v("ConnectivityReceiver", "Connectivity state changed... has internet? " + hasInternet);
if (hasInternet)
if (hasInternet) {
context.startService(new Intent(context, CheckService.class)
.putExtra(CheckService.ONLY_WAITING, true));
}
}
}

View file

@ -16,14 +16,17 @@ import android.widget.Toast;
import com.afollestad.bridge.Bridge;
import com.afollestad.bridge.BridgeException;
import com.afollestad.bridge.Response;
import com.afollestad.inquiry.Inquiry;
import com.afollestad.inquiry.Query;
import com.afollestad.nocknock.BuildConfig;
import com.afollestad.nocknock.R;
import com.afollestad.nocknock.api.ServerModel;
import com.afollestad.nocknock.api.ServerStatus;
import com.afollestad.nocknock.api.ValidationMode;
import com.afollestad.nocknock.ui.MainActivity;
import com.afollestad.nocknock.ui.ViewSiteActivity;
import com.afollestad.nocknock.util.JsUtil;
import com.afollestad.nocknock.util.NetworkUtil;
import java.util.Locale;
@ -31,6 +34,7 @@ import java.util.Locale;
/**
* @author Aidan Follestad (afollestad)
*/
@SuppressWarnings("CheckResult")
public class CheckService extends Service {
public static String ACTION_CHECK_UPDATE = BuildConfig.APPLICATION_ID + ".CHECK_UPDATE";
@ -89,7 +93,7 @@ public class CheckService extends Service {
}
private void updateStatus(ServerModel site) {
Inquiry.get()
Inquiry.get(this)
.update(MainActivity.SITES_TABLE_NAME, ServerModel.class)
.where("_id = ?", site.id)
.values(site)
@ -151,13 +155,13 @@ public class CheckService extends Service {
return START_NOT_STICKY;
}
Inquiry.init(this, MainActivity.DB_NAME, 1);
Inquiry.newInstance(this, MainActivity.DB_NAME).build();
isRunning(true);
Bridge.config()
.defaultHeader("User-Agent", getString(R.string.app_name) + " (Android)");
new Thread(() -> {
final Query<ServerModel, Integer> query = Inquiry.get()
final Query<ServerModel, Integer> query = Inquiry.get(this)
.selectFrom(MainActivity.SITES_TABLE_NAME, ServerModel.class);
if (intent != null && intent.hasExtra(MODEL_ID)) {
query.where("_id = ?", intent.getLongExtra(MODEL_ID, -1));
@ -190,12 +194,27 @@ public class CheckService extends Service {
updateStatus(site);
try {
Bridge.get(site.url)
final Response response = Bridge.get(site.url)
.throwIfNotSuccess()
.cancellable(false)
.request();
.request()
.response();
site.reason = null;
site.status = ServerStatus.OK;
if (site.validationMode == ValidationMode.TERM_SEARCH) {
final String body = response.asString();
if (body == null || !body.contains(site.validationContent)) {
site.status = ServerStatus.ERROR;
site.reason = "Term \"" + site.validationContent + "\" not found in response body.";
}
} else if (site.validationMode == ValidationMode.JAVASCRIPT) {
final String body = response.asString();
site.reason = JsUtil.exec(site.validationContent, body);
if (site.reason != null && !site.toString().isEmpty())
site.status = ServerStatus.ERROR;
}
} catch (BridgeException e) {
processError(e, site);
}
@ -212,4 +231,10 @@ public class CheckService extends Service {
return START_STICKY;
}
@Override
public void onDestroy() {
Inquiry.destroy(this);
super.onDestroy();
}
}

View file

@ -15,6 +15,7 @@ import android.view.ViewAnimationUtils;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
@ -23,6 +24,7 @@ import android.widget.TextView;
import com.afollestad.nocknock.R;
import com.afollestad.nocknock.api.ServerModel;
import com.afollestad.nocknock.api.ServerStatus;
import com.afollestad.nocknock.api.ValidationMode;
/**
* @author Aidan Follestad (afollestad)
@ -37,6 +39,7 @@ public class AddSiteActivity extends AppCompatActivity implements View.OnClickLi
private EditText inputInterval;
private Spinner spinnerInterval;
private TextView textUrlWarning;
private Spinner responseValidationSpinner;
private boolean isClosing;
@ -51,6 +54,7 @@ public class AddSiteActivity extends AppCompatActivity implements View.OnClickLi
textUrlWarning = (TextView) findViewById(R.id.textUrlWarning);
inputInterval = (EditText) findViewById(R.id.checkIntervalInput);
spinnerInterval = (Spinner) findViewById(R.id.checkIntervalSpinner);
responseValidationSpinner = (Spinner) findViewById(R.id.responseValidationMode);
toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(view -> closeActivityWithReveal());
@ -71,6 +75,7 @@ public class AddSiteActivity extends AppCompatActivity implements View.OnClickLi
ArrayAdapter<String> intervalOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner,
getResources().getStringArray(R.array.interval_options));
intervalOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
spinnerInterval.setAdapter(intervalOptionsAdapter);
inputUrl.setOnFocusChangeListener((view, hasFocus) -> {
@ -90,6 +95,38 @@ public class AddSiteActivity extends AppCompatActivity implements View.OnClickLi
}
});
ArrayAdapter<String> validationOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner,
getResources().getStringArray(R.array.response_validation_options));
validationOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
responseValidationSpinner.setAdapter(validationOptionsAdapter);
responseValidationSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
final View searchTerm = findViewById(R.id.responseValidationSearchTerm);
final View javascript = findViewById(R.id.responseValidationScript);
final TextView modeDesc = (TextView) findViewById(R.id.validationModeDescription);
searchTerm.setVisibility(i == 1 ? View.VISIBLE : View.GONE);
javascript.setVisibility(i == 2 ? View.VISIBLE : View.GONE);
switch (i) {
case 0:
modeDesc.setText(R.string.validation_mode_status_desc);
break;
case 1:
modeDesc.setText(R.string.validation_mode_term_desc);
break;
case 2:
modeDesc.setText(R.string.validation_mode_javascript_desc);
break;
}
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
findViewById(R.id.doneBtn).setOnClickListener(this);
}
@ -192,6 +229,21 @@ public class AddSiteActivity extends AppCompatActivity implements View.OnClickLi
model.lastCheck = System.currentTimeMillis() - model.checkInterval;
switch (responseValidationSpinner.getSelectedItemPosition()) {
case 0:
model.validationMode = ValidationMode.STATUS_CODE;
model.validationContent = null;
break;
case 1:
model.validationMode = ValidationMode.TERM_SEARCH;
model.validationContent = ((EditText) findViewById(R.id.responseValidationSearchTerm)).getText().toString().trim();
break;
case 2:
model.validationMode = ValidationMode.JAVASCRIPT;
model.validationContent = ((EditText) findViewById(R.id.responseValidationScriptInput)).getText().toString().trim().replace("\n", " ");
break;
}
setResult(RESULT_OK, new Intent()
.putExtra("model", model));
finish();

View file

@ -3,6 +3,7 @@ package com.afollestad.nocknock.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -33,6 +34,7 @@ import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.nocknock.R;
import com.afollestad.nocknock.adapter.ServerAdapter;
import com.afollestad.nocknock.api.ServerModel;
import com.afollestad.nocknock.api.ValidationMode;
import com.afollestad.nocknock.dialogs.AboutDialog;
import com.afollestad.nocknock.services.CheckService;
import com.afollestad.nocknock.util.AlarmUtil;
@ -44,7 +46,8 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
private final static int ADD_SITE_RQ = 6969;
private final static int VIEW_SITE_RQ = 6923;
public final static String DB_NAME = "nock_nock";
public final static String SITES_TABLE_NAME = "sites";
public final static String SITES_TABLE_NAME_OLD = "sites";
public final static String SITES_TABLE_NAME = "site_models";
private FloatingActionButton mFab;
private RecyclerView mList;
@ -72,6 +75,7 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
}
};
@SuppressLint("CommitPrefEdits")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -94,9 +98,33 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
mFab = (FloatingActionButton) findViewById(R.id.fab);
mFab.setOnClickListener(this);
Inquiry.init(this, DB_NAME, 1);
Inquiry.newInstance(this, DB_NAME).build();
Bridge.config()
.defaultHeader("User-Agent", getString(R.string.app_name) + " (Android)");
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (!sp.getBoolean("migrated_db", false)) {
final Inquiry mdb = Inquiry.newInstance(this, DB_NAME)
.instanceName("migrate_db")
.build(false);
final ServerModel[] models = Inquiry.get(this)
.selectFrom(SITES_TABLE_NAME_OLD, ServerModel.class)
.projection("_id", "name", "url", "status", "checkInterval", "lastCheck", "reason")
.all();
if (models != null) {
Log.d("SiteMigration", "Migrating " + models.length + " sites to the new table.");
for (ServerModel model : models) {
model.validationMode = ValidationMode.STATUS_CODE;
model.validationContent = null;
}
//noinspection CheckResult
mdb.insertInto(SITES_TABLE_NAME, ServerModel.class)
.values(models)
.run();
mdb.dropTable(SITES_TABLE_NAME_OLD);
}
sp.edit().putBoolean("migrated_db", true).commit();
}
}
private void showRefreshTutorial() {
@ -141,6 +169,10 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
super.onPause();
CheckService.isAppOpen(this, false);
if (isFinishing()) {
Inquiry.destroy(this);
}
NotificationManagerCompat.from(this).cancel(CheckService.NOTI_ID);
try {
unregisterReceiver(mReceiver);
@ -152,7 +184,7 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
private void refreshModels() {
mAdapter.clear();
mEmptyText.setVisibility(View.VISIBLE);
Inquiry.get()
Inquiry.get(this)
.selectFrom(SITES_TABLE_NAME, ServerModel.class)
.all(this::setModels);
}
@ -225,28 +257,21 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
if (requestCode == ADD_SITE_RQ) {
mAdapter.add(model);
mEmptyText.setVisibility(View.GONE);
Inquiry.get().insertInto(SITES_TABLE_NAME, ServerModel.class)
Inquiry.get(this).insertInto(SITES_TABLE_NAME, ServerModel.class)
.values(model)
.run(inserted -> {
AlarmUtil.setSiteChecks(MainActivity.this, model);
checkSite(MainActivity.this, model);
});
} else if (requestCode == VIEW_SITE_RQ) {
Inquiry.get()
.update(MainActivity.SITES_TABLE_NAME, ServerModel.class)
.where("_id = ?", model.id)
.values(model)
.run(changed -> {
mAdapter.update(model);
AlarmUtil.setSiteChecks(MainActivity.this, model);
checkSite(MainActivity.this, model);
});
mAdapter.update(model);
AlarmUtil.setSiteChecks(MainActivity.this, model);
checkSite(MainActivity.this, model);
}
}
}
public static void removeSite(final Context context, final ServerModel model, final Runnable onRemoved) {
Inquiry.init(context, DB_NAME, 1);
new MaterialDialog.Builder(context)
.title(R.string.remove_site)
.content(Html.fromHtml(context.getString(R.string.remove_site_prompt, model.name)))
@ -256,10 +281,15 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
AlarmUtil.cancelSiteChecks(context, model);
final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
nm.cancel(model.url, CheckService.NOTI_ID);
Inquiry.get()
.deleteFrom(SITES_TABLE_NAME, ServerModel.class)
//noinspection CheckResult
final Inquiry rinq = Inquiry.newInstance(context, DB_NAME)
.instanceName("remove_site")
.build(false);
//noinspection CheckResult
rinq.deleteFrom(SITES_TABLE_NAME, ServerModel.class)
.where("_id = ?", model.id)
.run();
rinq.destroyInstance();
if (onRemoved != null)
onRemoved.run();
}).show();

View file

@ -14,15 +14,18 @@ import android.util.Log;
import android.util.Patterns;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import com.afollestad.bridge.Bridge;
import com.afollestad.inquiry.Inquiry;
import com.afollestad.nocknock.R;
import com.afollestad.nocknock.api.ServerModel;
import com.afollestad.nocknock.api.ServerStatus;
import com.afollestad.nocknock.api.ValidationMode;
import com.afollestad.nocknock.services.CheckService;
import com.afollestad.nocknock.util.TimeUtil;
import com.afollestad.nocknock.views.StatusImageView;
@ -44,6 +47,7 @@ public class ViewSiteActivity extends AppCompatActivity implements View.OnClickL
private TextView textLastCheckResult;
private TextView textNextCheck;
private TextView textUrlWarning;
private Spinner responseValidationSpinner;
private ServerModel mModel;
@ -77,9 +81,11 @@ public class ViewSiteActivity extends AppCompatActivity implements View.OnClickL
checkIntervalSpinner = (Spinner) findViewById(R.id.checkIntervalSpinner);
textLastCheckResult = (TextView) findViewById(R.id.textLastCheckResult);
textNextCheck = (TextView) findViewById(R.id.textNextCheck);
responseValidationSpinner = (Spinner) findViewById(R.id.responseValidationMode);
ArrayAdapter<String> intervalOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner,
getResources().getStringArray(R.array.interval_options));
intervalOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
checkIntervalSpinner.setAdapter(intervalOptionsAdapter);
inputUrl.setOnFocusChangeListener((view, hasFocus) -> {
@ -99,6 +105,38 @@ public class ViewSiteActivity extends AppCompatActivity implements View.OnClickL
}
});
ArrayAdapter<String> validationOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner,
getResources().getStringArray(R.array.response_validation_options));
validationOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
responseValidationSpinner.setAdapter(validationOptionsAdapter);
responseValidationSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
final View searchTerm = findViewById(R.id.responseValidationSearchTerm);
final View javascript = findViewById(R.id.responseValidationScript);
final TextView modeDesc = (TextView) findViewById(R.id.validationModeDescription);
searchTerm.setVisibility(i == 1 ? View.VISIBLE : View.GONE);
javascript.setVisibility(i == 2 ? View.VISIBLE : View.GONE);
switch (i) {
case 0:
modeDesc.setText(R.string.validation_mode_status_desc);
break;
case 1:
modeDesc.setText(R.string.validation_mode_term_desc);
break;
case 2:
modeDesc.setText(R.string.validation_mode_javascript_desc);
break;
}
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
mModel = (ServerModel) getIntent().getSerializableExtra("model");
update();
@ -115,7 +153,7 @@ public class ViewSiteActivity extends AppCompatActivity implements View.OnClickL
}
}
@SuppressLint("SetTextI18n")
@SuppressLint({"SetTextI18n", "SwitchIntDef"})
private void update() {
final SimpleDateFormat df = new SimpleDateFormat("MMMM dd, hh:mm:ss a", Locale.getDefault());
@ -169,6 +207,16 @@ public class ViewSiteActivity extends AppCompatActivity implements View.OnClickL
}
}
responseValidationSpinner.setSelection(mModel.validationMode - 1);
switch (mModel.validationMode) {
case ValidationMode.TERM_SEARCH:
((TextView) findViewById(R.id.responseValidationSearchTerm)).setText(mModel.validationContent);
break;
case ValidationMode.JAVASCRIPT:
((TextView) findViewById(R.id.responseValidationScriptInput)).setText(mModel.validationContent);
break;
}
findViewById(R.id.doneBtn).setOnClickListener(this);
}
@ -195,26 +243,24 @@ public class ViewSiteActivity extends AppCompatActivity implements View.OnClickL
}
}
// Save button
@Override
public void onClick(View view) {
private void performSave(boolean withValidation) {
mModel.name = inputName.getText().toString().trim();
mModel.url = inputUrl.getText().toString().trim();
mModel.status = ServerStatus.WAITING;
if (mModel.name.isEmpty()) {
if (withValidation && mModel.name.isEmpty()) {
inputName.setError(getString(R.string.please_enter_name));
return;
} else {
inputName.setError(null);
}
if (mModel.url.isEmpty()) {
if (withValidation && mModel.url.isEmpty()) {
inputUrl.setError(getString(R.string.please_enter_url));
return;
} else {
inputUrl.setError(null);
if (!Patterns.WEB_URL.matcher(mModel.url).find()) {
if (withValidation && !Patterns.WEB_URL.matcher(mModel.url).find()) {
inputUrl.setError(getString(R.string.please_enter_valid_url));
return;
} else {
@ -245,6 +291,35 @@ public class ViewSiteActivity extends AppCompatActivity implements View.OnClickL
mModel.lastCheck = System.currentTimeMillis() - mModel.checkInterval;
switch (responseValidationSpinner.getSelectedItemPosition()) {
case 0:
mModel.validationMode = ValidationMode.STATUS_CODE;
mModel.validationContent = null;
break;
case 1:
mModel.validationMode = ValidationMode.TERM_SEARCH;
mModel.validationContent = ((EditText) findViewById(R.id.responseValidationSearchTerm)).getText().toString().trim();
break;
case 2:
mModel.validationMode = ValidationMode.JAVASCRIPT;
mModel.validationContent = ((EditText) findViewById(R.id.responseValidationScriptInput)).getText().toString().trim().replace("\n", " ");
break;
}
final Inquiry inq = Inquiry.newInstance(this, MainActivity.DB_NAME)
.build(false);
//noinspection CheckResult
inq.update(MainActivity.SITES_TABLE_NAME, ServerModel.class)
.where("_id = ?", mModel.id)
.values(mModel)
.run();
inq.destroyInstance();
}
// Save button
@Override
public void onClick(View view) {
performSave(true);
setResult(RESULT_OK, new Intent().putExtra("model", mModel));
finish();
}
@ -253,10 +328,11 @@ public class ViewSiteActivity extends AppCompatActivity implements View.OnClickL
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.refresh:
performSave(false);
MainActivity.checkSite(this, mModel);
return true;
case R.id.remove:
MainActivity.removeSite(this, mModel, () -> finish());
MainActivity.removeSite(this, mModel, this::finish);
return true;
}
return false;

View file

@ -0,0 +1,72 @@
package com.afollestad.nocknock.util;
import android.util.Log;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
/**
* @author Aidan Follestad (afollestad)
*/
public class JsUtil {
public static String exec(String code, String response) {
try {
final String func = String.format(
"function validate(response) { " +
"try { " +
"%s " +
"} catch(e) { " +
"return e; " +
"} " +
"}", code.replace("\n", " "));
// Every Rhino VM begins with the enter()
// This Context is not Android's Context
Context rhino = Context.enter();
// Turn off optimization to make Rhino Android compatible
rhino.setOptimizationLevel(-1);
try {
Scriptable scope = rhino.initStandardObjects();
// Note the forth argument is 1, which means the JavaScript source has
// been compressed to only one line using something like YUI
rhino.evaluateString(scope, func, "JavaScript", 1, null);
// Get the functionName defined in JavaScriptCode
Function jsFunction = (Function) scope.get("validate", scope);
// Call the function with params
Object jsResult = jsFunction.call(rhino, scope, scope, new Object[]{response});
// Parse the jsResult object to a String
String result = Context.toString(jsResult);
boolean success = result != null && result.equals("true");
String message = "The script returned a value other than true!";
if (!success && result != null && !result.equals("false")) {
if (result.equals("undefined")) {
message = "The script did not return or throw anything!";
} else {
message = result;
}
}
Log.d("JsUtil", "Evaluated to " + message + " (" + success + "): " + code);
return !success ? message : null;
} finally {
Context.exit();
}
} catch (EvaluatorException e) {
return e.getMessage();
}
}
private JsUtil() {
}
}

View file

@ -26,8 +26,7 @@
android:orientation="vertical"
android:paddingBottom="@dimen/content_inset"
android:paddingLeft="@dimen/content_inset"
android:paddingRight="@dimen/content_inset"
android:paddingTop="@dimen/content_inset_less">
android:paddingRight="@dimen/content_inset">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
@ -80,12 +79,17 @@
android:visibility="gone"
tools:text="Warning: this app checks for server availability with HTTP requests. It's recommended that you use an HTTP URL." />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset"
android:background="@color/dividerColorDark" />
<TextView
android:id="@+id/checkIntervalLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/list_text_spacing"
android:layout_marginTop="@dimen/content_inset_more"
android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif"
android:text="@string/check_interval"
android:textColor="?colorAccent"
@ -121,6 +125,104 @@
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset"
android:background="@color/dividerColorDark" />
<TextView
android:id="@+id/responseValidation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif"
android:text="@string/response_validation_mode"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size" />
<Spinner
android:id="@+id/responseValidationMode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/responseValidationSearchTerm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset_less"
android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:layout_marginTop="-4dp"
android:fontFamily="sans-serif-light"
android:hint="@string/search_term"
android:textSize="@dimen/body_font_size"
android:visibility="gone" />
<HorizontalScrollView
android:id="@+id/responseValidationScript"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset"
android:layout_marginTop="@dimen/content_inset_half"
android:background="@color/colorPrimaryDark"
android:elevation="@dimen/fab_elevation"
android:padding="@dimen/content_inset_half"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="serif-monospace"
android:lineSpacingMultiplier="1.4"
android:singleLine="true"
android:text="@string/function_declaration"
android:textSize="@dimen/code_font_size" />
<EditText
android:id="@+id/responseValidationScriptInput"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:fontFamily="serif-monospace"
android:gravity="top"
android:hint="@string/default_js"
android:inputType="textMultiLine"
android:lineSpacingMultiplier="1.4"
android:paddingBottom="@dimen/content_inset_less"
android:paddingEnd="@dimen/content_inset_more"
android:paddingStart="@dimen/content_inset_more"
android:paddingTop="@dimen/content_inset_less"
android:scrollHorizontally="true"
android:textSize="@dimen/code_font_size"
tools:ignore="LabelFor,RtlSymmetry" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="serif-monospace"
android:text="@string/function_end"
android:textSize="@dimen/code_font_size" />
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/validationModeDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset_half"
android:fontFamily="sans-serif-light"
android:lineSpacingMultiplier="1.2"
android:text="@string/validation_mode_status_desc"
android:textSize="@dimen/body_font_size" />
<Button
android:id="@+id/doneBtn"
android:layout_width="match_parent"

View file

@ -27,7 +27,7 @@
android:paddingBottom="@dimen/content_inset"
android:paddingLeft="@dimen/content_inset"
android:paddingRight="@dimen/content_inset"
android:paddingTop="@dimen/content_inset_less">
android:paddingTop="@dimen/content_inset_half">
<LinearLayout
android:layout_width="match_parent"
@ -95,15 +95,14 @@
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset_more"
android:background="#37474F" />
android:layout_marginTop="@dimen/content_inset_less"
android:background="@color/dividerColorDark" />
<TextView
android:id="@+id/checkIntervalLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/list_text_spacing"
android:layout_marginTop="@dimen/content_inset_more"
android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif"
android:text="@string/check_interval"
android:textColor="?colorAccent"
@ -139,10 +138,114 @@
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset_less"
android:background="@color/dividerColorDark" />
<TextView
android:id="@+id/responseValidation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif"
android:text="@string/response_validation_mode"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size" />
<Spinner
android:id="@+id/responseValidationMode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/responseValidationSearchTerm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset_less"
android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:layout_marginTop="-4dp"
android:fontFamily="sans-serif-light"
android:hint="@string/search_term"
android:textSize="@dimen/body_font_size"
android:visibility="gone" />
<HorizontalScrollView
android:id="@+id/responseValidationScript"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset"
android:layout_marginTop="@dimen/content_inset_half"
android:background="@color/colorPrimaryDark"
android:elevation="@dimen/fab_elevation"
android:padding="@dimen/content_inset_half"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="serif-monospace"
android:lineSpacingMultiplier="1.4"
android:singleLine="true"
android:text="@string/function_declaration"
android:textSize="@dimen/code_font_size" />
<EditText
android:id="@+id/responseValidationScriptInput"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:fontFamily="serif-monospace"
android:gravity="top"
android:hint="@string/default_js"
android:inputType="textMultiLine"
android:lineSpacingMultiplier="1.4"
android:paddingBottom="@dimen/content_inset_less"
android:paddingEnd="@dimen/content_inset_more"
android:paddingStart="@dimen/content_inset_more"
android:paddingTop="@dimen/content_inset_less"
android:scrollHorizontally="true"
android:textSize="@dimen/code_font_size"
tools:ignore="LabelFor,RtlSymmetry" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="serif-monospace"
android:text="@string/function_end"
android:textSize="@dimen/code_font_size" />
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/validationModeDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset_half"
android:fontFamily="sans-serif-light"
android:lineSpacingMultiplier="1.2"
android:text="@string/validation_mode_status_desc"
android:textSize="@dimen/body_font_size" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset"
android:background="@color/dividerColorDark" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset_more"
android:layout_marginTop="@dimen/content_inset"
android:text="@string/last_check_result"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size" />
@ -160,7 +263,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset_more"
android:layout_marginTop="@dimen/content_inset"
android:text="@string/next_check"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size" />
@ -181,7 +284,7 @@
android:layout_height="wrap_content"
android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:layout_marginTop="@dimen/content_inset_double"
android:layout_marginTop="@dimen/content_inset_more"
android:text="@string/save"
android:textColor="#fff" />

View file

@ -4,7 +4,5 @@
android:layout_height="@dimen/button_height"
android:fontFamily="sans-serif-light"
android:gravity="center_vertical|start"
android:paddingLeft="@dimen/content_inset"
android:paddingRight="@dimen/content_inset"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/body_font_size" />

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/button_height"
android:fontFamily="sans-serif-light"
android:gravity="center_vertical|start"
android:paddingLeft="@dimen/content_inset"
android:paddingRight="@dimen/content_inset"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/body_font_size" />

View file

@ -11,4 +11,6 @@
<color name="md_yellow">#FDD835</color>
<color name="md_green">#43A047</color>
<color name="dividerColorDark">#37474F</color>
</resources>

View file

@ -18,5 +18,6 @@
<dimen name="fab_elevation_pressed">8dp</dimen>
<dimen name="button_height">52dp</dimen>
<dimen name="tutorial_button_width">300dp</dimen>
<dimen name="code_font_size">14sp</dimen>
</resources>

View file

@ -18,6 +18,7 @@
<a href=\'https://github.com/afollestad\'>GitHub</a>&nbsp;&nbsp;
<a href=\'https://www.linkedin.com/in/afollestad\'>LinkedIn</a>
<br/><i>Nock Nock is open source! Check out the <a href=\'https://github.com/afollestad/nock-nock\'>GitHub page</a>!</i>
<br/>Icon by <a href=\'https://plus.google.com/+KevinAguilarC\'>Kevin Aguilar</a> of <b>221 Pixels</b>.
]]></string>
<string name="dismiss">Dismiss</string>
<string name="add_site">Add Site</string>
@ -49,6 +50,15 @@
<string name="warning_http_url">
Warning: this app checks for server availability with HTTP requests. It\'s recommended that you use an HTTP URL.
</string>
<string name="default_js">var responseObj = JSON.parse(response);\nreturn responseObj.success === true;</string>
<string name="function_declaration">function validate(response) {</string>
<string name="function_end">}</string>
<string name="response_validation_mode">Response Validation Mode</string>
<string name="search_term">Search term…</string>
<string name="validation_mode_status_desc">The HTTP status code is checked. If it\'s a successful status code, the site passes the check.</string>
<string name="validation_mode_term_desc">The status code check is done first. If it\'s successful, the response body is checked. If it contains your search term, the site passes the check.</string>
<string name="validation_mode_javascript_desc">The status code check is done first. If it\'s successful, the response body is passed to the JavaScript function above. If the function returns true, the site passes the check. Throw an exception to pass custom error messages to Nock Nock.</string>
<string-array name="interval_options">
<item>Minute(s)</item>
@ -62,4 +72,10 @@
<item>@string/remove_site</item>
</string-array>
<string-array name="response_validation_options">
<item>Status Code</item>
<item>Search Term</item>
<item>JavaScript Evaluation</item>
</string-array>
</resources>

View file

@ -15,6 +15,7 @@ buildscript {
allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
}