-
Notifications
You must be signed in to change notification settings - Fork 27.3k
feat(http): add support for JSONP requests #2905
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| library angular2.src.http.backends.browser_jsonp; | ||
| import 'package:angular2/di.dart'; | ||
| import 'dart:html' show document; | ||
| import 'dart:js' show context, JsObject, JsArray; | ||
|
|
||
| int _nextRequestId = 0; | ||
| const JSONP_HOME = '__ng_jsonp__'; | ||
|
|
||
| var _jsonpConnections = null; | ||
|
|
||
| JsObject _getJsonpConnections() { | ||
| if (_jsonpConnections == null) { | ||
| _jsonpConnections = context[JSONP_HOME] = new JsObject(context['Object']); | ||
| } | ||
| return _jsonpConnections; | ||
| } | ||
|
|
||
| // Make sure not to evaluate this in a non-browser environment! | ||
| @Injectable() | ||
| class BrowserJsonp { | ||
| // Construct a <script> element with the specified URL | ||
| dynamic build(String url) { | ||
| var node = document.createElement('script'); | ||
| node.src = url; | ||
| return node; | ||
| } | ||
|
|
||
| nextRequestID() { | ||
| return "__req${_nextRequestId++}"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was just looking at the AngularJS httpBackend implementation and found that the id is toStringed as base 36: angular/angular.js@fd38655#diff-725ec6acb6c38b9cec7c30861f35be8cR31 Do you have any idea what that's for?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. extra complexity? Igor might have a convincing reason for it |
||
| } | ||
|
|
||
| requestCallback(String id) { | ||
| return """${JSONP_HOME}.${id}.finished"""; | ||
| } | ||
|
|
||
| exposeConnection(String id, dynamic connection) { | ||
| var connections = _getJsonpConnections(); | ||
| var wrapper = new JsObject(context['Object']); | ||
|
|
||
| wrapper['_id'] = id; | ||
| wrapper['__dart__'] = connection; | ||
| wrapper['finished'] = ([dynamic data]) => connection.finished(data); | ||
|
|
||
| connections[id] = wrapper; | ||
| } | ||
|
|
||
| removeConnection(String id) { | ||
| var connections = _getJsonpConnections(); | ||
| connections[id] = null; | ||
| } | ||
|
|
||
| // Attach the <script> element to the DOM | ||
| send(dynamic node) { document.body.append(node); } | ||
|
|
||
| // Remove <script> element from the DOM | ||
| cleanup(dynamic node) { | ||
| node.remove(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import {Injectable} from 'angular2/di'; | ||
| import {global} from 'angular2/src/facade/lang'; | ||
|
|
||
| let _nextRequestId = 0; | ||
| export const JSONP_HOME = '__ng_jsonp__'; | ||
| var _jsonpConnections = null; | ||
|
|
||
| function _getJsonpConnections(): {[key: string]: any} { | ||
| if (_jsonpConnections === null) { | ||
| _jsonpConnections = global[JSONP_HOME] = {}; | ||
| } | ||
| return _jsonpConnections; | ||
| } | ||
|
|
||
| // Make sure not to evaluate this in a non-browser environment! | ||
| @Injectable() | ||
| export class BrowserJsonp { | ||
| // Construct a <script> element with the specified URL | ||
| build(url: string): any { | ||
| let node = document.createElement('script'); | ||
| node.src = url; | ||
| return node; | ||
| } | ||
|
|
||
| nextRequestID(): string { return `__req${_nextRequestId++}`; } | ||
|
|
||
| requestCallback(id: string): string { return `${JSONP_HOME}.${id}.finished`; } | ||
|
|
||
| exposeConnection(id: string, connection: any) { | ||
| let connections = _getJsonpConnections(); | ||
| connections[id] = connection; | ||
| } | ||
|
|
||
| removeConnection(id: string) { | ||
| var connections = _getJsonpConnections(); | ||
| connections[id] = null; | ||
| } | ||
|
|
||
| // Attach the <script> element to the DOM | ||
| send(node: any) { document.body.appendChild(<Node>(node)); } | ||
|
|
||
| // Remove <script> element from the DOM | ||
| cleanup(node: any) { | ||
| if (node.parentNode) { | ||
| node.parentNode.removeChild(<Node>(node)); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| import {ConnectionBackend, Connection} from '../interfaces'; | ||
| import {ReadyStates, RequestMethods, RequestMethodsMap} from '../enums'; | ||
| import {Request} from '../static_request'; | ||
| import {Response} from '../static_response'; | ||
| import {ResponseOptions, BaseResponseOptions} from '../base_response_options'; | ||
| import {Injectable} from 'angular2/di'; | ||
| import {BrowserJsonp} from './browser_jsonp'; | ||
| import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; | ||
| import {StringWrapper, isPresent, ENUM_INDEX, makeTypeError} from 'angular2/src/facade/lang'; | ||
|
|
||
| export class JSONPConnection implements Connection { | ||
| readyState: ReadyStates; | ||
| request: Request; | ||
| response: EventEmitter; | ||
| private _id: string; | ||
| private _script: Element; | ||
| private _responseData: any; | ||
| private _finished: boolean = false; | ||
|
|
||
| constructor(req: Request, private _dom: BrowserJsonp, | ||
| private baseResponseOptions?: ResponseOptions) { | ||
| if (req.method !== RequestMethods.GET) { | ||
| throw makeTypeError("JSONP requests must use GET request method."); | ||
| } | ||
| this.request = req; | ||
| this.response = new EventEmitter(); | ||
| this.readyState = ReadyStates.LOADING; | ||
| this._id = _dom.nextRequestID(); | ||
|
|
||
| _dom.exposeConnection(this._id, this); | ||
|
|
||
| // Workaround Dart | ||
| // url = url.replace(/=JSONP_CALLBACK(&|$)/, `generated method`); | ||
| let callback = _dom.requestCallback(this._id); | ||
| let url: string = req.url; | ||
| if (url.indexOf('=JSONP_CALLBACK&') > -1) { | ||
| url = StringWrapper.replace(url, '=JSONP_CALLBACK&', `=${callback}&`); | ||
| } else if (url.lastIndexOf('=JSONP_CALLBACK') === url.length - '=JSONP_CALLBACK'.length) { | ||
| url = StringWrapper.substring(url, 0, url.length - '=JSONP_CALLBACK'.length) + `=${callback}`; | ||
| } | ||
|
|
||
| let script = this._script = _dom.build(url); | ||
|
|
||
| script.addEventListener('load', (event) => { | ||
| if (this.readyState === ReadyStates.CANCELLED) return; | ||
| this.readyState = ReadyStates.DONE; | ||
| _dom.cleanup(script); | ||
| if (!this._finished) { | ||
| ObservableWrapper.callThrow( | ||
| this.response, makeTypeError('JSONP injected script did not invoke callback.')); | ||
| return; | ||
| } | ||
|
|
||
| let responseOptions = new ResponseOptions({body: this._responseData}); | ||
| if (isPresent(this.baseResponseOptions)) { | ||
| responseOptions = this.baseResponseOptions.merge(responseOptions); | ||
| } | ||
|
|
||
| ObservableWrapper.callNext(this.response, new Response(responseOptions)); | ||
| }); | ||
|
|
||
| script.addEventListener('error', (error) => { | ||
| if (this.readyState === ReadyStates.CANCELLED) return; | ||
| this.readyState = ReadyStates.DONE; | ||
| _dom.cleanup(script); | ||
| ObservableWrapper.callThrow(this.response, error); | ||
| }); | ||
|
|
||
| _dom.send(script); | ||
| } | ||
|
|
||
| finished(data?: any) { | ||
| // Don't leak connections | ||
| this._finished = true; | ||
| this._dom.removeConnection(this._id); | ||
| if (this.readyState === ReadyStates.CANCELLED) return; | ||
| this._responseData = data; | ||
| } | ||
|
|
||
| dispose(): void { | ||
| this.readyState = ReadyStates.CANCELLED; | ||
| let script = this._script; | ||
| this._script = null; | ||
| if (isPresent(script)) { | ||
| this._dom.cleanup(script); | ||
| } | ||
| ObservableWrapper.callReturn(this.response); | ||
| } | ||
| } | ||
|
|
||
| @Injectable() | ||
| export class JSONPBackend implements ConnectionBackend { | ||
| constructor(private _browserJSONP: BrowserJsonp, private _baseResponseOptions: ResponseOptions) {} | ||
| createConnection(request: Request): JSONPConnection { | ||
| return new JSONPConnection(request, this._browserJSONP, this._baseResponseOptions); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| library angular2.src.http.http_utils; | ||
| import 'dart:js' show JsObject; | ||
| import 'dart:collection' show LinkedHashMap, LinkedHashSet; | ||
|
|
||
| bool isJsObject(o) { | ||
| return o is JsObject || o is LinkedHashMap || o is LinkedHashSet; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export {isJsObject} from 'angular2/src/facade/lang'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not TypeError? https://api.dartlang.org/apidocs/channels/be/dartdoc-viewer/dart:core.TypeError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dart's TypeError class doesn't let me attach an arbitrary message. I guess Dart doesn't use it as a catch-all exception the way JS does
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha.