quiq-chat
Advanced tools
Comparing version 1.53.0 to 1.54.0
{ | ||
"name": "quiq-chat", | ||
"version": "1.53.0", | ||
"version": "1.54.0", | ||
"descri1tion": | ||
@@ -42,2 +42,3 @@ "Library to help with network requests to create a webchat client for Quiq Messaging", | ||
"store": "2.0.12", | ||
"stubborn-fetch": "^0.0.4", | ||
"ua-parser-js": "0.7.12" | ||
@@ -44,0 +45,0 @@ }, |
@@ -43,5 +43,3 @@ # quiq-chat [![Build Status](https://travis-ci.org/Quiq/quiq-chat.svg?branch=master)](https://travis-ci.org/Quiq/quiq-chat) [![npm version](https://badge.fury.io/js/quiq-chat.svg)](https://badge.fury.io/js/quiq-chat) [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) | ||
}); | ||
QuiqChatClient.onRetryableError(error => { | ||
// Show some error message | ||
}).QuiqChatClient.onErrorResolved(() => { | ||
QuiqChatClient.onErrorResolved(() => { | ||
// Remove the error message | ||
@@ -86,6 +84,2 @@ }); | ||
#### onRetryableError(error: ?ApiError) => [QuiqChatClient](#quiqchatclient) | ||
Called whenever there is a retryable error from the API | ||
#### onErrorResolved() => [QuiqChatClient](#quiqchatclient) | ||
@@ -120,6 +114,2 @@ | ||
#### onClientInactiveTimeout() => [QuiqChatClient](#quiqchatclient) | ||
Called when quiq-chat disconnects the websocket due to the chat client being inactive for a set amount of time | ||
#### getMessages(cache?: boolean = true) => Promise<Array<[ConversationMessage](#ConversationMessage)>> | ||
@@ -126,0 +116,0 @@ |
@@ -15,3 +15,2 @@ // @flow | ||
import {set} from 'store'; | ||
import * as stubbornFetch from '../stubbornFetch'; | ||
import * as Utils from '../Utils/utils'; | ||
@@ -54,3 +53,2 @@ import log from 'loglevel'; | ||
const onRegistration = jest.fn(); | ||
const onClientInactiveTimeout = jest.fn(); | ||
const host = 'https://test.goquiq.fake'; | ||
@@ -60,3 +58,2 @@ const contactPoint = 'test'; | ||
const mockStore = (storage: any); | ||
const setClientInactive = jest.spyOn(stubbornFetch, 'setClientInactive'); | ||
@@ -84,3 +81,2 @@ beforeEach(() => { | ||
QuiqChatClient.onBurn(onBurn); | ||
QuiqChatClient.onClientInactiveTimeout(onClientInactiveTimeout); | ||
@@ -120,6 +116,2 @@ QuiqChatClient.start(); | ||
}); | ||
it('calls setClientInactive with false', () => { | ||
expect(setClientInactive).toBeCalledWith(false); | ||
}); | ||
}); | ||
@@ -165,6 +157,2 @@ | ||
}); | ||
it('does not call setClientInactive', () => { | ||
expect(setClientInactive).not.toBeCalled(); | ||
}); | ||
}); | ||
@@ -306,3 +294,2 @@ | ||
QuiqChatClient.onBurn(onBurn); | ||
QuiqChatClient.onClientInactiveTimeout(onClientInactiveTimeout); | ||
@@ -398,3 +385,2 @@ if (!QuiqChatClient) { | ||
QuiqChatClient.onBurn(onBurn); | ||
QuiqChatClient.onClientInactiveTimeout(onClientInactiveTimeout); | ||
@@ -543,3 +529,2 @@ QuiqChatClient.events = []; | ||
QuiqChatClient.onBurn(onBurn); | ||
QuiqChatClient.onClientInactiveTimeout(onClientInactiveTimeout); | ||
@@ -546,0 +531,0 @@ if (!QuiqChatClient) { |
@@ -35,2 +35,3 @@ // @flow | ||
} from './types'; | ||
import {setFetchMode} from 'quiqFetch'; | ||
import * as storage from './storage'; | ||
@@ -145,8 +146,2 @@ import logger from './logging'; | ||
onRetryableError = (callback: (error: ?ApiError) => void): QuiqChatClient => { | ||
this.callbacks.onRetryableError = callback; | ||
StubbornFetch.registerCallbacks({onRetryableError: callback}); | ||
return this; | ||
}; | ||
onErrorResolved = (callback: () => void): QuiqChatClient => { | ||
@@ -173,8 +168,2 @@ this.callbacks.onErrorResolved = callback; | ||
onClientInactiveTimeout = (callback: () => void): QuiqChatClient => { | ||
this.callbacks.onClientInactiveTimeout = callback; | ||
return this; | ||
}; | ||
onPersistentDataChange = (callback: (data: PersistentData) => void): QuiqChatClient => { | ||
@@ -192,3 +181,2 @@ storage.registerCallbacks({onPersistentDataChange: callback}); | ||
this.initialized = true; | ||
StubbornFetch.setClientInactive(false); | ||
@@ -400,2 +388,4 @@ // Order Matters here. Ensure we successfully complete this fetchConversation request before connecting to | ||
_setFetchMode = setFetchMode; | ||
/** | ||
@@ -402,0 +392,0 @@ * Returns an object of state information, useful for logging errors. |
// @flow | ||
import stubbornFetch from './stubbornFetch'; | ||
import {checkRequiredSettings} from './globals'; | ||
import {isStorageEnabled, getTrackingId} from './storage'; | ||
import oldStubbornFetch from './stubbornFetch'; | ||
import StubbornFetch, {StubbornFetchError} from 'stubborn-fetch'; | ||
import {checkRequiredSettings, getSessionApiUrl, getGenerateUrl, getBurned} from './globals'; | ||
import {isStorageEnabled, getTrackingId, getAccessToken} from './storage'; | ||
import {login} from './apiCalls'; | ||
import merge from 'lodash/merge'; | ||
import {formatQueryParams, createGuid} from './Utils/utils'; | ||
import {burnItDown, formatQueryParams, createGuid} from './Utils/utils'; | ||
import {version} from '../package.json'; | ||
import logger from './logging'; | ||
import logging from './logging'; | ||
const log = logger('QuiqFetch'); | ||
const messages = { | ||
burned: 'Client in bad state. Aborting call.', | ||
burnedFromServer: 'Received 466 response code from server. Blocking any further API Calls.', | ||
storageDisabled: 'Storage is not enabled, aborting call', | ||
trackingIdChanged: 'Tracking ID changed, not retrying after login.', | ||
cannotParseResponse: (url: string) => `Couldn't parse API response from ${url}`, | ||
unknownError: (url: string) => `Unknown error while parsing ${url}`, | ||
}; | ||
const quiqFetchLog = logging('QuiqFetch'); | ||
const logger = { | ||
log: quiqFetchLog.log, | ||
info: quiqFetchLog.info, | ||
warn: quiqFetchLog.warn, | ||
error: (msg: string, e: StubbornFetchError) => | ||
quiqFetchLog.error( | ||
msg, | ||
e, | ||
e && | ||
e.error && | ||
e.error.data && | ||
e.error.data.response && | ||
e.error.data.response.status && | ||
e.error.data.response.status >= 400 && | ||
e.error.data.response.status < 500, | ||
), | ||
}; | ||
let fetchMode; | ||
export const setFetchMode = (mode?: 'edge' | 'legacy') => { | ||
fetchMode = mode; | ||
}; | ||
const quiqFetch = ( | ||
@@ -28,5 +61,9 @@ url: string, | ||
if (!isStorageEnabled()) { | ||
return Promise.reject('Storage is not enabled, aborting call'); | ||
return Promise.reject(new Error(messages.storageDisabled)); | ||
} | ||
if (getBurned()) { | ||
return Promise.reject(new Error(messages.burned)); | ||
} | ||
if (options.checkRequiredSettings) checkRequiredSettings(); | ||
@@ -49,3 +86,9 @@ | ||
mode: 'cors', | ||
headers: { | ||
correlationId, | ||
}; | ||
request.method = request.method || 'GET'; | ||
request.headers = Object.assign( | ||
{}, | ||
{ | ||
'X-Quiq-Line': '2', | ||
@@ -55,21 +98,71 @@ 'X-Quiq-Client-Id': 'Quiq-Chat-Client', | ||
'x-centricient-correlation-id': correlationId, | ||
'X-Quiq-Access-Token': getAccessToken(), | ||
}, | ||
correlationId, | ||
}; | ||
options.requestType === 'JSON' | ||
? { | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
} | ||
: {}, | ||
); | ||
let headers = {}; | ||
if (options.requestType === 'JSON' && request.headers) { | ||
headers = { | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
}; | ||
} | ||
if (overrides) { | ||
request.headers = merge(request.headers, headers); | ||
request = merge(request, overrides); | ||
} | ||
request.method = request.method || 'GET'; | ||
return stubbornFetch(parsedUrl, request) | ||
if (!fetchMode || fetchMode === 'legacy') { | ||
return oldStubbornFetch(parsedUrl, request) | ||
.then((res: Promise<Response> | Response): any => { | ||
if (options.responseType === 'JSON' && res && res.json) { | ||
return ((res: any): Response) | ||
.json() | ||
.then(result => result) | ||
.catch(err => { | ||
logger.warn(messages.cannotParseResponse(parsedUrl), {exception: err}); | ||
return err; | ||
}); | ||
} else if (options.responseType === 'NONE') { | ||
return; | ||
} | ||
return res; | ||
}) | ||
.catch(err => { | ||
return Promise.reject(err); | ||
}); | ||
} | ||
const onError = (e: StubbornFetchError) => { | ||
if (e.data && e.data.response && e.data.response.status) { | ||
const {status} = e.data.response; | ||
if ( | ||
status === 466 || | ||
(status === 401 && (url === getGenerateUrl() || url === getSessionApiUrl())) | ||
) { | ||
burnItDown(); | ||
return true; | ||
} | ||
} | ||
switch (e.type) { | ||
case StubbornFetchError.types.MAX_ERRORS_EXCEEDED: | ||
case StubbornFetchError.types.RATE_LIMITED: | ||
burnItDown(); | ||
return true; | ||
} | ||
return false; | ||
}; | ||
return new StubbornFetch(parsedUrl, request, { | ||
retries: -1, | ||
maxErrors: 100, | ||
retryOnNetworkFailure: true, | ||
totalRequestTimeLimit: 30000, | ||
onError, | ||
logger, | ||
minimumStatusCodeForRetry: 402, | ||
}) | ||
.send() | ||
.then((res: Promise<Response> | Response): any => { | ||
@@ -81,3 +174,3 @@ if (options.responseType === 'JSON' && res && res.json) { | ||
.catch(err => { | ||
log.warn(`Couldn't parse API response from ${parsedUrl}`, {exception: err}); | ||
logger.warn(messages.cannotParseResponse(parsedUrl), {exception: err}); | ||
return err; | ||
@@ -91,4 +184,30 @@ }); | ||
}) | ||
.catch(err => { | ||
return Promise.reject(err); | ||
.catch(error => { | ||
if (!error) return Promise.reject(new Error(messages.unknownError(parsedUrl))); | ||
if (onError(error)) { | ||
return Promise.reject(new Error(messages.burned)); | ||
} | ||
if ( | ||
error.type === StubbornFetchError.types.HTTP_ERROR && | ||
error.data && | ||
error.data.response && | ||
error.data.response.status && | ||
error.data.response.status === 401 | ||
) { | ||
return login() | ||
.then(({trackingId, oldTrackingId}) => { | ||
// Do NOT retry if tid changed--this is a new session, and any call we might be making was intended for old session | ||
if (oldTrackingId && trackingId !== oldTrackingId) { | ||
logger.warn(messages.trackingIdChanged); | ||
return Promise.reject(new Error(error)); | ||
} | ||
return quiqFetch(url, overrides, options); | ||
}) | ||
.catch(err => Promise.reject(err)); | ||
} | ||
return Promise.reject(error); | ||
}); | ||
@@ -95,0 +214,0 @@ }; |
@@ -10,3 +10,3 @@ import Raven from 'raven-js'; | ||
autoBreadcrumbs: { | ||
xhr: false, // XMLHttpRequest | ||
xhr: true, // XMLHttpRequest | ||
console: true, // console logging | ||
@@ -13,0 +13,0 @@ dom: true, // DOM interactions, i.e. clicks/typing |
@@ -22,3 +22,2 @@ // @flow | ||
'Client has exceeded maximum number of errors for a single session. Aborting session.', | ||
clientInactive: 'The client has been inactive for 30 minutes. Blocking any further API Calls.', | ||
}; | ||
@@ -28,3 +27,2 @@ | ||
onError?: (error: ?ApiError) => void, | ||
onRetryableError?: (error: ?ApiError) => void, | ||
onErrorResolved?: () => void, | ||
@@ -36,3 +34,2 @@ }; | ||
let initialized = false; | ||
let clientInactive = false; | ||
let errorCount = 0; | ||
@@ -48,6 +45,2 @@ | ||
export const setClientInactive = (isInactive: boolean) => { | ||
clientInactive = isInactive; | ||
}; | ||
const logRequest = (logData: Object) => { | ||
@@ -115,8 +108,2 @@ if (logData._logged) return; | ||
if (clientInactive) { | ||
logData.statusCode = -1; | ||
logData.reason = 'Request blocked because client is inactive'; | ||
logRequest(logData); | ||
return reject(new Error(messages.clientInactive)); | ||
} | ||
if (!bypassUrls.find(u => url.includes(u)) && !initialized) { | ||
@@ -175,3 +162,2 @@ log.warn(`Request to ${url} blocked because client is not yet initialized`, { | ||
if (callbacks.onRetryableError) callbacks.onRetryableError(); | ||
errorCount++; | ||
@@ -193,3 +179,2 @@ return login().then(({trackingId, oldTrackingId}) => { | ||
if (!timedOut && response.status >= 402 && response.status !== 422 && retryCount < 4) { | ||
if (callbacks.onRetryableError) callbacks.onRetryableError(); | ||
errorCount++; | ||
@@ -226,3 +211,2 @@ retryCount++; | ||
if (!timedOut && retryCount < 4) { | ||
if (callbacks.onRetryableError) callbacks.onRetryableError(); | ||
errorCount++; | ||
@@ -229,0 +213,0 @@ retryCount++; |
@@ -65,3 +65,2 @@ // @flow | ||
onError?: (error: ?ApiError) => void, | ||
onRetryableError?: (error: ?ApiError) => void, | ||
onErrorResolved?: () => void, | ||
@@ -73,3 +72,2 @@ onConnectionStatusChange?: (connected: boolean) => void, | ||
onNewSession?: () => void, | ||
onClientInactiveTimeout?: () => void, | ||
onPersistentDataChange?: (data: PersistentData) => void, | ||
@@ -76,0 +74,0 @@ sentryMetadata?: () => Object, |
@@ -33,16 +33,19 @@ // @flow | ||
{data = {}, exception = null} = {}, | ||
shouldCapture = true, | ||
) => { | ||
const extra = Object.assign({}, QuiqChatClient && QuiqChatClient._getState(), data); | ||
if (exception) { | ||
Raven.captureException(exception, { | ||
level, | ||
logger: loggerName, | ||
extra, | ||
}); | ||
} else { | ||
Raven.captureMessage(message, { | ||
level, | ||
logger: loggerName, | ||
extra, | ||
}); | ||
if (shouldCapture) { | ||
if (exception) { | ||
Raven.captureException(exception, { | ||
level, | ||
logger: loggerName, | ||
extra, | ||
}); | ||
} else { | ||
Raven.captureMessage(message, { | ||
level, | ||
logger: loggerName, | ||
extra, | ||
}); | ||
} | ||
} | ||
@@ -49,0 +52,0 @@ rawMethod(message); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1468363
11
93
6825
272
+ Addedstubborn-fetch@^0.0.4
+ Addedstubborn-fetch@0.0.4(transitive)