@miso.ai/client-sdk-core 1.13.0 → 1.13.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -65,11 +65,14 @@ class Ask extends _base.default {
65
65
  async searchAutocomplete(payload, options) {
66
66
  return this._run(NAME.SEARCH_AUTOCOMPLETE, payload, options);
67
67
  }
68
+ async querySuggestion(payload, options) {
69
+ return this._run(NAME.QUERY_SUGGESTION, payload, options);
70
+ }
68
71
  }
69
72
  exports.default = Ask;
70
73
  class Answer extends _iterable.IdBasedIterableApiStub {
71
74
  constructor(api, response, options = {}) {
72
- super(api, '_questionGet', response.question_id, options);
75
+ super(api, '_questionGet', response.question_id, mergeCustomApiIterator(api, options));
73
76
  this._response = response;
74
77
  }
75
78
  get questionId() {
@@ -78,7 +81,7 @@ class Answer extends _iterable.IdBasedIterableApiStub {
78
81
  }
79
82
  class SearchResult extends _iterable.IdBasedIterableApiStub {
80
83
  constructor(api, response, options = {}) {
81
- super(api, '_searchGet', response.question_id, options);
84
+ super(api, '_searchGet', response.question_id, mergeCustomApiIterator(api, options));
82
85
  (0, _commons.defineValues)(this, response);
83
86
  this._response = response;
84
87
  }
@@ -90,4 +93,10 @@ class SearchResult extends _iterable.IdBasedIterableApiStub {
90
93
  // helpers //
91
94
  function isId(value) {
92
95
  return typeof value === 'string' && value.charAt(0) !== '{';
96
+ }
97
+ function mergeCustomApiIterator(api, options) {
98
+ const customIterator = api.helpers._root._customApiIterator;
99
+ return (0, _commons.trimObj)(Object.assign({
100
+ customIterator
101
+ }, options));
93
102
  }
@@ -6,35 +6,41 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _commons = require("@miso.ai/commons");
8
8
  var _constants = require("../constants.js");
9
+ const _excluded = ["method", "headers", "timeout"];
10
+ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
9
11
  class ApiHelpers {
10
12
  constructor(client, root) {
11
13
  this._client = client;
12
14
  this._root = root;
13
15
  this._bulk = new _commons.Bulk(this._runBulkFetch.bind(this), client._error.bind(client));
14
16
  }
15
- async fetch(url, payload, {
16
- method = 'POST',
17
- headers,
18
- timeout
19
- } = {}) {
17
+ async fetch(url, payload, _ref = {}) {
18
+ let {
19
+ method = 'POST',
20
+ headers,
21
+ timeout
22
+ } = _ref,
23
+ options = _objectWithoutPropertiesLoose(_ref, _excluded);
20
24
  const {
21
25
  apiKey,
22
26
  request = {}
23
27
  } = this._client.options;
24
28
  timeout = timeout || request.timeout;
25
29
 
26
- // TODO: external abort signal
27
30
  // TODO: organize arguments
28
31
  const body = method !== 'GET' && payload != undefined ? JSON.stringify(payload) : undefined;
32
+
33
+ // TODO: merge signal from options if any
29
34
  const signal = timeout ? AbortSignal.timeout(timeout) : undefined;
30
- const res = await (this._root._customFetch || window.fetch)(url, (0, _commons.trimObj)({
35
+ const res = await (this._root._customFetch || fetch)(url, (0, _commons.trimObj)(Object.assign({
31
36
  method,
32
37
  headers,
33
- body,
34
38
  cache: 'no-cache',
35
- mode: 'cors',
36
- signal
37
- }));
39
+ mode: 'cors'
40
+ }, options, {
41
+ signal,
42
+ body
43
+ })));
38
44
  const resBody = await res.json();
39
45
  if (res.status >= 400 || resBody.errors) {
40
46
  var err = new Error(resBody.message);
@@ -102,7 +108,10 @@ class ApiHelpers {
102
108
  if (method && method !== 'POST') {
103
109
  throw new Error(`Non-POST API is not supported in useBeacon mode: ${url}`);
104
110
  }
105
- const successful = this._root._customSendBeacon ? this._root._customSendBeacon(url, JSON.stringify(payload)) : window.navigator.sendBeacon(url, JSON.stringify(payload));
111
+ if (!this._root._customSendBeacon && !navigator.sendBeacon) {
112
+ throw new Error(`Send beacon is not supported in this environment.`);
113
+ }
114
+ const successful = this._root._customSendBeacon ? this._root._customSendBeacon(url, JSON.stringify(payload)) : navigator.sendBeacon(url, JSON.stringify(payload));
106
115
  if (!successful) {
107
116
  throw new Error(`Send beacon unsuccessful: ${url}`);
108
117
  }
@@ -113,7 +122,7 @@ class ApiHelpers {
113
122
  } = this._client.options;
114
123
  const apiName = paths.filter(s => s).join('/');
115
124
  const url = `${this.getApiEndpoint(apiName)}/${apiName}`;
116
- return `${url}?api_key=${window.encodeURIComponent(apiKey)}`;
125
+ return `${url}?api_key=${encodeURIComponent(apiKey)}`;
117
126
  }
118
127
  getApiEndpoint(apiName) {
119
128
  const {
@@ -27,8 +27,11 @@ class IterableApiStub {
27
27
  } = {}) => {
28
28
  // TODO: pass signal
29
29
  const response = await this.get();
30
- return [response, this._isFinished(response), this._revisionOf(response)];
30
+ return [response, isFinished(response), revisionOf(response)];
31
31
  };
32
+ if (typeof this._options.customIterator === 'function') {
33
+ return this._options.customIterator.call(this, fetch);
34
+ }
32
35
  let _this$_options = this._options,
33
36
  {
34
37
  pollingInterval: interval,
@@ -42,7 +45,7 @@ class IterableApiStub {
42
45
  const onResponse = (response, finished) => {
43
46
  if (finished) {
44
47
  stac.clear();
45
- } else if (this._isUpdated(prevResponse, response)) {
48
+ } else if (isUpdated(prevResponse, response)) {
46
49
  stac.touch();
47
50
  }
48
51
  prevResponse = response;
@@ -53,15 +56,6 @@ class IterableApiStub {
53
56
  onResponse
54
57
  }, options))[Symbol.asyncIterator]();
55
58
  }
56
- _isFinished(response) {
57
- return !!(response && response.finished);
58
- }
59
- _isUpdated(previousResponse, newResponse) {
60
- return !previousResponse || previousResponse.answer_stage !== newResponse.answer_stage || lengthOf(previousResponse.answer) !== lengthOf(newResponse.answer) || lengthOf(previousResponse.reasoning) !== lengthOf(newResponse.reasoning);
61
- }
62
- _revisionOf(response) {
63
- return response && response.revision;
64
- }
65
59
  }
66
60
  exports.IterableApiStub = IterableApiStub;
67
61
  class IdBasedIterableApiStub extends IterableApiStub {
@@ -76,6 +70,15 @@ class IdBasedIterableApiStub extends IterableApiStub {
76
70
  }
77
71
  }
78
72
  exports.IdBasedIterableApiStub = IdBasedIterableApiStub;
73
+ function isFinished(response) {
74
+ return !!(response && response.finished);
75
+ }
76
+ function isUpdated(previousResponse, newResponse) {
77
+ return !previousResponse || previousResponse.answer_stage !== newResponse.answer_stage || lengthOf(previousResponse.answer) !== lengthOf(newResponse.answer) || lengthOf(previousResponse.reasoning) !== lengthOf(newResponse.reasoning);
78
+ }
79
+ function revisionOf(response) {
80
+ return response && response.revision;
81
+ }
79
82
  function lengthOf(value = '') {
80
83
  return value.length;
81
84
  }
@@ -11,11 +11,13 @@ var _context = _interopRequireDefault(require("./context.js"));
11
11
  var helpers = _interopRequireWildcard(require("./utils.js"));
12
12
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
13
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
- const {
15
- currentScript
16
- } = document;
14
+ const currentScript = _commons.isInBrowser ? document.currentScript : undefined;
17
15
  class MisoClient extends _commons.Component {
18
16
  static attach() {
17
+ if (typeof window === 'undefined') {
18
+ console.warn(`MisoClient cannot be attached in non-browser environment.`);
19
+ return;
20
+ }
19
21
  (window.MisoClients || (window.MisoClients = [])).push(MisoClient);
20
22
  if (window.MisoClient) {
21
23
  if (window.MisoClient !== MisoClient) {
@@ -62,6 +64,10 @@ class MisoClient extends _commons.Component {
62
64
  delete options.apiHost;
63
65
  return (0, _commons.trimObj)(options);
64
66
  }
67
+
68
+ /**
69
+ * Used by shopify plugin.
70
+ */
65
71
  _readConfigFromScriptUrl() {
66
72
  const url = currentScript.src || currentScript.href; // might be <link> as well
67
73
  const params = new URL(new Request(url).url).searchParams;
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  var _commons = require("@miso.ai/commons");
8
- function _objectDestructuringEmpty(t) { if (null == t) throw new TypeError("Cannot destructure " + t); }
8
+ function _objectDestructuringEmpty(t) { if (null == t) throw new TypeError("Cannot destructure " + t); } // [browser only]
9
9
  const PLUGIN_ID = 'std:analytics';
10
10
  class AnalyticsPlugin extends _commons.Component {
11
11
  static get id() {
@@ -25,8 +25,10 @@ class AnalyticsPlugin extends _commons.Component {
25
25
  //member.config(options);
26
26
  }
27
27
  }
28
- install(MisoClient, context) {
29
- context.addSubtree(this);
28
+ install(MisoClient, {
29
+ addSubtree
30
+ }) {
31
+ addSubtree(this);
30
32
  this._engagement = new _commons.UserEngagementObserver(this._handleEngagement.bind(this));
31
33
  this._activity = new _commons.UserActivityObserver(this._handleActivity.bind(this));
32
34
  // TODO: should we provide a way to stop the observer?
@@ -136,6 +138,12 @@ class Analytics {
136
138
  this._loadingAt = undefined;
137
139
  this._readyAt = undefined;
138
140
  this.config();
141
+ this._engagedSeconds = 0;
142
+ this._engagementInterval = setInterval(() => {
143
+ if (this._plugin._engagement.engaged && this._readyAt) {
144
+ this._engagedSeconds += 1;
145
+ }
146
+ }, 1000);
139
147
  }
140
148
  _injectWorkflow(workflow) {
141
149
  this._listenToLiftcycleEvents(workflow);
@@ -163,6 +171,7 @@ class Analytics {
163
171
  this._startAt = Date.now();
164
172
  this._loadingAt = undefined;
165
173
  this._readyAt = undefined;
174
+ this._engagedSeconds = 0;
166
175
  }
167
176
  _handleLoading({
168
177
  session
@@ -190,7 +199,8 @@ class Analytics {
190
199
  analytics_timestamp: state.timestamp,
191
200
  user_engagement_time: state.userEngagementTime,
192
201
  user_idle_time: state.userIdleTime,
193
- user_waiting_time: state.userWaitingTime
202
+ user_waiting_time: state.userWaitingTime,
203
+ user_engagement_time_v2: state.userEngagementTimeV2
194
204
  }
195
205
  }
196
206
  });
@@ -208,6 +218,7 @@ class Analytics {
208
218
  let userEngagementTime = 0,
209
219
  userIdleTime = 0,
210
220
  userWaitingTime = 0;
221
+ let userEngagementTimeV2 = this._engagedSeconds * 1000;
211
222
  if (loadingAt !== undefined) {
212
223
  userWaitingTime = currentTime - loadingAt;
213
224
  }
@@ -220,7 +231,8 @@ class Analytics {
220
231
  timestamp: currentTime,
221
232
  userWaitingTime,
222
233
  userEngagementTime,
223
- userIdleTime
234
+ userIdleTime,
235
+ userEngagementTimeV2
224
236
  });
225
237
  }
226
238
  _syncEngagementState() {
@@ -114,7 +114,7 @@ function getAutoAnonymousId() {
114
114
  // 1. Cache the value for the page
115
115
  // 2. Get and set the value from cookies/localStorage is possible
116
116
  // 3. In case cookies/localStorage are disabled, generate a new value for each page
117
- return _pageBasedAutoAnonymousId || (_pageBasedAutoAnonymousId = (0, _commons.getOrComputeFromStorage)('miso_anonymous_id', _commons.uuidv4));
117
+ return _pageBasedAutoAnonymousId || (_pageBasedAutoAnonymousId = _commons.isInBrowser ? (0, _commons.getOrComputeFromStorage)('miso_anonymous_id', _commons.uuidv4) : (0, _commons.uuidv4)());
118
118
  }
119
119
  class ClientContext {
120
120
  constructor(plugin) {
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
+ var _commons = require("@miso.ai/commons");
7
8
  const PLUGIN_ID = 'std:native-fetch';
8
9
  function isNativeFunction(fn) {
9
10
  return fn.toString().indexOf("[native code]") > -1;
@@ -11,6 +12,9 @@ function isNativeFunction(fn) {
11
12
  let _fetchSource;
12
13
  let _fetchError;
13
14
  const fetchFn = (() => {
15
+ if (!_commons.isInBrowser) {
16
+ return undefined;
17
+ }
14
18
  if (isNativeFunction(window.fetch)) {
15
19
  _fetchSource = 'original';
16
20
  return window.fetch;
@@ -32,6 +36,10 @@ class NativeFetchPlugin {
32
36
  return PLUGIN_ID;
33
37
  }
34
38
  install(MisoClient, context) {
39
+ if (!_commons.isInBrowser) {
40
+ MisoClient.debug(`<plugin:${PLUGIN_ID}>`, '[info]', `Not in browser, skipped`);
41
+ return;
42
+ }
35
43
  if (!fetchFn) {
36
44
  MisoClient.debug(`<plugin:${PLUGIN_ID}>`, '[error]', `Failed to obtain native fetch: ${_fetchError.message}`);
37
45
  return;
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  var _commons = require("@miso.ai/commons");
8
+ // [browser only]
9
+
8
10
  const ID = 'std:page-info';
9
11
  class PageInfoPlugin {
10
12
  static get id() {
@@ -16,7 +16,6 @@ class PluginRoot extends _commons.Registry {
16
16
  keyName: 'id'
17
17
  });
18
18
  this._root = root;
19
- this._currentScript = document.currentScript;
20
19
  this._events._replays.add('install').add('subtree');
21
20
  this._installed = {};
22
21
  this._installedResolutions = {};
@@ -86,6 +85,9 @@ class PluginRoot extends _commons.Registry {
86
85
  this._subtrees.push(component);
87
86
  this._events.emit('subtree', component);
88
87
  }
88
+ addExtensionPoints(obj) {
89
+ (0, _commons.defineValues)(this.context, obj);
90
+ }
89
91
  addPayloadPass(pass) {
90
92
  if (typeof pass !== 'function') {
91
93
  throw new Error(`Expect parameter to be a function: ${pass}`);
@@ -116,6 +118,12 @@ class PluginRoot extends _commons.Registry {
116
118
  }
117
119
  this._root._customSendBeacon = sendBeacon;
118
120
  }
121
+ setCustomApiIterator(customApiIterator) {
122
+ if (typeof customApiIterator !== 'function') {
123
+ throw new Error(`Expect parameter to be a function: ${customApiIterator}`);
124
+ }
125
+ this._root._customApiIterator = customApiIterator;
126
+ }
119
127
  onHubUpdate(callback) {
120
128
  if (typeof callback !== 'function') {
121
129
  throw new Error(`Expect parameter to be a function: ${callback}`);
@@ -199,7 +207,7 @@ class PluginRoot extends _commons.Registry {
199
207
  }
200
208
  }
201
209
  async _tryRegisterRemotely(id) {
202
- if (id.startsWith('std:')) {
210
+ if (id.startsWith('std:') && _commons.isInBrowser) {
203
211
  try {
204
212
  await this._registerStdRemotely(id);
205
213
  return;
@@ -210,6 +218,11 @@ class PluginRoot extends _commons.Registry {
210
218
  // TODO: try as URL
211
219
  throw new Error(`Cannot register plugin remotely: ${id}`);
212
220
  }
221
+
222
+ /**
223
+ * [browser only]
224
+ * @param {string} id
225
+ */
213
226
  async _registerStdRemotely(id) {
214
227
  const {
215
228
  version
@@ -221,7 +234,7 @@ class PluginRoot extends _commons.Registry {
221
234
  // skip if already exists
222
235
  const script = document.createElement('script');
223
236
  script.async = true;
224
- script.src = (0, _urls.getPluginScriptUrl)(id, version, this._currentScript);
237
+ script.src = (0, _urls.getPluginScriptUrl)(id, version);
225
238
  script.setAttribute('data-request', request);
226
239
  document.head.appendChild(script);
227
240
  // TODO: hook up error event
@@ -232,14 +245,15 @@ class PluginRoot extends _commons.Registry {
232
245
  !this.isRegistered(id) && this.register(Plugin);
233
246
  }
234
247
  }
248
+ exports.default = PluginRoot;
249
+ const PLUGIN_QUERY_METHODS = ['installed', 'registered', 'isInstalled', 'isRegistered', 'whenInstalled', 'whenRegistered'];
235
250
 
236
251
  /**
237
252
  * The interface of extended features only for plugin developers.
238
253
  */
239
- exports.default = PluginRoot;
240
254
  class PluginContext {
241
255
  constructor(root, classes) {
242
- (0, _commons.delegateGetters)(this, root, ['getPluginClass', 'addSubtree', 'addPayloadPass', 'addHeadersPass', 'addUrlPass', 'setCustomFetch', 'setCustomSendBeacon', 'onHubUpdate', 'onHubEmit', 'whenInstalled', 'whenRegistered']);
256
+ (0, _commons.delegateGetters)(this, root, [...PLUGIN_QUERY_METHODS, 'getPluginClass', 'addSubtree', 'addExtensionPoints', 'addPayloadPass', 'addHeadersPass', 'addUrlPass', 'setCustomFetch', 'setCustomSendBeacon', 'setCustomApiIterator', 'onHubUpdate', 'onHubEmit']);
243
257
  (0, _commons.defineValues)(this, {
244
258
  classes
245
259
  });
@@ -251,6 +265,6 @@ class PluginContext {
251
265
  */
252
266
  class Plugins {
253
267
  constructor(root) {
254
- (0, _commons.delegateGetters)(this, root, ['installed', 'registered', 'isInstalled', 'isRegistered', 'whenInstalled', 'whenRegistered', 'get', 'register', 'use', 'install']);
268
+ (0, _commons.delegateGetters)(this, root, [...PLUGIN_QUERY_METHODS, 'get', 'register', 'use', 'install']);
255
269
  }
256
270
  }
@@ -4,10 +4,15 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getPluginScriptUrl = getPluginScriptUrl;
7
- function getPluginScriptUrl(id, version, currentScript) {
7
+ var _commons = require("@miso.ai/commons");
8
+ const currentScript = _commons.isInBrowser ? document.currentScript : undefined;
9
+ function getPluginScriptUrl(id, version) {
8
10
  // TODO: look up by manifest
9
11
  const pkgName = id.slice(4);
10
12
  if (version === 'dev') {
13
+ if (!currentScript) {
14
+ throw new Error(`Cannot get plugin script URL for ${id}: no currentScript.`);
15
+ }
11
16
  const src = currentScript.src;
12
17
  const searchIndex = src.indexOf('?');
13
18
  const k = searchIndex < 0 ? src.length : searchIndex;
@@ -4,4 +4,4 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _default = exports.default = '1.13.0';
7
+ var _default = exports.default = '1.13.1-beta.1';
package/package.json CHANGED
@@ -30,7 +30,7 @@
30
30
  "url": "https://github.com/MisoAI/miso-client-js-sdk/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@miso.ai/commons": "1.13.0"
33
+ "@miso.ai/commons": "1.13.1-beta.1"
34
34
  },
35
- "version": "1.13.0"
35
+ "version": "1.13.1-beta.1"
36
36
  }
package/src/api/ask.js CHANGED
@@ -1,4 +1,4 @@
1
- import { API, defineValues } from '@miso.ai/commons';
1
+ import { API, defineValues, trimObj } from '@miso.ai/commons';
2
2
  import ApiBase from './base.js';
3
3
  import { IdBasedIterableApiStub } from './iterable.js';
4
4
 
@@ -60,12 +60,16 @@ export default class Ask extends ApiBase {
60
60
  return this._run(NAME.SEARCH_AUTOCOMPLETE, payload, options);
61
61
  }
62
62
 
63
+ async querySuggestion(payload, options) {
64
+ return this._run(NAME.QUERY_SUGGESTION, payload, options);
65
+ }
66
+
63
67
  }
64
68
 
65
69
  class Answer extends IdBasedIterableApiStub {
66
70
 
67
71
  constructor(api, response, options = {}) {
68
- super(api, '_questionGet', response.question_id, options);
72
+ super(api, '_questionGet', response.question_id, mergeCustomApiIterator(api, options));
69
73
  this._response = response;
70
74
  }
71
75
 
@@ -78,7 +82,7 @@ class Answer extends IdBasedIterableApiStub {
78
82
  class SearchResult extends IdBasedIterableApiStub {
79
83
 
80
84
  constructor(api, response, options = {}) {
81
- super(api, '_searchGet', response.question_id, options);
85
+ super(api, '_searchGet', response.question_id, mergeCustomApiIterator(api, options));
82
86
  defineValues(this, response);
83
87
  this._response = response;
84
88
  }
@@ -93,3 +97,11 @@ class SearchResult extends IdBasedIterableApiStub {
93
97
  function isId(value) {
94
98
  return typeof value === 'string' && value.charAt(0) !== '{';
95
99
  }
100
+
101
+ function mergeCustomApiIterator(api, options) {
102
+ const customIterator = api.helpers._root._customApiIterator;
103
+ return trimObj({
104
+ customIterator,
105
+ ...options,
106
+ });
107
+ }
@@ -13,23 +13,25 @@ export default class ApiHelpers {
13
13
  method = 'POST',
14
14
  headers,
15
15
  timeout,
16
+ ...options
16
17
  } = {}) {
17
18
  const { apiKey, request = {} } = this._client.options;
18
19
  timeout = timeout || request.timeout;
19
20
 
20
- // TODO: external abort signal
21
21
  // TODO: organize arguments
22
22
  const body = method !== 'GET' && payload != undefined ? JSON.stringify(payload) : undefined;
23
23
 
24
+ // TODO: merge signal from options if any
24
25
  const signal = timeout ? AbortSignal.timeout(timeout) : undefined;
25
26
 
26
- const res = await (this._root._customFetch || window.fetch)(url, trimObj({
27
+ const res = await (this._root._customFetch || fetch)(url, trimObj({
27
28
  method,
28
29
  headers,
29
- body,
30
30
  cache: 'no-cache',
31
31
  mode: 'cors',
32
+ ...options,
32
33
  signal,
34
+ body,
33
35
  }));
34
36
 
35
37
  const resBody = await res.json();
@@ -78,9 +80,12 @@ export default class ApiHelpers {
78
80
  if (method && method !== 'POST') {
79
81
  throw new Error(`Non-POST API is not supported in useBeacon mode: ${url}`);
80
82
  }
83
+ if (!this._root._customSendBeacon && !navigator.sendBeacon) {
84
+ throw new Error(`Send beacon is not supported in this environment.`);
85
+ }
81
86
  const successful = this._root._customSendBeacon ?
82
87
  this._root._customSendBeacon(url, JSON.stringify(payload)) :
83
- window.navigator.sendBeacon(url, JSON.stringify(payload));
88
+ navigator.sendBeacon(url, JSON.stringify(payload));
84
89
  if (!successful) {
85
90
  throw new Error(`Send beacon unsuccessful: ${url}`);
86
91
  }
@@ -90,7 +95,7 @@ export default class ApiHelpers {
90
95
  const { apiKey } = this._client.options;
91
96
  const apiName = paths.filter(s => s).join('/');
92
97
  const url = `${this.getApiEndpoint(apiName)}/${apiName}`;
93
- return `${url}?api_key=${window.encodeURIComponent(apiKey)}`;
98
+ return `${url}?api_key=${encodeURIComponent(apiKey)}`;
94
99
  }
95
100
 
96
101
  getApiEndpoint(apiName) {
@@ -23,8 +23,13 @@ export class IterableApiStub {
23
23
  const fetch = async ({ signal } = {}) => {
24
24
  // TODO: pass signal
25
25
  const response = await this.get();
26
- return [response, this._isFinished(response), this._revisionOf(response)];
26
+ return [response, isFinished(response), revisionOf(response)];
27
27
  };
28
+
29
+ if (typeof this._options.customIterator === 'function') {
30
+ return this._options.customIterator.call(this, fetch);
31
+ }
32
+
28
33
  let { pollingInterval: interval, signal, stallTimeout = 120000, ...options } = this._options;
29
34
  const stac = new StallTimeoutAbortController(stallTimeout);
30
35
  signal = signals.any(this._ac.signal, stac.signal, signal);
@@ -33,7 +38,7 @@ export class IterableApiStub {
33
38
  const onResponse = (response, finished) => {
34
39
  if (finished) {
35
40
  stac.clear();
36
- } else if (this._isUpdated(prevResponse, response)) {
41
+ } else if (isUpdated(prevResponse, response)) {
37
42
  stac.touch();
38
43
  }
39
44
  prevResponse = response;
@@ -41,21 +46,6 @@ export class IterableApiStub {
41
46
  return polling(fetch, { interval, signal, onResponse, ...options })[Symbol.asyncIterator]();
42
47
  }
43
48
 
44
- _isFinished(response) {
45
- return !!(response && response.finished);
46
- }
47
-
48
- _isUpdated(previousResponse, newResponse) {
49
- return !previousResponse ||
50
- previousResponse.answer_stage !== newResponse.answer_stage ||
51
- lengthOf(previousResponse.answer) !== lengthOf(newResponse.answer) ||
52
- lengthOf(previousResponse.reasoning) !== lengthOf(newResponse.reasoning);
53
- }
54
-
55
- _revisionOf(response) {
56
- return response && response.revision;
57
- }
58
-
59
49
  }
60
50
 
61
51
  export class IdBasedIterableApiStub extends IterableApiStub {
@@ -73,6 +63,21 @@ export class IdBasedIterableApiStub extends IterableApiStub {
73
63
 
74
64
  }
75
65
 
66
+ function isFinished(response) {
67
+ return !!(response && response.finished);
68
+ }
69
+
70
+ function isUpdated(previousResponse, newResponse) {
71
+ return !previousResponse ||
72
+ previousResponse.answer_stage !== newResponse.answer_stage ||
73
+ lengthOf(previousResponse.answer) !== lengthOf(newResponse.answer) ||
74
+ lengthOf(previousResponse.reasoning) !== lengthOf(newResponse.reasoning);
75
+ }
76
+
77
+ function revisionOf(response) {
78
+ return response && response.revision;
79
+ }
80
+
76
81
  function lengthOf(value = '') {
77
82
  return value.length;
78
83
  }
package/src/client.js CHANGED
@@ -1,14 +1,18 @@
1
- import { trimObj, Component, uuidv4, defineValues } from '@miso.ai/commons';
1
+ import { trimObj, Component, uuidv4, defineValues, isInBrowser } from '@miso.ai/commons';
2
2
  import { root, register, init } from './root.js';
3
3
  import { Api } from './api/index.js';
4
4
  import Context from './context.js';
5
5
  import * as helpers from './utils.js';
6
6
 
7
- const { currentScript } = document;
7
+ const currentScript = isInBrowser ? document.currentScript : undefined;
8
8
 
9
9
  class MisoClient extends Component {
10
10
 
11
11
  static attach() {
12
+ if (typeof window === 'undefined') {
13
+ console.warn(`MisoClient cannot be attached in non-browser environment.`);
14
+ return;
15
+ }
12
16
  (window.MisoClients || (window.MisoClients = [])).push(MisoClient);
13
17
  if (window.MisoClient) {
14
18
  if (window.MisoClient !== MisoClient) {
@@ -65,6 +69,9 @@ class MisoClient extends Component {
65
69
  return trimObj(options);
66
70
  }
67
71
 
72
+ /**
73
+ * Used by shopify plugin.
74
+ */
68
75
  _readConfigFromScriptUrl() {
69
76
  const url = currentScript.src || currentScript.href; // might be <link> as well
70
77
  const params = new URL(new Request(url).url).searchParams;
@@ -1,3 +1,5 @@
1
+ // [browser only]
2
+
1
3
  import { Component, UserEngagementObserver, UserActivityObserver, Stopwatch, trimObj, mergeInteractions } from '@miso.ai/commons';
2
4
 
3
5
  const PLUGIN_ID = 'std:analytics';
@@ -24,8 +26,8 @@ export default class AnalyticsPlugin extends Component {
24
26
  }
25
27
  }
26
28
 
27
- install(MisoClient, context) {
28
- context.addSubtree(this);
29
+ install(MisoClient, { addSubtree }) {
30
+ addSubtree(this);
29
31
  this._engagement = new UserEngagementObserver(this._handleEngagement.bind(this));
30
32
  this._activity = new UserActivityObserver(this._handleActivity.bind(this));
31
33
  // TODO: should we provide a way to stop the observer?
@@ -150,6 +152,13 @@ class Analytics {
150
152
  this._readyAt = undefined;
151
153
 
152
154
  this.config();
155
+
156
+ this._engagedSeconds = 0;
157
+ this._engagementInterval = setInterval(() => {
158
+ if (this._plugin._engagement.engaged && this._readyAt) {
159
+ this._engagedSeconds += 1;
160
+ }
161
+ }, 1000);
153
162
  }
154
163
 
155
164
  _injectWorkflow(workflow) {
@@ -183,6 +192,8 @@ class Analytics {
183
192
  this._startAt = Date.now();
184
193
  this._loadingAt = undefined;
185
194
  this._readyAt = undefined;
195
+
196
+ this._engagedSeconds = 0;
186
197
  }
187
198
 
188
199
  _handleLoading({ session } = {}) {
@@ -209,6 +220,7 @@ class Analytics {
209
220
  user_engagement_time: state.userEngagementTime,
210
221
  user_idle_time: state.userIdleTime,
211
222
  user_waiting_time: state.userWaitingTime,
223
+ user_engagement_time_v2: state.userEngagementTimeV2,
212
224
  },
213
225
  },
214
226
  });
@@ -229,6 +241,7 @@ class Analytics {
229
241
  const loadingAt = this._loadingAt;
230
242
  const readyAt = this._readyAt;
231
243
  let userEngagementTime = 0, userIdleTime = 0, userWaitingTime = 0;
244
+ let userEngagementTimeV2 = this._engagedSeconds * 1000;
232
245
 
233
246
  if (loadingAt !== undefined) {
234
247
  userWaitingTime = currentTime - loadingAt;
@@ -244,6 +257,7 @@ class Analytics {
244
257
  userWaitingTime,
245
258
  userEngagementTime,
246
259
  userIdleTime,
260
+ userEngagementTimeV2,
247
261
  });
248
262
  }
249
263
 
@@ -1,4 +1,4 @@
1
- import { API, uuidv4, Component, delegateProperties, getOrComputeFromStorage, mergeInteractions, mergeApiPayloads } from '@miso.ai/commons';
1
+ import { API, uuidv4, Component, delegateProperties, getOrComputeFromStorage, mergeInteractions, mergeApiPayloads, isInBrowser } from '@miso.ai/commons';
2
2
 
3
3
  const { GROUP, NAME } = API;
4
4
 
@@ -73,7 +73,7 @@ function getAutoAnonymousId() {
73
73
  // 1. Cache the value for the page
74
74
  // 2. Get and set the value from cookies/localStorage is possible
75
75
  // 3. In case cookies/localStorage are disabled, generate a new value for each page
76
- return _pageBasedAutoAnonymousId || (_pageBasedAutoAnonymousId = getOrComputeFromStorage('miso_anonymous_id', uuidv4));
76
+ return _pageBasedAutoAnonymousId || (_pageBasedAutoAnonymousId = isInBrowser ? getOrComputeFromStorage('miso_anonymous_id', uuidv4) : uuidv4());
77
77
  }
78
78
 
79
79
  class ClientContext {
@@ -1,3 +1,5 @@
1
+ import { isInBrowser } from '@miso.ai/commons';
2
+
1
3
  const PLUGIN_ID = 'std:native-fetch';
2
4
 
3
5
  function isNativeFunction(fn) {
@@ -8,6 +10,9 @@ let _fetchSource;
8
10
  let _fetchError;
9
11
 
10
12
  const fetchFn = (() => {
13
+ if (!isInBrowser) {
14
+ return undefined;
15
+ }
11
16
  if (isNativeFunction(window.fetch)) {
12
17
  _fetchSource = 'original';
13
18
  return window.fetch;
@@ -32,6 +37,10 @@ export default class NativeFetchPlugin {
32
37
  }
33
38
 
34
39
  install(MisoClient, context) {
40
+ if (!isInBrowser) {
41
+ MisoClient.debug(`<plugin:${PLUGIN_ID}>`, '[info]', `Not in browser, skipped`);
42
+ return;
43
+ }
35
44
  if (!fetchFn) {
36
45
  MisoClient.debug(`<plugin:${PLUGIN_ID}>`, '[error]', `Failed to obtain native fetch: ${_fetchError.message}`);
37
46
  return;
@@ -1,3 +1,5 @@
1
+ // [browser only]
2
+
1
3
  import { trimObj } from '@miso.ai/commons';
2
4
 
3
5
  const ID = 'std:page-info';
@@ -1,4 +1,4 @@
1
- import { delegateGetters, defineValues, Registry, Resolution, Resources } from '@miso.ai/commons';
1
+ import { isInBrowser, delegateGetters, defineValues, Registry, Resolution, Resources } from '@miso.ai/commons';
2
2
  import classes from '../classes.js';
3
3
  import AnonymousPlugin from './anonymous.js';
4
4
  import { getPluginScriptUrl } from './urls.js';
@@ -11,7 +11,6 @@ export default class PluginRoot extends Registry {
11
11
  keyName: 'id',
12
12
  });
13
13
  this._root = root;
14
- this._currentScript = document.currentScript;
15
14
  this._events._replays.add('install').add('subtree');
16
15
  this._installed = {};
17
16
  this._installedResolutions = {};
@@ -87,6 +86,10 @@ export default class PluginRoot extends Registry {
87
86
  this._events.emit('subtree', component);
88
87
  }
89
88
 
89
+ addExtensionPoints(obj) {
90
+ defineValues(this.context, obj);
91
+ }
92
+
90
93
  addPayloadPass(pass) {
91
94
  if (typeof pass !== 'function') {
92
95
  throw new Error(`Expect parameter to be a function: ${pass}`);
@@ -122,6 +125,13 @@ export default class PluginRoot extends Registry {
122
125
  this._root._customSendBeacon = sendBeacon;
123
126
  }
124
127
 
128
+ setCustomApiIterator(customApiIterator) {
129
+ if (typeof customApiIterator !== 'function') {
130
+ throw new Error(`Expect parameter to be a function: ${customApiIterator}`);
131
+ }
132
+ this._root._customApiIterator = customApiIterator;
133
+ }
134
+
125
135
  onHubUpdate(callback) {
126
136
  if (typeof callback !== 'function') {
127
137
  throw new Error(`Expect parameter to be a function: ${callback}`);
@@ -210,7 +220,7 @@ export default class PluginRoot extends Registry {
210
220
  }
211
221
 
212
222
  async _tryRegisterRemotely(id) {
213
- if (id.startsWith('std:')) {
223
+ if (id.startsWith('std:') && isInBrowser) {
214
224
  try {
215
225
  await this._registerStdRemotely(id);
216
226
  return;
@@ -222,6 +232,10 @@ export default class PluginRoot extends Registry {
222
232
  throw new Error(`Cannot register plugin remotely: ${id}`);
223
233
  }
224
234
 
235
+ /**
236
+ * [browser only]
237
+ * @param {string} id
238
+ */
225
239
  async _registerStdRemotely(id) {
226
240
  const { version } = this._root;
227
241
  const request = version === 'dev' ? `plugin:${id}@dev:${this.MisoClient.uuid}` : `plugin:${id}@${version}`;
@@ -231,7 +245,7 @@ export default class PluginRoot extends Registry {
231
245
  // skip if already exists
232
246
  const script = document.createElement('script');
233
247
  script.async = true;
234
- script.src = getPluginScriptUrl(id, version, this._currentScript);
248
+ script.src = getPluginScriptUrl(id, version);
235
249
  script.setAttribute('data-request', request);
236
250
  document.head.appendChild(script);
237
251
  // TODO: hook up error event
@@ -244,13 +258,30 @@ export default class PluginRoot extends Registry {
244
258
 
245
259
  }
246
260
 
261
+ const PLUGIN_QUERY_METHODS = [
262
+ 'installed', 'registered', 'isInstalled', 'isRegistered', 'whenInstalled', 'whenRegistered'
263
+ ];
264
+
247
265
  /**
248
266
  * The interface of extended features only for plugin developers.
249
267
  */
250
268
  class PluginContext {
251
269
 
252
270
  constructor(root, classes) {
253
- delegateGetters(this, root, ['getPluginClass', 'addSubtree', 'addPayloadPass', 'addHeadersPass', 'addUrlPass', 'setCustomFetch', 'setCustomSendBeacon', 'onHubUpdate', 'onHubEmit', 'whenInstalled', 'whenRegistered']);
271
+ delegateGetters(this, root, [
272
+ ...PLUGIN_QUERY_METHODS,
273
+ 'getPluginClass',
274
+ 'addSubtree',
275
+ 'addExtensionPoints',
276
+ 'addPayloadPass',
277
+ 'addHeadersPass',
278
+ 'addUrlPass',
279
+ 'setCustomFetch',
280
+ 'setCustomSendBeacon',
281
+ 'setCustomApiIterator',
282
+ 'onHubUpdate',
283
+ 'onHubEmit',
284
+ ]);
254
285
  defineValues(this, { classes });
255
286
  }
256
287
 
@@ -262,7 +293,13 @@ class PluginContext {
262
293
  class Plugins {
263
294
 
264
295
  constructor(root) {
265
- delegateGetters(this, root, ['installed', 'registered', 'isInstalled', 'isRegistered', 'whenInstalled', 'whenRegistered', 'get', 'register', 'use', 'install']);
296
+ delegateGetters(this, root, [
297
+ ...PLUGIN_QUERY_METHODS,
298
+ 'get',
299
+ 'register',
300
+ 'use',
301
+ 'install',
302
+ ]);
266
303
  }
267
304
 
268
305
  }
@@ -1,8 +1,15 @@
1
- export function getPluginScriptUrl(id, version, currentScript) {
1
+ import { isInBrowser } from '@miso.ai/commons';
2
+
3
+ const currentScript = isInBrowser ? document.currentScript : undefined;
4
+
5
+ export function getPluginScriptUrl(id, version) {
2
6
  // TODO: look up by manifest
3
7
  const pkgName = id.slice(4);
4
8
 
5
9
  if (version === 'dev') {
10
+ if (!currentScript) {
11
+ throw new Error(`Cannot get plugin script URL for ${id}: no currentScript.`);
12
+ }
6
13
  const src = currentScript.src;
7
14
  const searchIndex = src.indexOf('?');
8
15
  const k = searchIndex < 0 ? src.length : searchIndex;
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '1.13.0';
1
+ export default '1.13.1-beta.1';