diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj index b262c50d3..52a85dee5 100644 --- a/CrossPlatformModules.csproj +++ b/CrossPlatformModules.csproj @@ -288,6 +288,10 @@ + + + + file-name-resolver.d.ts @@ -1670,6 +1674,8 @@ PreserveNewest + + diff --git a/fetch/README.md b/fetch/README.md new file mode 100644 index 000000000..fb27fc900 --- /dev/null +++ b/fetch/README.md @@ -0,0 +1,17 @@ +# Isomorphic Fetch Implementation + +## status + +WIP + +## motivation + +implementation of [fetch API](https://fetch.spec.whatwg.org/) in pure javascript. +polyfill for browser, and implemnt for node.js. +make network http access isomorphic. + + +## License + +The MIT License (MIT) +Copyright (c) 2013 Jxck diff --git a/fetch/body.ts b/fetch/body.ts new file mode 100644 index 000000000..1070006b5 --- /dev/null +++ b/fetch/body.ts @@ -0,0 +1,100 @@ +// https://fetch.spec.whatwg.org/#json +type object = JSON; + +type body = Object; // byte stream + +// https://fetch.spec.whatwg.org/#bodyinit +type BodyInit = Blob | BufferSource | FormData | URLSearchParams | USVString + +// https://fetch.spec.whatwg.org/#body +interface IBody { + // readonly property + bodyUsed: boolean; + + // method + arrayBuffer(): Promise; + blob(): Promise; + formData(): Promise; + json(): Promise; + text(): Promise; +}; + +// https://fetch.spec.whatwg.org/#body +class Body implements IBody { + private _bodyUsed: boolean; + private _body: body; + private _usedFlag: boolean; + private _mimeType: string; + + get bodyUsed(): boolean { + return this._bodyUsed; + } + + get body(): body { + return this._body; + } + + get usedFlag(): boolean { + return this._usedFlag; + } + + get mimeType(): string { + return this._mimeType; + } + + consumeBody(_type: string): any { + // step 1 + var p = new Promise((resolve, reject) => { + // step 2 + if (this._bodyUsed == true) { + return reject(new TypeError("body was already used")); + } + + // step 3 + this._bodyUsed = true; + + // step 3-1 + var stream = this._body; + + // step 3-2 + if (stream == null) { + stream = []; + } + + // step 3-3 + // TODO: Let bytes be the result of reading from stream until it returns end-of-stream. + var bytes; + + // step 4 + // TODO: implement me + switch(_type) { + case "ArrayBuffer": + case "Blob": + case "FormData": + case "JSON": + case "text": + } + }); + return p; + } + + arrayBuffer(): Promise { + return this.consumeBody("ArrayBuffer"); + } + + blob(): Promise { + return this.consumeBody("Blob"); + } + + formData(): Promise { + return this.consumeBody("FormData"); + } + + json(): Promise { + return this.consumeBody("JSON"); + } + + text(): Promise { + return this.consumeBody("text"); + } +} diff --git a/fetch/fetch.ts b/fetch/fetch.ts new file mode 100644 index 000000000..c2dbc6f75 --- /dev/null +++ b/fetch/fetch.ts @@ -0,0 +1,677 @@ +/// +/// +/// + +// http://heycam.github.io/webidl/#common-BufferSource +class BufferSource { +} + +// https://url.spec.whatwg.org/#urlsearchparams +class URLSearchParams { +} + +// https://fetch.spec.whatwg.org/#concept-method +enum MethodEnum { + OPTIONS, + GET, + HEAD, + POST, + PUT, + DELETE, + TRACE, + CONNECT, +} + +// https://fetch.spec.whatwg.org/#simple-method +enum SimpleMethodEnum { + GET, + HEAD, + POST +} + +function isSimpleMethod(method: ByteString): boolean { + if (SimpleMethodEnum[method] !== undefined) { + return true; + } + return false; +} + +// https://fetch.spec.whatwg.org/#forbidden-method +enum ForbiddenMethodEnum { + CONNECT, + TRACE, + TRACK +} + +function isForbiddenMethod(method: ByteString): boolean { + if (ForbiddenMethodEnum[method] !== undefined) { + return true; + } + return false +} + +// https://fetch.spec.whatwg.org/#requestcontext +type RequestContext = string; +enum RequestContextEnum { + "audio", "beacon", + "cspreport", "download", + "embed", "eventsource", + "favicon", "fetch", + "font", "form", + "frame", "hyperlink", + "iframe", "image", + "imageset", "import", + "internal", "location", + "manifest", "object", + "ping", "plugin", + "prefetch", "script", + "serviceworker", "sharedworker", + "subresource", "style", + "track", "video", + "worker", "xmlhttprequest", + "xslt" +}; + +// https://fetch.spec.whatwg.org/#concept-request-context-frame-type +enum ContextFrameTypeEnum { + "auxiliary", + "top-level", + "nested", + "none" +} + +// https://fetch.spec.whatwg.org/#concept-request-mode +type RequestMode = string; +enum RequestModeEnum { + "same-origin", + "no-cors", + "cors" +}; + +// https://fetch.spec.whatwg.org/#concept-request-credentials-mode +type RequestCredentials = string; +enum RequestCredentialsEnum { + "omit", + "same-origin", + "include" +}; + +// https://fetch.spec.whatwg.org/#concept-request-cache-mode +type RequestCache = string; +enum RequestCacheEnum { + "default", + "bypass", + "reload", + "revalidate", + "force-cache", + "offline" +}; + +// https://fetch.spec.whatwg.org/#concept-response-type +type ResponseType = string; +enum ResponseTypeEnum { + "basic", + "cors", + "default", + "error", + "opaque" +}; + +///////////////////////////// +/// Request +///////////////////////////// + +// https://fetch.spec.whatwg.org/#requestinfo +type RequestInfo = Request | USVString; + +// https://fetch.spec.whatwg.org/#request +interface IRequest extends IBody { + // readonly property + method: ByteString; + url: USVString; + headers: Headers; + context: RequestContext; + referrer: DOMString; + mode: RequestMode; + credentials: RequestCredentials; + cache: RequestCache; + + // method + clone(): IRequest; +}; + +// https://fetch.spec.whatwg.org/#requestinit +// dictionary RequestInit +interface RequestInit { + method: ByteString; + headers: HeadersInit; + body: BodyInit; + mode: RequestMode; + credentials: RequestCredentials; + cache: RequestCache; +}; + +type Client = Object; +type Referrer = Object; +type Context = Object; + +type request = { + method: string; + url: string; + headerList: Header[]; + unsafeRequestFlag: boolean; + body: body; + //TODO: client: Client; + context: Context; + //TODO: origin: string; + forceOriginHeaderFlag: boolean; + sameOriginDataURLFlag: boolean; + //TODO: referrer: Referrer; + mode: string; + credentialsMode: string; + cacheMode: string; +} + +class Request implements IRequest { + // readonly property on IRequest + private _method: ByteString; + private _url: USVString; + private _headers: Headers; + private _context: RequestContext; + private _referrer: DOMString; + private _mode: ByteString; + private _credentials: RequestCredentials; + private _cache: RequestCache; + + // readonly property on IBody + private _bodyUsed: boolean; + + // https://fetch.spec.whatwg.org/#dom-request-method + get method(): ByteString { + return this._method; + } + + // https://fetch.spec.whatwg.org/#dom-request-url + get url(): USVString { + return this._url; + } + + // https://fetch.spec.whatwg.org/#dom-request-headers + get headers(): Headers { + return this._headers; + } + + // https://fetch.spec.whatwg.org/#dom-request-context + get context(): RequestContext { + return this._context; + } + + // https://fetch.spec.whatwg.org/#dom-request-referrer + get referrer(): DOMString { + return null; + } + + // https://fetch.spec.whatwg.org/#dom-request-mode + get mode(): RequestMode { + return null; + } + + // https://fetch.spec.whatwg.org/#dom-request-credentials + get credentials(): RequestCredentials { + return this._credentials; + } + + // https://fetch.spec.whatwg.org/#dom-request-cache + get cache(): RequestCache { + return null; + } + + // https://fetch.spec.whatwg.org/#dom-body-bodyused + get bodyUsed(): boolean { + return this._bodyUsed; + } + + // https://fetch.spec.whatwg.org/#dom-request-clone + // method on IRequest + public clone(): IRequest { + return null; + } + + // https://fetch.spec.whatwg.org/#dom-body-arraybuffer + // method on IBody + public arrayBuffer(): Promise { + return null; + } + + // https://fetch.spec.whatwg.org/#dom-body-blob + public blob(): Promise { + return null; + } + + // https://fetch.spec.whatwg.org/#dom-body-formdata + public formData(): Promise { + return null; + } + + // https://fetch.spec.whatwg.org/#dom-body-json + public json(): Promise { + return null; + } + + // https://fetch.spec.whatwg.org/#dom-body-text + public text(): Promise { + return null; + } + + // Request + public request: request; + public body: body; + public usedFlag: boolean; + public mimeType: string; + + // https://fetch.spec.whatwg.org/#dom-request + constructor(input: RequestInfo, init?: RequestInit) { + // can't detect class by instanceof + // if (input instanceof Request) { } + + var request: request; + // step 1 + if (typeof input === "object" && input.body !== null) { // Request + // step 1-1 + if (input.usedFlag) { + throw new TypeError("Request already used"); + } + // step 1-2 + input.usedFlag = true; + + // step 2 + request = input.request; + } else { + // step 2 + // new request otherwise + request = { + url: null, + method: MethodEnum[MethodEnum.GET], + headerList: [], + unsafeRequestFlag: false, + body: null, + //TODO: client: entry settings object, + //TODO: origin: entry settings object.origin, + forceOriginHeaderFlag: false, + sameOriginDataURLFlag: false, + referrer: null, + context: null, + mode: RequestModeEnum[RequestModeEnum["no-cors"]], + credentialsMode: RequestCredentialsEnum[RequestCredentialsEnum.omit], + cacheMode: RequestCacheEnum[RequestCacheEnum.default], + } + } + + // step 3 + request = { + url: request.url, + method: request.method, + headerList: request.headerList, + unsafeRequestFlag: true, + body: request.body, + //TODO: client: entry settings object, + //TODO: origin: entry settings object.origin, + forceOriginHeaderFlag: true, + sameOriginDataURLFlag: true, + //TODO: referrer : request.client, + context: 'fetch', + mode: request.mode, + credentialsMode: request.credentialsMode, + cacheMode: request.cacheMode + } + + // step 4, 5, 6 + var fallbackMode: RequestMode = null; + var fallbackCredentials: RequestCredentials = null; + var fallbackCache: RequestCache = null; + + //TODO: + function parseURL(url: string): string { + return url; + } + // step 7 + if (typeof input === "string") { + // step 7-1 + var parsedURL; + + try { + parsedURL = parseURL(input); + } catch(err) { + // step 7-2 + throw new TypeError(err); + } + + // step 7-3 + request.url = parsedURL; + + // step 7-4, 7-5, 7-6 + fallbackMode = RequestModeEnum[RequestModeEnum.cors]; + fallbackCredentials = RequestCredentialsEnum[RequestCredentialsEnum.omit]; + fallbackCache = RequestCacheEnum[RequestCacheEnum.default]; + } + + // step 8 + var mode = init.mode? init.mode: fallbackMode; + + // step 9 + if (mode !== null) request.mode = mode; + + // step 10 + var credentials = init.credentials? init.credentials: fallbackCredentials; + + // step 11 + if (credentials !== null) request.credentialsMode = credentials; + + // step 12 + var cache = init.cache? init.cache: fallbackCache; + + // step 13 + if (cache !== null) request.cacheMode = cache; + + // step 14 + if (init.method) { + var method = init.method; + + // step 14-1 + if(isForbiddenMethod(method)) { + throw new TypeError("forbidden method " + method); + } + + // step 14-2 + method = method.toUpperCase(); + + // step 14-3 + request.method = method; + } + + // step 15 + var r = this; + r.request = request; + r._headers = new Headers(); + + // step 16 + var headers = r.headers; + + // step 17 + if (init.headers) { + headers = init.headers; + } + + // step 18 + r.request.headerList = []; + + // step 19 + if (r.request.mode === "no-cors") { + // 19-1 + if (!isSimpleMethod(this.request.method)) { + throw new TypeError("not simple method" + method); + } + // 19-2 + r.headers.guard = "request-no-CORS"; + } + + // step 20 + r._headers = headers; + + // step 21 + if (init.body) { + // step 21-1 + var result = extract(init.body); + + // step 21-2 + r.request.body = result.stream; + + // step 21-3 + if (result.contentType !== null) { + var hasContentType = request.headerList.some((header) => { + return header.name === "Content-Type"; + }); + + if (!hasContentType) { + r._headers.append("Content-Type", result.contentType); + } + } + } + + // step 22 + // FIXME implement mime type extract + r.mimeType = null; + } +} + +// https://fetch.spec.whatwg.org/#concept-bodyinit-extract +function extract(object: any): any { + // step 1 + var stream = []; + + // step 2 + var contentType = null; + + // step 3 + switch(object.constructor) { + // Blob + case Blob: + stream = object.contents; + + if (object.type) { + contentType = object.type; + } + case BufferSource: + // TODO: stream = copy(object); + case FormData: + // TODO: + case URLSearchParams: + stream = object.list.toString(); + contentType = "application/x-www-form-urlencoded;charset=UTF-8"; + case String: // USVString + // stream = encode(object); + contentType = "text/plain;charset=UTF-8"; + } + + // step 4 + return { stream: stream, contentType: contentType }; +} + + + +// https://fetch.spec.whatwg.org/#response +interface IResponse extends IBody { // Response implements Body; + // static Response error(); + // static Response redirect(USVString url, optional unsigned short status = 302); + + type: ResponseType; // readonly + url: USVString; // readonly + status: number; // readonly + statusText: ByteString; // readonly + headers: Headers; // readonly + // Response clone(); +}; + +// https://fetch.spec.whatwg.org/#responseinit +class ResponseInit { + status: number = 200; + statusText: ByteString = "OK"; + headers: HeadersInit; +}; + +class response { + type: string; + terminationReason: string; + url: string; + status: number; + statusMessage: string; + headerList: Header[]; + body: Body; + cacheState: string; + TLSState: string; + + constructor() { + this.type = "default"; + this.terminationReason = "timeout"; + this.url = null; + this.status = 200; + this.statusMessage = "OK"; + this.headerList = []; + this.body = null; + this.cacheState = "none"; + this.TLSState = "unauthenticated"; + } +} + +// https://fetch.spec.whatwg.org/#response +class Response implements IResponse { + // implements body + _bodyUsed: boolean; + + _type: ResponseType; // readonly + _url: USVString; // readonly + _status: number; // readonly + _statusText: ByteString; // readonly + _headers: Headers; // readonly + + _response: response; + + // https://fetch.spec.whatwg.org/#dom-response + // [Constructor(optional BodyInit body, optional ResponseInit init), Exposed=(Window,Worker)] + constructor(body?: BodyInit, init?: ResponseInit) { + if (init !== undefined) { + // step 1 + if (init.status < 200 && init.status > 599) { + throw new RangeError("status is not in the range 200 to 599"); + } + + // step 2 + if (init.statusText.indexOf("\r") < 0 || init.statusText.indexOf("\n") < 0) { + throw new TypeError("Invalid Reason-Phrase token production"); + } + } + + // step 3 + var r = this; + r._response = new response(); + r._headers = new Headers(); + + if (init !== undefined) { + // step 4 + r._response.status = init.status; + // step 5 + r._response.statusMessage = init.statusText; + } + + // step 6 + if (init.headers) { + // step 6-1 + r._response.headerList = []; + + // step 6-2 + // TODO: implements fill + // r._response.headerList =; + } + + // step 7 + if (body) { + // step 7-1 + var extracted = extract(body); + var stream = extracted.stream; + var contentType = extracted.contentType; + + // step 7-2 + r._response.body = stream; + + // step 7-3 + if (contentType !== null) { + var hasContentType = r._response.headerList.some((header) => { + return header.name === "Content-Type"; + }); + + if (!hasContentType) { + // TODO: append + // r._response.headerList.append(Header("Content-Type", contentType)); + } + } + + // step 8 + // TODO: extracting MIME type + + // step 9 + // TODO: TLS state + + // step 10 + return r; + } + } + + get bodyUsed(): boolean { + return this._bodyUsed; + } + + get type(): ResponseType { + return this._type; + } + + get url(): USVString { + return this._url; + } + + get status(): number { + return this._status; + } + + get statusText(): ByteString { + return this._statusText; + } + + get headers(): Headers { + return this._headers; + } + + arrayBuffer(): Promise { + return null; + } + + blob(): Promise { + return null; + } + + formData(): Promise { + return null; + } + + json(): Promise { + return null; + } + + text(): Promise { + return null; + } +} + + +// https://fetch.spec.whatwg.org/#globalfetch +// Window implements GlobalFetch; +interface Window { + fetch(input: RequestInfo, init?: RequestInit): Promise; +}; + +// WorkerGlobalScope implements GlobalFetch; +this.fetch = function(input: RequestInfo, init?: RequestInit): Promise { + // step 1 + var p = new Promise((resolve, reject) => { + try { + // step 2 + var r = (new Request(input, init)).request; + } catch(e) { + reject(e); + } + }); + + // step 4 + return p +} + +// WorkerGlobalScope implements GlobalFetch; diff --git a/fetch/headers.ts b/fetch/headers.ts new file mode 100644 index 000000000..33e3d2bf6 --- /dev/null +++ b/fetch/headers.ts @@ -0,0 +1,341 @@ +/// + +// https://fetch.spec.whatwg.org/#forbidden-header-name +var ForbiddenHeaderName = { + "Accept-Charset": "Accept-Charset", + "Accept-Encoding": "Accept-Encoding", + "Access-Control-Request-Headers": "Access-Control-Request-Headers", + "Access-Control-Request-Method": "Access-Control-Request-Method", + "Connection": "Connection", + "Content-Length": "Content-Length", + "Cookie": "Cookie", + "Cookie2": "Cookie2", + "Date": "Date", + "DNT": "DNT", + "Expect": "Expect", + "Host": "Host", + "Keep-Alive": "Keep-Alive", + "Origin": "Origin", + "Referer": "Referer", + "TE": "TE", + "Trailer": "Trailer", + "Transfer-Encoding": "Transfer-Encoding", + "Upgrade": "Upgrade", + "User-Agent": "User-Agent", + "Via": "Via" +}; +function isForbiddenHeaderName(name: ByteString): boolean { + if (ForbiddenHeaderName[name] !== undefined) { + return true; + } + var reg = /^(Proxy\-|Sec\-)/ + if (reg.exec(name)) { + return true; + } + + return false; +} + +// https://fetch.spec.whatwg.org/#forbidden-response-header-name +var ForbiddenResponseHeaderName = { + "Set-Cookie": "Set-Cookie", + "Set-Cookie2": "Set-Cookie2" +} +function isForbiddenResponseHeaderName(name: ByteString): boolean { + return ForbiddenResponseHeaderName[name] !== undefined; +} + +// https://fetch.spec.whatwg.org/#simple-header +var SimpleHeaderName = { + "Accept": "Accept", + "Accept-Language": "Accept-Language", + "Content-Language": "Content-Language" +} +var SimpleHeaderValue = { + "application/x-www-form-urlencoded": "application/x-www-form-urlencoded", + "multipart/form-data": "multipart/form-data", + "text/plain": "text/plain" +} +function isSimpleHeader(name, value: ByteString): boolean { + if (SimpleHeaderName[name] !== undefined) { + return true; + } + + if (name === "Content-Type") { + // TODO: parse value https://fetch.spec.whatwg.org/#concept-header-parse + if (SimpleHeaderValue[value] !== undefined) { + return true; + } + } + + return false; +} + +// https://fetch.spec.whatwg.org/#headersinit +// typedef (Headers or sequence> or OpenEndedDictionary) HeadersInit; +type HeadersInit = Headers | ByteString[][] | OpenEndedDictionary; + +// https://fetch.spec.whatwg.org/#headers +interface IHeaders { + append(name, value: ByteString): void; + delete(name: ByteString): void; + get(name: ByteString): ByteString; + getAll(name: ByteString): ByteString[]; + has(name: ByteString): boolean; + set(name, value: ByteString): void; + // iterable; +}; + +// https://fetch.spec.whatwg.org/#concept-header +class Header { + name: ByteString; + value: ByteString; + constructor(name, value: ByteString) { + // TODO: validation + this.name = name; + this.value = value; + } +} + +var Guard = { + "immutable": "immutable", + "request": "request", + "request-no-CORS": "request-no-CORS", + "response": "response", + "none": "none", +} + +function copy(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} + +// https://fetch.spec.whatwg.org/#headers +class Headers implements IHeaders{ + public headerList: Header[] = []; + public guard: string = Guard.none; + + // https://fetch.spec.whatwg.org/#dom-headers + constructor(init?: HeadersInit) { + // step 1 + var headers = this; // new Headers object + + // step 2 + if (init !== undefined) { + this.fill(headers, init); + } + + // step 3 + return headers; + } + + // https://fetch.spec.whatwg.org/#concept-headers-fill + private fill(headers: Headers, object: HeadersInit) { + // step 1 Headers + if (object instanceof Headers) { + var headerListCopy: Header[] = copy(object.headerList); + headerListCopy.forEach((header: Header) => { + headers.append(header.name, header.value); + }); + return; + } + + // step 2 ByteString[][] + if (Array.isArray(object)) { + var headerSequence = object; + headerSequence.forEach((header) => { + if(header.length !== 2) { + throw new TypeError("init for Headers was incorrect BytesString Sequence"); + } + headers.append(header[0], header[1]); + }); + return; + } + + // step 3 OpenEndedDictionary + if (typeof object === "object") { + Object.keys(object).forEach((key) => { + headers.append(key.toString(), object[key]); + }); + } + } + + // https://fetch.spec.whatwg.org/#dom-headers-append + append(name, value: ByteString): void { + // https://fetch.spec.whatwg.org/#concept-headers-append + // step 1 + if (!name || !value) { + // TODO name/value validation + throw new TypeError("invalid name/value"); + } + + // step 2, 3, 4, 5 + switch(this.guard) { + case Guard.immutable: + throw new TypeError("operation to immutable headers"); + case Guard.request: + if (isForbiddenHeaderName(name)) { + return; + } + case Guard["request-no-CORS"]: + if (!isSimpleHeader(name, value)) { + return; + } + case Guard.response: + if (isForbiddenResponseHeaderName(name)) { + return; + } + } + + // step 6 + name = name.toLowerCase(); + this.headerList.push(new Header(name, value)); + } + + // https://fetch.spec.whatwg.org/#dom-headers-delete + delete(name: ByteString): void { + // step 1 + if (!name) { + throw new TypeError("invalid name"); + } + + // step 2, 3, 4, 5 + switch(this.guard) { + case Guard.immutable: + throw new TypeError("operation to immutable headers"); + case Guard.request: + if (isForbiddenHeaderName(name)) { + return; + } + case Guard["request-no-CORS"]: + if (!isSimpleHeader(name, "invalid")) { + return; + } + case Guard.response: + if (isForbiddenResponseHeaderName(name)) { + return; + } + } + + name = name.toLowerCase(); + // step 6 + this.headerList = this.headerList.filter((header: Header) => { + return header.name !== name; + }); + } + + // https://fetch.spec.whatwg.org/#dom-headers-get + get(name: ByteString) :ByteString { + // step 1 + if (!name) { + throw new TypeError("invalid name"); + } + + // step 2 + var value: ByteString = null; + this.headerList.forEach((header: Header) => { + if (header.name === name) { + value = header.value; + return; + } + }); + + return value; + } + + // https://fetch.spec.whatwg.org/#dom-headers-getall + getAll(name: ByteString) :ByteString[] { + // step 1 + if (!name) { + throw new TypeError("invalid name"); + } + + // step 2 + var result: ByteString[] = this.headerList.reduce((acc: ByteString[], header: Header) => { + if (header.name === name) { + acc.push(header.value); + } + return acc; + }, []); + + return result; + } + + // https://fetch.spec.whatwg.org/#dom-headers-has + has(name: ByteString) :boolean { + // step 1 + if (!name) { + throw new TypeError("invalid name"); + } + + // step 2 + return this.headerList.some((header: Header) => { + return header.name === name; + }); + } + + // https://fetch.spec.whatwg.org/#dom-headers-set + set(name, value: ByteString): void { + // step 1 + if (!name || !value) { + throw new TypeError("invalid name/value"); + } + + switch(this.guard) { + // step 2 + case Guard.immutable: + throw new TypeError("operation to immutable headers"); + // step 3 + case Guard.request: + if (isForbiddenHeaderName(name)) { + return; + } + // step 4 + case Guard["request-no-CORS"]: + if (!isSimpleHeader(name, "invalid")) { + return; + } + // step 5 + case Guard.response: + if (isForbiddenResponseHeaderName(name)) { + return; + } + } + + // step 6 + // see https://fetch.spec.whatwg.org/#concept-header-list-set + + // step 6-1 + name = name.toLowerCase(); + + // find the all indexes of headers whos key is supplyed key + var indexes: number[] = this.headerList.reduce((acc: number[], header: Header, index: number) => { + if (header.name === name) { + acc.push(index); + } + return acc; + }, []); + + // count of existing headers + var len = indexes.length; + + // step 6-3 + // if there are no key + if (len === 0) { + // append to last and return + return this.append(name, value); + } + + // step 6-2 + // remove the headers in indexes from the last(because splice chenges index) + // and change first header value + indexes.reverse().forEach((e, i: number) => { + if(i === len - 1) { + // only replace first entry + this.headerList[e].value = value; + } else { + // remove duplicate from last + this.headerList.splice(e, 1); + } + }); + } +} diff --git a/fetch/package.json b/fetch/package.json new file mode 100644 index 000000000..f19e3419b --- /dev/null +++ b/fetch/package.json @@ -0,0 +1,28 @@ +{ + "name": "fetch-standard", + "author": "Jxck", + "license": "MIT", + "version": "0.0.0", + "description": "implementaion of https://fetch.spec.whatwg.org/", + "homepage": "https://github.com/Jxck/fetch", + "bugs": { + "url": "https://github.com/Jxck/fetch/issues" + }, + "keywords": [ + "fetch", + "whatwg" + ], + "repository": { + "type": "git", + "url": "https://github.com/Jxck/fetch" + }, + "main": "fetch.ts", + "scripts": { + "clean": "rm *.js", + "build": "tsc fetch.ts --target ES5", + "test": "npm run build && node fetch.js" + }, + "devDependencies": { + "typescript": "^1.4.1" + } +} diff --git a/fetch/webidl.d.ts b/fetch/webidl.d.ts new file mode 100644 index 000000000..62bae8bca --- /dev/null +++ b/fetch/webidl.d.ts @@ -0,0 +1,14 @@ +// http://heycam.github.io/webidl/#idl-ByteString +declare type ByteString = string; + +// http://heycam.github.io/webidl/#idl-USVString +declare type USVString = string; + +// http://heycam.github.io/webidl/#idl-DOMString +declare type DOMString = string; + +// see: https://fetch.spec.whatwg.org/#headersinit +declare type OpenEndedDictionary = Object; + +declare class FormData { } +declare class Blob { } \ No newline at end of file