I'm using the "Content Blocker Extension" in my project, and have been informed that I have to call SFContentBlockerManager.reloadContentBlockerWithIdentifier() in my main app to reload the data in blockerList.json.
But I don't really know HOW I should call it. Any ideas?
When there is need (like when the user changes something) to reload the data from blockerList.json you call:
SFContentBlockerManager.reloadContentBlocker(withIdentifier: "your-blocker-id", completionHandler: { error in
if let error = error {
// do something here when an error is thrown
print(error.localizedDescription)
}
})
Related
obviously I am new to RxSwift and though I consumed a lot of documentations and speeches, I think I am missing some fundamental concepts.
In my app I have a RESTful web service to load various resources but the base url of the web service is unknown at build/start time. Instead I have a "URL resolver" web service which I can call with my apps bundle, version and possible environment ("production", "debug" or any custom string entered in the apps debug settings) to obtain the base url I then use for the actual service.
My thinking was that I would create 2 services, one for the URL resolver and one for the actual web service which gives me my resources. The URL resolver would have a Variable and a Observable. I use the variable to signal the need to refresh the base url via a web service call to the URL resolver. I do this by observing the variable and filter only for true values. A function in the service class set the variables value to true (initially it is false) and inside an observer of the filtered variable, I make the web service call in another Observable (this example uses a dummy JSON web service):
import Foundation
import RxSwift
import Alamofire
struct BaseURL: Codable {
let title: String
}
struct URLService {
private static var counter = 0
private static let urlVariable: Variable<Bool> = Variable(false)
static let urlObservable: Observable<BaseURL> = urlVariable.asObservable()
.filter { counter += 1; return $0 }
.flatMap { _ in
return Observable.create { observer in
let url = counter < 5 ? "https://jsonplaceholder.typicode.com/posts" : ""
let requestReference = Alamofire.request(url).responseJSON { response in
do {
let items = try JSONDecoder().decode([BaseURL].self, from: response.data!)
observer.onNext(items[0])
} catch {
observer.onError(error)
}
}
return Disposables.create() {
requestReference.cancel()
}
}
}
static func getBaseUrl() {
urlVariable.value = true;
}
static func reset() {
counter = 0;
}
}
Now the problem is that sometimes it can happen that a web service call fails and I would need to show the error to the user so a retry can be made. I thought that the onError was useful for this but it seems to kills all the subscribers forever.
I could put the subscribing in its own function and inside the error handler of the Observer, I could show a alert and then call the subscribe function again like so:
func subscribe() {
URLService.urlObservable.subscribe(onNext: { (baseURL) in
let alert = UIAlertController(title: "Success in Web Service", message: "Base URL is \(baseURL.title)", preferredStyle: .alert)
let actionYes = UIAlertAction(title: "Try again!", style: .default, handler: { action in
URLService.getBaseUrl()
})
alert.addAction(actionYes)
DispatchQueue.main.async {
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}, onError: { error in
let alert = UIAlertController(title: "Error in Web Service", message: "Something went wrong: \(error.localizedDescription)", preferredStyle: .alert)
let actionYes = UIAlertAction(title: "Yes", style: .default, handler: { action in
URLService.reset()
self.subscribe()
})
alert.addAction(actionYes)
DispatchQueue.main.async {
VesselService.reset()
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}).disposed(by: disposeBag)
}
Then in my AppDelegate I would call
subscribe()
URLService.getBaseUrl()
The problem is that all other observers get killed on an error as well but since the the only other observer on the URLService.urlObservable is my other web service class, I guess I could implement the same style subscribe function in there as well.
I read that some people suggest to return a Result enum which has 2 cases: the actual result (.success(result: T)) or an error (.error(error: Error)).
So what is the better way of handling errors web service errors in Rx? I cant wrap my head around this problem and I'm trying for 2 days to understand it. Any ideas or suggestions?
Update
It just came to my mind that I could ignore errors from the web service calls completely and instead post any error to a global "error" variable which my app delegate could observe to show alerts. The "error" could reference the function which initially caused it so a retry could be made. I'm still confused and not sure what I should do. :/
Update 2
I think I might found a working solution. As I am still a beginner to Rx and RxSwift, I'm happy to take improvement suggestions. As I was writing the actual code, I splitted my call chain in two parts:
The part where I make the web service calls
The part where I click a button and process the result of the web service, whether it is an error or a success
In the part where I click the button and process the result, I use catchError and retry as suggested in the comments. The code looks like this:
let userObservable = URLService
.getBaseUrl(environment: UserDefaults.standard.environment) //Get base url from web service 1
.flatMap({ [unowned self] baseURL -> Observable<User> in
UserService.getUser(baseURL: baseURL,
email: self.usernameTextField.text!,
password: self.passwordTextField.text!) //Get user from web service 2 using the base url from webservice 1
})
signInButton
.rx
.tap
.throttle(0.5, scheduler: MainScheduler.instance)
.flatMap({ [unowned self] () -> Observable<()> in
Observable.create { observable in
let hud = MBProgressHUD.present(withTitle: "Signing in...");
self.hud = hud
observable.onNext(())
return Disposables.create {
hud?.dismiss()
}
}
})
.flatMap({ () -> Observable<User> in
return userObservable
})
.catchError({ [unowned self] error -> Observable<User> in
self.hud?.dismiss()
self.handleError(error)
return userObservable
})
.retry()
.subscribe(onNext: { [unowned self] (user) in
UserDefaults.standard.accessToken = user.accessToken
UserDefaults.standard.tokenType = user.tokenType
self.hud?.dismiss()
})
.disposed(by: disposeBag)
The trick was to move the call to the two web services out of the cain into their own variable so I can re-call it at any time. When I now return the "userObservable" and an error happens during the web service call, I can show the error in the catchError and return the same "userObservable" for the next retry.
At the moment this only properly handles errors when they occur in the web service call chain so I think I should make the button tap a driver.
Okay so for everyone who comes here, you probably have a lack of understanding or a misconception of how the Rx world is supposed to work. I still find it sometimes confusing but I found a way better solution than what I posted in my original question.
In Rx, a error "kills" or rather completes all observers in the chain and that is actually a good thing. If there are expected errors like API error in web service calls, you should either try to handle them where they occur or treat them like expected values.
For example, your observer could return a optional type and subscribers could filter for the existence of values. If an error in the API call occurs, return nil. Other "error handlers" could filter for nil values to display error messages to the user.
Also viable is to return a Result enum with two cases: .success(value: T) and .error(error: Error). You treat the error as a acceptable result and the observer is responsible for checking if it should display a error message or the success result value.
Yet another option, which surely is not the best as well but works it to simply nest the call which you expect to fail inside the subscriber of the call which must not be affected. In my case that is a button tap which causes a call to a web service.
The "Update 2" of my original post would become:
signInButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(onNext: { [unowned self] () in
log.debug("Trying to sign user in. Presenting HUD")
self.hud = MBProgressHUD.present(withTitle: "Signing in...");
self.viewModel.signIn()
.subscribe(onNext: { [unowned self] user in
log.debug("User signed in successfully. Dismissing HUD")
self.hud?.dismiss()
}, onError: { [unowned self] error in
log.error("Failed to sign user in. Dismissing HUD and presenting error: \(error)")
self.hud?.dismiss()
self.handleError(error)
}).disposed(by: self.disposeBag)
}).disposed(by: self.disposeBag)
The MVVM view model makes the calls to the web serivces like so:
func signIn() -> Observable<User> {
log.debug("HUD presented. Loading BaseURL to sign in User")
return URLService.getBaseUrl(environment: UserDefaults.standard.environment)
.flatMap { [unowned self] baseURL -> Observable<BaseURL> in
log.debug("BaseURL loaded. Checking if special env is used.")
if let specialEnv = baseURL.users[self.username.value] {
log.debug("Special env is used. Reloading BaseURL")
UserDefaults.standard.environment = specialEnv
return URLService.getBaseUrl(environment: specialEnv)
} else {
log.debug("Current env is used. Returning BaseURL")
return Observable.just(baseURL)
}
}
.flatMap { [unowned self] baseURL -> Observable<User> in
log.debug("BaseURL to use is: \(baseURL.url). Now signing in User.")
let getUser = UserService.getUser(baseURL: baseURL.url, email: self.username.value, password: self.password.value).share()
getUser.subscribe(onError: { error in
UserDefaults.standard.environment = nil
}).disposed(by: self.disposeBag)
return getUser
}
.map{ user in
UserDefaults.standard.accessToken = user.accessToken
UserDefaults.standard.tokenType = user.tokenType
return user
}
}
First I was thinking to only call the view models signIn() function when pressing the button but since there should be no UI code in the view model, I figured that presenting and dismissing the HUD is the responsibility of the ViewController.
I think this design is now pretty solid. The button observer never completes and can continue to send events forever. Earlier, if there was a second error, it might happen that the button observer died and my logs showed that the userObservable was executed twice, which must also not be happen.
I just wonder if there is a better way then nesting the subscribers.
Important Fact
I forgot to mention an important factor in the question. I am running this in a TestCase. I think this issue has something to do with the TestCase not awaiting for async completionHandler to return
Migrated out from Alamofire to SwiftHTTP, since I found this much easier.
On SwiftHHTP there is no way to know what URL got generated, what error it returned. For example, I tried to see the opt.debugDescription variable, it returned something cryptic like description String "<SwiftHTTP.HTTP: 0x60000007e540>"
Steps I have followed
I have set YES to Allow Arbitrary Loads.
Safari on the iPhone Simulator responds with the correct JSON if I paste fullurl ->http://120.0.0.1:8080/myapi/Driver/getDriver?driver=2243&domain=4345&key=asdfasdf. Even catalina.out on the tomcat server running on my mac responds with a debug message.
But when I run this in a test case under Xcode the below code prints none of debug print's.
--1->, --2-->, --3-->, nothing got printed.
Debugger breakpoints also dont stop here.
CODE
var getData = [String:String]()
getData = ["domain": "4345",
"driver" : "2343",
"key" : "asdfasdf"]
var urlComponents = URLComponents(string: fullURL)!
var queryItems = [URLQueryItem]()
queryItems = self.getData.map{ URLQueryItem(name : $0.0, value : $0.1) }
urlComponents.queryItems = queryItems
print("fullurl ->"+(urlComponents.url)!.absoluteString)
do {
let opt = try HTTP.GET((urlComponents.url)!.absoluteString)
opt.start { response in
if let err = response.error {
print("--1-> error: \(err.localizedDescription)")
return //also notify app of failure as needed
}
print("--2--> opt finished: \(response.description)")
self.responseData = response
}
} catch let error {
print("--3--> got an error creating the request: \(error)")
}
EDIT
Even after changing the code to https or http://www.google.com, same result.
let testComponents = URLComponents(string: "https://www.google.com")!
URLSession.shared.dataTask(with: (testComponents.url)!, completionHandler: {
(data, response, error) in
if(error != nil){
print("..1>..")
}else{
do{
print ("..2>.." )
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String : AnyObject]
self.responseData = json
}catch let error as NSError{
print("..3>..")
}
}
}).resume()
EDIT 1
Tried from here #Vivek's answer.
callWebService(url: (urlComponents.url)!.absoluteString)
.
.
func callWebService(url : String) {
.
.
let callURL = URL.init(string: url)
Nothing got printed again, Error / JSON, nothing.
Yes, Unit Tests don't wait by default for the completionHandler to be called. If you call asynchronous functions in tests, you don't need to change the function's code, but the behavior of the test.
The solution: XCTestExpectation
In your test-class (the subclass of XCTest), add this property:
var expectation: XCTestExpectation?
A test-function for an asynchronous request could basically look like this:
func testRequest() {
expectation = expectation(description: "Fetched sites") //1
//2
some.asyncStuffWithCompletionHandler() {
someData in
if someData == nil {
XCTestFail("no data") //3
return
}
//looks like the request was successful
expectation?.fulfill() //4
}
//5
waitForExpectations(timeout: 30, handler: nil)
}
Explanation
This defines, what you expect the tested code to do. But actually, it's not important, what you add as description. It's just an information for you, when running the test
This is the function with a completionHandler, you are calling
If you want to let the test fail within the completionHanlder, call XCTestFail()
If everything in the completionHandler worked as expected, fulfill the expectation by calling expectation?.fulfill.
Here comes the important part: This part of the code will be executed before the completionHandler! If this would be the end of the function, the test would be stopped. That's why we tell the test to wait until the expectations are fulfilled (or a certain amount of time passed)
There is an interesting blog post about Unit Tests. (see the section "XCTestExpectation") It's written in an old Swift syntax, but the concept is the same.
I've read the Feathers book, so I know that to create an error response I simply instantiate the appropriate feathers-errors class:
import {BadRequest} from 'feathers-errors';
const errorResponse = new BadRequest(`foo`, `bar`);
However, I'm having difficulty returning that error response to the user. Even when I create an endpoint that does nothing but return an error response ...
class SomeService {
create(data) {
return new BadRequest(`foo`, `bar`);
}
}
it doesn't work: instead of getting an error response, I get no response, and inside the Chrome debugger I can see that the response is pending (until it eventually times out and becomes an ERR_EMPTY_RESPONSE).
I tried reading about Express error handling, and in the examples I saw people used next to wrap the the response. However, next comes from the error handler, and I'm not sure where in my Feathers code I can get that next function.
If anyone could help explain (using next or not) how I can return a complete, not pending, error response, I would greatly appreciate it.
Your services have to return a promise. If your code is not asynchronous you can turn it into a promise with Promise.resolve and Promise.reject:
class SomeService {
create(data) {
return Promise.reject(new BadRequest(`foo`, `bar`));
}
}
Also make sure you registered the Express error handler to get nicely formatted errors:
const errorHandler = require('feathers-errors/handler');
// Last in the chain
app.use(errorHandler);
There is also more information in the error handling chapter.
I've been trying to retreive my json data for my iOS App. I tried many different sollutions but none of these worked properly for me. So this was the code I was using to read the json from the url and convert it.
let url = NSURL(string: "http://www.blind3d.byethost7.com/service.php")!
func load() {
do {
let request = NSURLRequest(URL: url)
let data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: nil)
self.handleData(data)
}
catch let error as NSError {
print("wieso dont you do siss : \(NSURLRequest(URL: url))")
self.handleError(error)
}
}
func handleError(error : NSError?) {
print("wieso dont you do siss : \(NSURLRequest(URL: url))")
NSLog("%#", "Error with loading from \(url): \(error)")
}
func handleData(data : NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments)
handleJSON(json)
}
catch let error as NSError {
handleError(error)
}
}
but somehow this isn't running properly. I am always getting this error when I am executing this method: NSJSONSerialization
Error with loading from http://www.blind3d.byethost7.com/service.php: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})
The json data I wanted to use for my app is here
Thank you for your help guys
The problem occurs because there it no actual JSON in the data variable. I tried your web service, and this is what you get returned in the data, along with all the other error html tags:
"This site requires Javascript to work, please enable Javascript in your
browser or use a browser with Javascript support"
The full response:
<html><body><script type="text/javascript" src="/aes.js" ></script><script>function toNumbers(d){var e=[];d.replace(/(..)/g,function(d){e.push(parseInt(d,16))});return e}function toHex(){for(var d=[],d=1==arguments.length&&arguments[0].constructor==Array?arguments[0]:arguments,e="",f=0;f<d.length;f++)e+=(16>d[f]?"0":"")+d[f].toString(16);return e.toLowerCase()}var a=toNumbers("f655ba9d09a112d4968c63579db590b4"),b=toNumbers("98344c2eee86c3994890592585b49f80"),c=toNumbers("26049265c821fd7227c09955cbb61ebc");document.cookie="__test="+toHex(slowAES.decrypt(c,2,a,b))+"; expires=Thu, 31-Dec-37 23:55:55 GMT; path=/";location.href="http://www.blind3d.byethost7.com/service.php?ckattempt=1";</script><noscript>This site requires Javascript to work, please enable Javascript in your browser or use a browser with Javascript support</noscript></body></html>
This seems to happen, because there is some Javascript injected in the webpage you are trying to parse from, probably for statistics, or some other unknown reasons.
For checking by yourself, print your data - print(data), before calling self.handleData(data)
Try removing \r\n or escape them with '\' like
\\r\\n
and you are good to go. BTW, using json is painful in swift like this, SwiftyJSON is a necessary library if you deal with json frequently.
This is the result of installed "testCookie-nginx-module"
It's supposed to prevent DDOS attacks on your hosting
When you visit your site for the first time, it sends you this JS code, which your browser is supposed to process and set a special cookie (its name it _test)
Only with this cookie attached to your IP your browser can see the original content (your content: html php json etc.)
Seems the only way for you - is to process this JS (with AES, HEX and other JS functions, get the right _test cookie and send another request with this cookie)
So i am making some ajax post and it seems to work fine on the localhost, but when I publish it to ec2 server on amazon, I get Uncaught SyntaxError: Unexpected token B. Which seems to point to JSON parsing failure. Exact same database, same browser, and same methods being called. Why would it work on local and not on the server.
$.ajax({
url: '#Url.Action("Action")',
type: "POST",
data: ko.toJSON(viewModel),
dataType: "json",
contentType: "application/json; charset:utf-8",
success: function (result) {
},
error: function (xhr, textStatus, errorThrown) {
var errorData = $.parseJSON(xhr.responseText);
var errorMessages = [];
for (var key in errorData)
{
errorMessages.push(errorData[key]);
}
toastr.error(errorMessages.join("<br />"), 'Uh oh');
}
});
Here is the basic layout on the server side:
[HttpPost]
public JsonResult Action(ViewModel model)
{
try
{
Response.StatusCode = (int)HttpStatusCode.OK;
return Json("Successfull");
}
catch (Exception ex)
{
logger.Log(LogLevel.Error, string.Format("{0} \n {1}", ex.Message, ex.StackTrace));
Response.StatusCode = (int)HttpStatusCode.BadRequest;
List<string> errors = new List<string>();
errors.Add(ex.Message);
return Json(errors);
}
}
Within the try statement, I do a couple of queries to the database and post some calculations on Authorize.Net (https://api.authorize.net/soap/v1/Service.asmx)
If there are any error with Authorize.net web service calls then I return errors like this:
if (profile.resultCode == MessageTypeEnum.Error)
{
logger.Log(LogLevel.Error, string.Join(",", profile.messages.Select(x => x.text)));
Response.StatusCode = (int)HttpStatusCode.BadRequest;
List<string> errors = new List<string>();
profile.messages.ToList().ForEach(x => errors.Add(x.text));
db.SaveChanges();
return Json(errors);
}
This error that I am logging:
A public action method 'AddPromoCode' was not found on controller 'Flazingo.Controllers.PositionController'. at
System.Web.Mvc.Controller.HandleUnknownAction(String actionName) at
System.Web.Mvc.Controller.ExecuteCore() at
System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) at
System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.b__5() at
System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass1.b__0() at
System.Web.Mvc.MvcHandler.<>c__DisplayClasse.b__d() at
System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean&
completedSynchronously)
You have another post at can't find action only on live server, works fine in local server, so I'm guessing that this post is specifically related to the javascript pieces, not the server-side pieces.
It sounds like something bad happens on the server, the server sends back some type of error, and the your error handler (in javascript) dies when trying to handle that response.
I get Uncaught SyntaxError: Unexpected token B. Which seems to point
to JSON parsing failure.
That sounds quite reasonable. Let's look at the code:
.ajax({
...
error: function (xhr, textStatus, errorThrown) {
var errorData = $.parseJSON(xhr.responseText);
var errorMessages = [];
...
},
...
});
I would highly recommend taking a look at what xhr.responseText is. My guess it that it does not contain valid JSON, so the parseJSON method throws the 'Unexpected token B' error.
To look at this value, you could put console.log(xhr.responseText); or you could use a tool like the javascript debugger in your web browser or fiddler to see what is there.
My guess is that the server is sending back a string with something like There was an error on the server instead of JSON like you are expecting. I see that you have error handling built in - my guess is that there is an error within your error handling, and there is nothing to catch it. I would recommend doing debugging on the server side to see if there is an error somewhere that you are not expecting.
Perhaps profile.messages is something that can only be enumerated once, and when you try to do it again it throws an error. Or maybe DB.SaveChanges is throwing an error for some reason. Either of these would result in the logged message that you see with the behavior you see on the client side.
You are attempting to return a 400 response (Bad Request) with your own custom response content.
I think that IIS by default doesn't allow you to do this, and as CodeThug mentioned, may be replacing your custom JSON content with a server message.
But it appears that you can override this behaviour:
http://develoq.net/2011/returning-a-body-content-with-400-http-status-code/
<system.webServer>
<httpErrors existingResponse="PassThrough"></httpErrors>
</system.webServer>
I have received similar mysterious errors in the past when using ASP.NET script bundling on knockout and bootstrap, especially when including the already-minified versions in a bundle.
If you are running in DEBUG mode on localhost, then ASP.NET will not be minifying the javascript libraries. However, once you deploy, you are presumably no longer in DEBUG mode and now minifying/bundling the scripts. Sometimes the bundling/minification of these scripts can result in syntax errors similar to the one you posted.
If so, you may be able to load knockout from a CDN to avoid the need for bundling.
It seems JSON sending as the response from the server is badly generated
ex: if a value in the database is hi "my" friends
JSON file will be generated as text:"hi "my" friends"
so value for property text is badly generated.
double check values in production/development server for such values.
best practice is replace quotes with escape character
ex: text:"hi \"my\" friends"