{"action":"created","issue":{"url":"https://api.github.com/repos/evancz/elm-http/issues/47","repository_url":"https://api.github.com/repos/evancz/elm-http","labels_url":"https://api.github.com/repos/evancz/elm-http/issues/47/labels{/name}","comments_url":"https://api.github.com/repos/evancz/elm-http/issues/47/comments","events_url":"https://api.github.com/repos/evancz/elm-http/issues/47/events","html_url":"https://github.com/evancz/elm-http/issues/47","id":173330737,"number":47,"title":"Proposal: add JSONP mechanism","user":{"login":"tolgap","id":1763486,"avatar_url":"https://avatars.githubusercontent.com/u/1763486?v=3","gravatar_id":"","url":"https://api.github.com/users/tolgap","html_url":"https://github.com/tolgap","followers_url":"https://api.github.com/users/tolgap/followers","following_url":"https://api.github.com/users/tolgap/following{/other_user}","gists_url":"https://api.github.com/users/tolgap/gists{/gist_id}","starred_url":"https://api.github.com/users/tolgap/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tolgap/subscriptions","organizations_url":"https://api.github.com/users/tolgap/orgs","repos_url":"https://api.github.com/users/tolgap/repos","events_url":"https://api.github.com/users/tolgap/events{/privacy}","received_events_url":"https://api.github.com/users/tolgap/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2016-08-25T22:56:46Z","updated_at":"2016-08-25T22:56:48Z","closed_at":null,"body":"I know, JSONP is basically a hack to circumvent dealing with CORS inside browsers. It executes whatever Javascript is sent back, so it is also unsafe. *But* there still are many public APIs that are only consumable using JSONP (or you're forced to build a proxy server). It doesn't even necessarily have to be a public API. Simply if you're stuck with an API that you have to consume that has strict CORS headers, it most likely will support JSONP.\r\n\r\n## Proposal\r\n\r\nAdd two functions to `elm-http` for dealing with JSONP so we can leverage the usage of `Tasks`. One is a `Native` function, the other is an Elm function that accepts a decoder.\r\n\r\n```elm\r\njsonp : String -> String -> String -> Task x String\r\njsonp =\r\n    Native.Send.jsonp\r\n\r\n\r\njsonpGet : Json.Decoder value -> String -> String -> Task Error value\r\njsonpGet decoder url callbackParam =\r\n    let\r\n        decode s =\r\n            Json.decodeString decoder s\r\n                |> Task.fromResult\r\n                |> Task.mapError Http.UnexpectedPayload\r\n    in\r\n        randomCallbackName\r\n            `Task.andThen` jsonp url callbackParam\r\n            `Task.andThen` decode\r\n```\r\n\r\nAs you can see, there is no `err` handling for JSONP. This is because there is no way of knowing whether it will fail. You load the contents into a HTML `<script>` tag and let your browser take the wheel. I am sure that 99% of the time, JSONP will be used to parse JSON. So if there is any wrong output being generated in the `<script>` tag, the decode should fail with `Http.UnexpectedPayload`.\r\n\r\nJSONP circumvents CORS by loading javascript wrapped in a callback into a `<script>` tag. `<script>` tags can load any URL (barring HTTP/HTTPS discrepancies). The important thing is the callback. There could be multiple JSONP requests going on parallel, so we need to generate a global callback function that is unique for every JSONP `Task`. We use the `Random` module for this. The function `randomCallbackName` could look like this:\r\n\r\n```elm\r\nimport Random\r\nimport Time\r\n\r\nrandomCallbackName : Task x String\r\nrandomCallbackName =\r\n    let\r\n        generator =\r\n            Random.int 100 Random.maxInt\r\n    in\r\n        Time.now\r\n            |> Task.map (Random.step generator Random.initialSeed)\r\n            |> Task.map (fst >> toString >> (++) \"callback\")\r\n```\r\n\r\nWe're left with a random callback name that starts with `\"callback\"` and has a random number added to it. This random callback name will be used in the JS side. This is what the JS side could look like.\r\n\r\n```javascript\r\nvar _evancz$elm_http$Native_Http = function() {\r\n\r\n  function jsonp(url, callbackParam, callbackName)\r\n  {\r\n    return _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {\r\n      window[callbackName] = function(content)\r\n      {\r\n        callback(_elm_lang$core$Native_Scheduler.succeed(JSON.stringify(content)));\r\n      };\r\n\r\n      var scriptTag = createScript(url, callbackParam, callbackName);\r\n      document.head.appendChild(scriptTag);\r\n      document.head.removeChild(scriptTag);\r\n    });\r\n  }\r\n\r\n\r\n  function createScript(url, callbackParam, callbackName)\r\n  {\r\n    var s = document.createElement('script');\r\n    s.type = 'text/javascript';\r\n\r\n    if (url.indexOf('?') >= 0)\r\n    {\r\n      s.src = url + '&' + callbackParam + '=' + callbackName;\r\n    }\r\n    else {\r\n      s.src = url + '?' + callbackParam + '=' + callbackName;\r\n    }\r\n\r\n    return s;\r\n  }\r\n\r\n\r\n  return {\r\n    jsonp: F3(jsonp)\r\n  };\r\n\r\n}();\r\n```\r\n\r\nWe create a global function using `window[callbackName]`. This function receives the content from the JSONP call. To avoid doing anything unsafe, we `JSON.stringify` the callback contents and pass it to the Scheduler with `succeed`. The script tag gets created with the HTTP call URL, and it is appended and removed from the document `<head>`.\r\n\r\n## Discussion points\r\n\r\n1. JSONP is a hack to circumvent CORS. Do we want to support something like this in Elm?\r\n2. It loads any Javascript returned from the server into a script tag, so it could be unsafe. In this implementation, I use `JSON.stringify` on the callback contents.\r\n3. This is a rough mockup. I'm already using this code in a project of mine, and it's working great. Maybe some naming could be changed?"},"comment":{"url":"https://api.github.com/repos/evancz/elm-http/issues/comments/242571051","html_url":"https://github.com/evancz/elm-http/issues/47#issuecomment-242571051","issue_url":"https://api.github.com/repos/evancz/elm-http/issues/47","id":242571051,"user":{"login":"process-bot","id":16453249,"avatar_url":"https://avatars.githubusercontent.com/u/16453249?v=3","gravatar_id":"","url":"https://api.github.com/users/process-bot","html_url":"https://github.com/process-bot","followers_url":"https://api.github.com/users/process-bot/followers","following_url":"https://api.github.com/users/process-bot/following{/other_user}","gists_url":"https://api.github.com/users/process-bot/gists{/gist_id}","starred_url":"https://api.github.com/users/process-bot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/process-bot/subscriptions","organizations_url":"https://api.github.com/users/process-bot/orgs","repos_url":"https://api.github.com/users/process-bot/repos","events_url":"https://api.github.com/users/process-bot/events{/privacy}","received_events_url":"https://api.github.com/users/process-bot/received_events","type":"User","site_admin":false},"created_at":"2016-08-25T22:56:48Z","updated_at":"2016-08-25T22:56:48Z","body":"Thanks for the issue! Make sure it satisfies [this checklist][checklist]. My human colleagues will appreciate it!\n\nHere is [what to expect next][expectations], and if anyone wants to comment, keep [these things][participation] in mind.\n\n[checklist]: https://github.com/process-bot/contribution-checklist/blob/master/issues.md\n[expectations]: https://github.com/process-bot/contribution-checklist/blob/master/expectations.md\n[participation]: https://github.com/process-bot/contribution-checklist/blob/master/participation.md"}}
{"actor":{"display_login":"process-bot"}}