@fleetbase/ember-core 0.2.13 → 0.2.15

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.
@@ -0,0 +1,39 @@
1
+ import { Ability } from 'ember-can';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { singularize } from 'ember-inflector';
5
+
6
+ export default class extends Ability {
7
+ @service currentUser;
8
+ @tracked service;
9
+ @tracked resource;
10
+ @tracked ability;
11
+ @tracked permissions = new Set();
12
+
13
+ constructor() {
14
+ super(...arguments);
15
+ this.permissions = new Set(this.currentUser.permissions.map((permission) => permission.name));
16
+ }
17
+
18
+ parseProperty(str) {
19
+ let [service, ability, resource] = str.split(' ');
20
+
21
+ this.service = service;
22
+ this.ability = ability;
23
+ this.resource = singularize(resource);
24
+
25
+ return 'can';
26
+ }
27
+
28
+ get can() {
29
+ if (this.currentUser.isAdmin) {
30
+ return true;
31
+ }
32
+
33
+ const permission = [this.service, this.ability, this.resource].join(' ');
34
+ const wilcardPermission = [this.service, '*', this.resource].join(' ');
35
+ const wildcardServicePermission = [this.service, '*'].join(' ');
36
+
37
+ return this.permissions.has(permission) || this.permissions.has(wilcardPermission) || this.permissions.has(wildcardServicePermission);
38
+ }
39
+ }
@@ -0,0 +1,25 @@
1
+ import { decoratorWithRequiredParams } from '@ember-decorators/utils/decorator';
2
+ import { computed } from '@ember/object';
3
+ import { assert } from '@ember/debug';
4
+ import injectEngineService from '../utils/inject-engine-service';
5
+ import isObject from '../utils/is-object';
6
+
7
+ export default decoratorWithRequiredParams(function (target, key, descriptor, [engineName, options = {}]) {
8
+ assert('The first argument of the @engineService decorator must be a string', typeof engineName === 'string');
9
+ assert('The second argument of the @engineService decorator must be an object', isObject(options));
10
+
11
+ const { initializer } = descriptor;
12
+ delete descriptor.initializer;
13
+
14
+ const cp = computed(`_engineService_${key}`, function () {
15
+ const service = injectEngineService(this, engineName, key, options);
16
+
17
+ if (initializer) {
18
+ return initializer.call(this);
19
+ }
20
+
21
+ return service;
22
+ });
23
+
24
+ return cp(target, key, descriptor);
25
+ });
@@ -1,55 +1,52 @@
1
1
  import { decoratorWithRequiredParams } from '@ember-decorators/utils/decorator';
2
- import { assert } from '@ember/debug';
3
2
  import { getOwner } from '@ember/application';
4
- import { scheduleOnce } from '@ember/runloop';
3
+ import { assert } from '@ember/debug';
5
4
 
6
- export default function fetchFrom(endpoint, query = {}, options = {}) {
5
+ export default decoratorWithRequiredParams(function (target, key, descriptor, [endpoint, query = {}, options = {}]) {
7
6
  assert('The first argument of the @fetchFrom decorator must be a string', typeof endpoint === 'string');
8
7
  assert('The second argument of the @fetchFrom decorator must be an object', typeof query === 'object');
9
8
  assert('The third argument of the @fetchFrom decorator must be an object', typeof options === 'object');
10
9
 
11
- return decoratorWithRequiredParams(function (target, key) {
12
- const symbol = Symbol(`__${key}_fetchFrom`);
10
+ // Remove value and writable if previously set, use getter instead
11
+ delete descriptor.value;
12
+ delete descriptor.writable;
13
+ delete descriptor.initializer;
14
+
15
+ // Create symbol to track value
16
+ const symbol = Symbol(`__${key}_fetchFrom`);
17
+
18
+ // Setter to get symbol value
19
+ descriptor.set = function (value) {
20
+ this[symbol] = value;
21
+ };
22
+
23
+ // Get or set symbol value
24
+ descriptor.get = async function () {
25
+ if (this[symbol] !== undefined) {
26
+ return this[symbol];
27
+ }
13
28
 
14
- Object.defineProperty(target, symbol, {
29
+ Object.defineProperty(this, symbol, {
15
30
  configurable: true,
16
31
  enumerable: false,
17
32
  writable: true,
18
33
  value: null,
19
34
  });
20
35
 
21
- Object.defineProperty(target, key, {
22
- configurable: true,
23
- enumerable: true,
24
- get() {
25
- return this[symbol];
26
- },
27
- set(value) {
28
- this[symbol] = value;
29
- },
30
- });
31
-
32
- const originalInit = target.init;
33
-
34
- target.init = function () {
35
- if (originalInit) {
36
- originalInit.call(this);
37
- }
38
-
39
- scheduleOnce('afterRender', this, function () {
40
- const owner = getOwner(this);
41
- const fetch = owner.lookup('service:fetch'); // Get the Fleetbase Fetch service
42
-
43
- // Perform the query and set the result to the property
44
- fetch
45
- .get(endpoint, query, options)
46
- .then((result) => {
47
- this.set(key, result);
48
- })
49
- .catch(() => {
50
- this.set(key, []);
51
- });
36
+ const owner = getOwner(this);
37
+ const fetch = owner.lookup('service:fetch');
38
+ return fetch
39
+ .get(endpoint, query, options)
40
+ .then((response) => {
41
+ this.set(key, response);
42
+ if (options && typeof options.onComplete === 'function') {
43
+ options.onComplete(response, this);
44
+ }
45
+ })
46
+ .catch(() => {
47
+ this.set(key, null);
52
48
  });
53
- };
54
- }, 'fetchFrom')(endpoint, query, options);
55
- }
49
+ };
50
+
51
+ return descriptor;
52
+ });
@@ -1,55 +1,52 @@
1
1
  import { decoratorWithRequiredParams } from '@ember-decorators/utils/decorator';
2
- import { assert } from '@ember/debug';
3
2
  import { getOwner } from '@ember/application';
4
- import { scheduleOnce } from '@ember/runloop';
3
+ import { assert } from '@ember/debug';
4
+
5
+ export default decoratorWithRequiredParams(function (target, key, descriptor, [modelName, query = {}, options = {}]) {
6
+ assert('The first argument of the @fetchFrom decorator must be a string', typeof modelName === 'string');
7
+ assert('The second argument of the @fetchFrom decorator must be an object', typeof query === 'object');
8
+ assert('The third argument of the @fetchFrom decorator must be an object', typeof options === 'object');
9
+
10
+ // Remove value and writable if previously set, use getter instead
11
+ delete descriptor.value;
12
+ delete descriptor.writable;
13
+ delete descriptor.initializer;
14
+
15
+ // Create symbol to track value
16
+ const symbol = Symbol(`__${key}_fromStore`);
5
17
 
6
- export default function fromStore(modelName, query = {}, options = {}) {
7
- assert('The first argument of the @fromStore decorator must be a string', typeof modelName === 'string');
8
- assert('The second argument of the @fromStore decorator must be an object', typeof query === 'object');
9
- assert('The third argument of the @fromStore decorator must be an object', typeof options === 'object');
18
+ // Setter to get symbol value
19
+ descriptor.set = function (value) {
20
+ this[symbol] = value;
21
+ };
10
22
 
11
- return decoratorWithRequiredParams(function (target, key) {
12
- const symbol = Symbol(`__${key}_fromStore`);
23
+ // Get or set symbol value
24
+ descriptor.get = function () {
25
+ if (this[symbol] !== undefined) {
26
+ return this[symbol];
27
+ }
13
28
 
14
- Object.defineProperty(target, symbol, {
29
+ Object.defineProperty(this, symbol, {
15
30
  configurable: true,
16
31
  enumerable: false,
17
32
  writable: true,
18
33
  value: null,
19
34
  });
20
35
 
21
- Object.defineProperty(target, key, {
22
- configurable: true,
23
- enumerable: true,
24
- get() {
25
- return this[symbol];
26
- },
27
- set(value) {
28
- this[symbol] = value;
29
- },
30
- });
31
-
32
- const originalInit = target.init;
33
-
34
- target.init = function () {
35
- if (originalInit) {
36
- originalInit.call(this);
37
- }
38
-
39
- scheduleOnce('afterRender', this, function () {
40
- const owner = getOwner(this);
41
- const store = owner.lookup('service:store'); // Get the Ember Data store
42
-
43
- // Perform the query and set the result to the property
44
- store
45
- .query(modelName, query, options)
46
- .then((result) => {
47
- this.set(key, result);
48
- })
49
- .catch(() => {
50
- this.set(key, []);
51
- });
36
+ const owner = getOwner(this);
37
+ const store = owner.lookup('service:store');
38
+ return store
39
+ .query(modelName, query, options)
40
+ .then((response) => {
41
+ this.set(key, response);
42
+ if (options && typeof options.onComplete === 'function') {
43
+ options.onComplete(response, this);
44
+ }
45
+ })
46
+ .catch(() => {
47
+ this.set(key, null);
52
48
  });
53
- };
54
- }, 'fromStore')(modelName, query, options);
55
- }
49
+ };
50
+
51
+ return descriptor;
52
+ });
@@ -15,6 +15,7 @@ const hostServices = [
15
15
  'fileQueue',
16
16
  'universe',
17
17
  'intl',
18
+ 'abilities',
18
19
  { hostRouter: 'router' },
19
20
  ];
20
21
 
@@ -16,6 +16,7 @@ const services = [
16
16
  'fileQueue',
17
17
  'universe',
18
18
  'intl',
19
+ 'abilities',
19
20
  ];
20
21
 
21
22
  export default services;
@@ -0,0 +1,7 @@
1
+ import Service from 'ember-can/services/abilities';
2
+
3
+ export default class AbilitiesService extends Service {
4
+ parse(propertyName) {
5
+ return { propertyName, abilityName: 'dynamic' };
6
+ }
7
+ }
@@ -54,6 +54,13 @@ export default class CurrentUserService extends Service.extend(Evented) {
54
54
  id: 'anon',
55
55
  };
56
56
 
57
+ /**
58
+ * The current users permissions.
59
+ *
60
+ * @memberof CurrentUserService
61
+ */
62
+ @tracked permissions = [];
63
+
57
64
  /**
58
65
  * User options in localStorage
59
66
  *
@@ -96,36 +103,82 @@ export default class CurrentUserService extends Service.extend(Evented) {
96
103
  */
97
104
  @alias('user.company_uuid') companyId;
98
105
 
106
+ /**
107
+ * Alias for if user is admin.
108
+ *
109
+ * @var {Boolean}
110
+ * @memberof CurrentUserService
111
+ */
112
+ @alias('user.is_admin') isAdmin;
113
+
114
+ /**
115
+ * The prefix for this user options
116
+ *
117
+ * @var {String}
118
+ */
119
+ @computed('id') get optionsPrefix() {
120
+ return `${this.id}:`;
121
+ }
122
+
123
+ get latitude() {
124
+ return this.whois('latitude');
125
+ }
126
+
127
+ get longitude() {
128
+ return this.whois('longitude');
129
+ }
130
+
131
+ get currency() {
132
+ return this.whois('currency.code');
133
+ }
134
+
135
+ get city() {
136
+ return this.whois('city');
137
+ }
138
+
139
+ get country() {
140
+ return this.whois('country_code');
141
+ }
142
+
99
143
  /**
100
144
  * Loads the current authenticated user
101
145
  *
102
- * @void
146
+ * @return Promise<UserModel>|null
103
147
  */
104
148
  async load() {
105
149
  if (this.session.isAuthenticated) {
106
150
  let user = await this.store.findRecord('user', 'me');
107
151
  this.set('user', user);
108
152
  this.trigger('user.loaded', user);
153
+
154
+ // Set permissions
155
+ this.permissions = this.getUserPermissions(user);
156
+
157
+ return user;
109
158
  }
159
+
160
+ return null;
110
161
  }
111
162
 
112
163
  /**
113
164
  * Resolves a user model.
114
165
  *
115
- * @return {Promise}
166
+ * @return {Promise<User>}
116
167
  */
117
168
  @action promiseUser(options = {}) {
118
169
  const NoUserAuthenticatedError = new Error('Failed to authenticate user.');
119
170
 
120
171
  return new Promise((resolve, reject) => {
121
172
  if (this.session.isAuthenticated) {
122
- return this.store
123
- .queryRecord('user', { me: true })
124
- .then((user) => {
173
+ try {
174
+ this.store.queryRecord('user', { me: true }).then((user) => {
125
175
  // set the `current user`
126
176
  this.set('user', user);
127
177
  this.trigger('user.loaded', user);
128
178
 
179
+ // Set permissions
180
+ this.permissions = this.getUserPermissions(user);
181
+
129
182
  // set environment from user option
130
183
  this.theme.setEnvironment();
131
184
 
@@ -135,10 +188,10 @@ export default class CurrentUserService extends Service.extend(Evented) {
135
188
  }
136
189
 
137
190
  resolve(user);
138
- })
139
- .catch(() => {
140
- reject(NoUserAuthenticatedError);
141
191
  });
192
+ } catch (error) {
193
+ reject(NoUserAuthenticatedError);
194
+ }
142
195
  } else {
143
196
  reject(NoUserAuthenticatedError);
144
197
  }
@@ -146,60 +199,56 @@ export default class CurrentUserService extends Service.extend(Evented) {
146
199
  }
147
200
 
148
201
  /**
149
- * Loads and resolved all current users installed order configurations.
202
+ * Gets all user permissions.
150
203
  *
151
- * @return {Promise}
204
+ * @param {UserModel} user
205
+ * @return {Array}
206
+ * @memberof CurrentUserService
152
207
  */
153
- @action getInstalledOrderConfigs(params = {}) {
154
- return new Promise((resolve, reject) => {
155
- this.fetch
156
- .get('fleet-ops/order-configs/get-installed', params)
157
- .then((configs) => {
158
- const serialized = [];
208
+ getUserPermissions(user) {
209
+ const permissions = [];
159
210
 
160
- for (let i = 0; i < configs.length; i++) {
161
- const config = configs.objectAt(i);
162
- const normalizedConfig = this.store.normalize('order-config', config);
163
- const serializedConfig = this.store.push(normalizedConfig);
211
+ // get direct applied permissions
212
+ if (user.get('permissions')) {
213
+ permissions.pushObjects(user.get('permissions').toArray());
214
+ }
215
+
216
+ // get role permissions and role policies permissions
217
+ if (user.get('role')) {
218
+ if (user.get('role.permissions')) {
219
+ permissions.pushObjects(user.get('role.permissions').toArray());
220
+ }
164
221
 
165
- serialized.pushObject(serializedConfig);
222
+ if (user.get('role.policies')) {
223
+ for (let i = 0; i < user.get('role.policies').length; i++) {
224
+ const policy = user.get('role.policies').objectAt(i);
225
+ if (policy.get('permissions')) {
226
+ permissions.pushObjects(policy.get('permissions').toArray());
166
227
  }
228
+ }
229
+ }
230
+ }
167
231
 
168
- resolve(serialized);
169
- })
170
- .catch(reject);
171
- });
232
+ // get direct applied policy permissions
233
+ if (user.get('policies')) {
234
+ for (let i = 0; i < user.get('policies').length; i++) {
235
+ const policy = user.get('policies').objectAt(i);
236
+ if (policy.get('permissions')) {
237
+ permissions.pushObjects(policy.get('permissions').toArray());
238
+ }
239
+ }
240
+ }
241
+
242
+ return permissions;
172
243
  }
173
244
 
174
245
  /**
175
- * The prefix for this user options
246
+ * Alias to get a user's whois property
176
247
  *
177
- * @var {String}
248
+ * @param {String} key
249
+ * @return {Mixed}
250
+ * @memberof CurrentUserService
178
251
  */
179
- @computed('id') get optionsPrefix() {
180
- return `${this.id}:`;
181
- }
182
-
183
- get latitude() {
184
- return this.whois('latitude');
185
- }
186
-
187
- get longitude() {
188
- return this.whois('longitude');
189
- }
190
-
191
- get currency() {
192
- return this.whois('currency.code');
193
- }
194
-
195
- get city() {
196
- return this.whois('city');
197
- }
198
-
199
- get country() {
200
- return this.whois('country_code');
201
- }
202
-
203
252
  @action whois(key) {
204
253
  return this.getWhoisProperty(key);
205
254
  }
@@ -13,6 +13,7 @@ import RSVP from 'rsvp';
13
13
  import loadInstalledExtensions from '../utils/load-installed-extensions';
14
14
  import loadExtensions from '../utils/load-extensions';
15
15
  import getWithDefault from '../utils/get-with-default';
16
+ import config from 'ember-get-config';
16
17
 
17
18
  export default class UniverseService extends Service.extend(Evented) {
18
19
  @service router;
@@ -846,7 +847,7 @@ export default class UniverseService extends Service.extend(Evented) {
846
847
  // If component is a definition register to host application
847
848
  if (typeof component === 'function') {
848
849
  const owner = getOwner(this);
849
- const widgetId = component.widgetId || widgetId || this._createUniqueWidgetHashFromDefinition(component);
850
+ widgetId = component.widgetId || widgetId || this._createUniqueWidgetHashFromDefinition(component);
850
851
 
851
852
  if (owner) {
852
853
  owner.register(`component:${widgetId}`, component);
@@ -1058,29 +1059,88 @@ export default class UniverseService extends Service.extend(Evented) {
1058
1059
  }
1059
1060
 
1060
1061
  /**
1061
- * Manually registers a component in a specified engine.
1062
+ * Registers a component class under one or more names within a specified engine instance.
1063
+ * This function provides flexibility in component registration by supporting registration under the component's
1064
+ * full class name, a simplified alias derived from the class name, and an optional custom name provided through the options.
1065
+ * This flexibility facilitates varied referencing styles within different parts of the application, enhancing modularity and reuse.
1062
1066
  *
1063
- * @method registerComponentInEngine
1064
- * @public
1065
- * @memberof UniverseService
1066
- * @param {String} engineName - The name of the engine where the component should be registered.
1067
- * @param {Object} componentClass - The component class to register, which should have a 'name' property.
1067
+ * @param {string} engineName - The name of the engine where the component will be registered.
1068
+ * @param {class} componentClass - The component class to be registered. Must be a class, not an instance.
1069
+ * @param {Object} [options] - Optional parameters for additional configuration.
1070
+ * @param {string} [options.registerAs] - A custom name under which the component can also be registered.
1071
+ *
1072
+ * @example
1073
+ * // Register a component with its default and alias names
1074
+ * registerComponentInEngine('mainEngine', HeaderComponent);
1075
+ *
1076
+ * // Additionally register the component under a custom name
1077
+ * registerComponentInEngine('mainEngine', HeaderComponent, { registerAs: 'header' });
1078
+ *
1079
+ * @remarks
1080
+ * - The function does not return any value.
1081
+ * - Registration only occurs if:
1082
+ * - The specified engine instance exists.
1083
+ * - The component class is properly defined with a non-empty name.
1084
+ * - The custom name, if provided, must be a valid string.
1085
+ * - Allows flexible component referencing by registering under multiple names.
1068
1086
  */
1069
- registerComponentInEngine(engineName, componentClass) {
1087
+ registerComponentInEngine(engineName, componentClass, options = {}) {
1070
1088
  const engineInstance = this.getEngineInstance(engineName);
1071
- if (engineInstance && !isBlank(componentClass) && typeof componentClass.name === 'string') {
1089
+ this.registerComponentToEngineInstance(engineInstance, componentClass, options);
1090
+ }
1091
+
1092
+ /**
1093
+ * Registers a component class under its full class name, a simplified alias, and an optional custom name within a specific engine instance.
1094
+ * This helper function does the actual registration of the component to the engine instance. It registers the component under its
1095
+ * full class name, a dasherized alias of the class name (with 'Component' suffix removed if present), and any custom name provided via options.
1096
+ *
1097
+ * @param {EngineInstance} engineInstance - The engine instance where the component will be registered.
1098
+ * @param {class} componentClass - The component class to be registered. This should be a class reference, not an instance.
1099
+ * @param {Object} [options] - Optional parameters for further configuration.
1100
+ * @param {string} [options.registerAs] - A custom name under which the component can be registered.
1101
+ *
1102
+ * @example
1103
+ * // Typical usage within the system (not usually called directly by users)
1104
+ * registerComponentToEngineInstance(engineInstance, HeaderComponent, { registerAs: 'header' });
1105
+ *
1106
+ * @remarks
1107
+ * - No return value.
1108
+ * - The registration is performed only if:
1109
+ * - The engine instance is valid and not null.
1110
+ * - The component class has a defined and non-empty name.
1111
+ * - The custom name, if provided, is a valid string.
1112
+ * - This function directly manipulates the engine instance's registration map.
1113
+ */
1114
+ registerComponentToEngineInstance(engineInstance, componentClass, options = {}) {
1115
+ if (engineInstance && componentClass && typeof componentClass.name === 'string') {
1072
1116
  engineInstance.register(`component:${componentClass.name}`, componentClass);
1117
+ engineInstance.register(`component:${dasherize(componentClass.name.replace('Component', ''))}`, componentClass);
1118
+ if (options && typeof options.registerAs === 'string') {
1119
+ engineInstance.register(`component:${options.registerAs}`, componentClass);
1120
+ }
1073
1121
  }
1074
1122
  }
1075
1123
 
1076
1124
  /**
1077
- * Manually registers a service in a specified engine.
1125
+ * Registers a service from one engine instance to another within the application.
1126
+ * This method retrieves an instance of a service from the current engine and then registers it
1127
+ * in a target engine, allowing the service to be shared across different parts of the application.
1078
1128
  *
1079
- * @method registerComponentInEngine
1080
- * @public
1081
- * @memberof UniverseService
1082
- * @param {String} engineName - The name of the engine where the component should be registered.
1083
- * @param {Object} serviceClass - The service class to register, which should have a 'name' property.
1129
+ * @param {string} targetEngineName - The name of the engine where the service should be registered.
1130
+ * @param {string} serviceName - The name of the service to be shared and registered.
1131
+ * @param {Object} currentEngineInstance - The engine instance that currently holds the service to be shared.
1132
+ *
1133
+ * @example
1134
+ * // Assuming 'appEngine' and 'componentEngine' are existing engine instances and 'logger' is a service in 'appEngine'
1135
+ * registerServiceInEngine('componentEngine', 'logger', appEngine);
1136
+ *
1137
+ * Note:
1138
+ * - This function does not return any value.
1139
+ * - It only performs registration if all provided parameters are valid:
1140
+ * - Both engine instances must exist.
1141
+ * - The service name must be a string.
1142
+ * - The service must exist in the current engine instance.
1143
+ * - The service is registered without instantiating a new copy in the target engine.
1084
1144
  */
1085
1145
  registerServiceInEngine(targetEngineName, serviceName, currentEngineInstance) {
1086
1146
  // Get the target engine instance
@@ -1111,11 +1171,16 @@ export default class UniverseService extends Service.extend(Evented) {
1111
1171
  * userService.doSomething();
1112
1172
  * }
1113
1173
  */
1114
- getServiceFromEngine(engineName, serviceName) {
1174
+ getServiceFromEngine(engineName, serviceName, options = {}) {
1115
1175
  const engineInstance = this.getEngineInstance(engineName);
1116
1176
 
1117
1177
  if (engineInstance && typeof serviceName === 'string') {
1118
1178
  const serviceInstance = engineInstance.lookup(`service:${serviceName}`);
1179
+ if (options && options.inject) {
1180
+ for (let injectionName in options.inject) {
1181
+ serviceInstance[injectionName] = options.inject[injectionName];
1182
+ }
1183
+ }
1119
1184
  return serviceInstance;
1120
1185
  }
1121
1186
 
@@ -1287,6 +1352,7 @@ export default class UniverseService extends Service.extend(Evented) {
1287
1352
  bootEngines(owner = null) {
1288
1353
  const booted = [];
1289
1354
  const pending = [];
1355
+ const additionalCoreExtensions = config.APP.extensions ?? [];
1290
1356
 
1291
1357
  // If no owner provided use the owner of this service
1292
1358
  if (owner === null) {
@@ -1296,9 +1362,11 @@ export default class UniverseService extends Service.extend(Evented) {
1296
1362
  const tryBootEngine = (extension) => {
1297
1363
  this.loadEngine(extension.name).then((engineInstance) => {
1298
1364
  if (engineInstance.base && engineInstance.base.setupExtension) {
1299
- const engineDependencies = getWithDefault(engineInstance.base, 'engineDependencies', []);
1365
+ if (booted.includes(extension.name)) {
1366
+ return;
1367
+ }
1300
1368
 
1301
- // Check if all dependency engines are booted
1369
+ const engineDependencies = getWithDefault(engineInstance.base, 'engineDependencies', []);
1302
1370
  const allDependenciesBooted = engineDependencies.every((dep) => booted.includes(dep));
1303
1371
 
1304
1372
  if (!allDependenciesBooted) {
@@ -1320,6 +1388,10 @@ export default class UniverseService extends Service.extend(Evented) {
1320
1388
  const stillPending = [];
1321
1389
 
1322
1390
  pending.forEach(({ extension, engineInstance }) => {
1391
+ if (booted.includes(extension.name)) {
1392
+ return;
1393
+ }
1394
+
1323
1395
  const engineDependencies = getWithDefault(engineInstance.base, 'engineDependencies', []);
1324
1396
  const allDependenciesBooted = engineDependencies.every((dep) => booted.includes(dep));
1325
1397
 
@@ -1333,13 +1405,13 @@ export default class UniverseService extends Service.extend(Evented) {
1333
1405
  });
1334
1406
 
1335
1407
  // If no progress was made, log an error in debug/development mode
1336
- assert('Some engines have unmet dependencies and cannot be booted:', pending.length === stillPending.length);
1408
+ assert(`Some engines have unmet dependencies and cannot be booted:`, stillPending.length === 0 && pending.length === 0);
1337
1409
 
1338
1410
  pending.length = 0;
1339
1411
  pending.push(...stillPending);
1340
1412
  };
1341
1413
 
1342
- loadInstalledExtensions().then((extensions) => {
1414
+ loadInstalledExtensions(additionalCoreExtensions).then((extensions) => {
1343
1415
  extensions.forEach((extension) => {
1344
1416
  tryBootEngine(extension);
1345
1417
  });
@@ -1,10 +1,49 @@
1
1
  import { getOwner } from '@ember/application';
2
+ import { isArray } from '@ember/array';
3
+ import isObject from './is-object';
2
4
 
3
- export default function injectEngineService(target, engineName, serviceName, key = null) {
5
+ function findService(owner, target, serviceName) {
6
+ let service = target[serviceName];
7
+ if (!service) {
8
+ service = owner.lookup(`service:${serviceName}`);
9
+ }
10
+
11
+ return service;
12
+ }
13
+
14
+ function injectServices(service, target, owner, injections) {
15
+ if (isArray(injections)) {
16
+ for (let i = 0; i < injections.length; i++) {
17
+ const serviceName = injections[i];
18
+ service[serviceName] = findService(owner, target, serviceName);
19
+ }
20
+ } else if (isObject(injections)) {
21
+ for (let serviceName in injections) {
22
+ service[serviceName] = injections[serviceName] ?? findService(owner, target, serviceName);
23
+ }
24
+ }
25
+ }
26
+
27
+ // unresolved services value will be the key as a string
28
+ function automaticServiceResolution(service, target, owner) {
29
+ for (let prop in service) {
30
+ if (typeof prop === 'string' && typeof service[prop] === 'string' && prop === service[prop]) {
31
+ service[prop] = findService(owner, target, prop);
32
+ }
33
+ }
34
+ }
35
+
36
+ export default function injectEngineService(target, engineName, serviceName, options = {}) {
4
37
  const owner = getOwner(target);
5
38
  const universe = owner.lookup('service:universe');
6
39
  const service = universe.getServiceFromEngine(engineName, serviceName);
40
+ const key = options.key || null;
7
41
  const effectiveServiceName = key || serviceName;
42
+ if (options && options.inject) {
43
+ injectServices(service, target, owner, options.inject);
44
+ } else {
45
+ automaticServiceResolution(service, target, owner);
46
+ }
8
47
 
9
48
  Object.defineProperty(target, effectiveServiceName, {
10
49
  value: service,
@@ -12,4 +51,6 @@ export default function injectEngineService(target, engineName, serviceName, key
12
51
  configurable: true,
13
52
  enumerable: true,
14
53
  });
54
+
55
+ return service;
15
56
  }
@@ -0,0 +1,3 @@
1
+ export default function isString(_var) {
2
+ return typeof _var === 'string';
3
+ }
@@ -1,8 +1,15 @@
1
1
  import loadExtensions from '../utils/load-extensions';
2
2
  import fleetbaseApiFetch from '../utils/fleetbase-api-fetch';
3
3
 
4
- export default async function loadInstalledExtensions() {
5
- const CORE_ENGINES = ['@fleetbase/fleetops-engine', '@fleetbase/storefront-engine', '@fleetbase/registry-bridge-engine', '@fleetbase/dev-engine', '@fleetbase/iam-engine'];
4
+ export default async function loadInstalledExtensions(additionalCoreEngines = []) {
5
+ const CORE_ENGINES = [
6
+ '@fleetbase/fleetops-engine',
7
+ '@fleetbase/storefront-engine',
8
+ '@fleetbase/registry-bridge-engine',
9
+ '@fleetbase/dev-engine',
10
+ '@fleetbase/iam-engine',
11
+ ...additionalCoreEngines,
12
+ ];
6
13
  const INDEXED_ENGINES = await loadExtensions();
7
14
  const INSTALLED_ENGINES = await fleetbaseApiFetch('get', 'engines', {}, { namespace: '~registry/v1', fallbackResponse: [] });
8
15
 
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-core/abilities/dynamic';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-core/decorators/engine-service';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-core/services/abilities';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-core/utils/is-string';
package/index.js CHANGED
@@ -41,6 +41,7 @@ module.exports = {
41
41
  new Funnel(path.dirname(require.resolve('socketcluster-client')), {
42
42
  files: ['socketcluster-client.min.js'],
43
43
  destDir: '/assets',
44
+ allowEmpty: true,
44
45
  })
45
46
  );
46
47
 
package/package.json CHANGED
@@ -1,128 +1,126 @@
1
1
  {
2
- "name": "@fleetbase/ember-core",
3
- "version": "0.2.13",
4
- "description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.",
5
- "keywords": [
6
- "fleetbase-core",
7
- "fleetbase-services",
8
- "fleetbase",
9
- "ember-addon"
10
- ],
11
- "repository": "https://github.com/fleetbase/ember-core",
12
- "license": "AGPL-3.0-or-later",
13
- "author": "Fleetbase Pte Ltd <hello@fleetbase.io>",
14
- "directories": {
15
- "app": "app",
16
- "addon": "addon",
17
- "tests": "tests"
18
- },
19
- "scripts": {
20
- "build": "ember build --environment=production",
21
- "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
22
- "lint:css": "stylelint \"**/*.css\"",
23
- "lint:css:fix": "concurrently \"npm:lint:css -- --fix\"",
24
- "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
25
- "lint:hbs": "ember-template-lint .",
26
- "lint:hbs:fix": "ember-template-lint . --fix",
27
- "lint:js": "eslint . --cache",
28
- "lint:js:fix": "eslint . --fix",
29
- "start": "ember serve",
30
- "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
31
- "test:ember": "ember test",
32
- "test:ember-compatibility": "ember try:each",
33
- "publish:npm": "npm config set registry https://registry.npmjs.org/ && npm publish",
34
- "publish:github": "npm config set '@fleetbase:registry' https://npm.pkg.github.com/ && npm publish"
35
- },
36
- "dependencies": {
37
- "@babel/core": "^7.23.2",
38
- "compress-json": "^3.0.0",
39
- "date-fns": "^2.30.0",
40
- "ember-auto-import": "^2.6.3",
41
- "ember-cli-babel": "^8.2.0",
42
- "ember-cli-htmlbars": "^6.3.0",
43
- "ember-cli-notifications": "^9.0.0",
44
- "ember-concurrency": "^3.1.1",
45
- "ember-concurrency-decorators": "^2.0.3",
46
- "ember-decorators": "^6.1.1",
47
- "ember-get-config": "^2.1.1",
48
- "ember-inflector": "^4.0.2",
49
- "ember-intl": "6.3.2",
50
- "ember-loading": "^2.0.0",
51
- "ember-local-storage": "^2.0.4",
52
- "ember-simple-auth": "^6.0.0",
53
- "ember-wormhole": "^0.6.0",
54
- "socketcluster-client": "^17.1.1"
55
- },
56
- "devDependencies": {
57
- "@babel/eslint-parser": "^7.22.15",
58
- "@babel/plugin-proposal-decorators": "^7.23.2",
59
- "@ember/optional-features": "^2.0.0",
60
- "@ember/test-helpers": "^3.2.0",
61
- "@embroider/test-setup": "^3.0.2",
62
- "@glimmer/component": "^1.1.2",
63
- "@glimmer/tracking": "^1.1.2",
64
- "broccoli-asset-rev": "^3.0.0",
65
- "broccoli-funnel": "^3.0.8",
66
- "broccoli-merge-trees": "^4.2.0",
67
- "broccoli-persistent-filter": "^3.1.3",
68
- "concurrently": "^8.2.2",
69
- "ember-cli": "~5.4.1",
70
- "ember-cli-clean-css": "^3.0.0",
71
- "ember-cli-dependency-checker": "^3.3.2",
72
- "ember-cli-inject-live-reload": "^2.1.0",
73
- "ember-cli-sri": "^2.1.1",
74
- "ember-cli-terser": "^4.0.2",
75
- "ember-data": "^4.12.5",
76
- "ember-file-upload": "8.4.0",
77
- "ember-load-initializers": "^2.1.2",
78
- "ember-page-title": "^8.0.0",
79
- "ember-qunit": "^8.0.1",
80
- "ember-resolver": "^11.0.1",
81
- "ember-source": "~5.4.0",
82
- "ember-source-channel-url": "^3.0.0",
83
- "ember-template-lint": "^5.11.2",
84
- "ember-try": "^3.0.0",
85
- "eslint": "^8.52.0",
86
- "eslint-config-prettier": "^9.0.0",
87
- "eslint-plugin-ember": "^11.11.1",
88
- "eslint-plugin-n": "^16.2.0",
89
- "eslint-plugin-prettier": "^5.0.1",
90
- "eslint-plugin-qunit": "^8.0.1",
91
- "loader.js": "^4.7.0",
92
- "prettier": "^3.0.3",
93
- "qunit": "^2.20.0",
94
- "qunit-dom": "^2.0.0",
95
- "resolve": "^1.22.2",
96
- "stylelint": "^15.11.0",
97
- "stylelint-config-standard": "^34.0.0",
98
- "stylelint-prettier": "^4.0.2",
99
- "webpack": "^5.89.0"
100
- },
101
- "overrides": {
102
- "broccoli-persistent-filter": "^3.1.3"
103
- },
104
- "engines": {
105
- "node": ">= 18"
106
- },
107
- "ember": {
108
- "edition": "octane"
109
- },
110
- "ember-addon": {
111
- "configPath": "tests/dummy/config"
112
- },
113
- "prettier": {
114
- "trailingComma": "es5",
115
- "tabWidth": 4,
116
- "semi": true,
117
- "singleQuote": true,
118
- "printWidth": 190,
119
- "overrides": [
120
- {
121
- "files": "*.hbs",
122
- "options": {
123
- "singleQuote": false
124
- }
125
- }
126
- ]
127
- }
2
+ "name": "@fleetbase/ember-core",
3
+ "version": "0.2.15",
4
+ "description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.",
5
+ "keywords": [
6
+ "fleetbase-core",
7
+ "fleetbase-services",
8
+ "fleetbase",
9
+ "ember-addon"
10
+ ],
11
+ "repository": "https://github.com/fleetbase/ember-core",
12
+ "license": "AGPL-3.0-or-later",
13
+ "author": "Fleetbase Pte Ltd <hello@fleetbase.io>",
14
+ "directories": {
15
+ "app": "app",
16
+ "addon": "addon",
17
+ "tests": "tests"
18
+ },
19
+ "scripts": {
20
+ "build": "ember build --environment=production",
21
+ "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
22
+ "lint:css": "stylelint \"**/*.css\"",
23
+ "lint:css:fix": "concurrently \"npm:lint:css -- --fix\"",
24
+ "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
25
+ "lint:hbs": "ember-template-lint .",
26
+ "lint:hbs:fix": "ember-template-lint . --fix",
27
+ "lint:js": "eslint . --cache",
28
+ "lint:js:fix": "eslint . --fix",
29
+ "start": "ember serve",
30
+ "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
31
+ "test:ember": "ember test",
32
+ "test:ember-compatibility": "ember try:each",
33
+ "publish:npm": "npm config set registry https://registry.npmjs.org/ && npm publish",
34
+ "publish:github": "npm config set '@fleetbase:registry' https://npm.pkg.github.com/ && npm publish"
35
+ },
36
+ "dependencies": {
37
+ "@babel/core": "^7.23.2",
38
+ "compress-json": "^3.0.0",
39
+ "date-fns": "^2.30.0",
40
+ "ember-auto-import": "^2.7.4",
41
+ "ember-can": "^6.0.0",
42
+ "ember-cli-babel": "^8.2.0",
43
+ "ember-cli-htmlbars": "^6.3.0",
44
+ "ember-cli-notifications": "^9.0.0",
45
+ "ember-concurrency": "^3.1.1",
46
+ "ember-concurrency-decorators": "^2.0.3",
47
+ "ember-decorators": "^6.1.1",
48
+ "ember-get-config": "^2.1.1",
49
+ "ember-inflector": "^4.0.2",
50
+ "ember-intl": "6.3.2",
51
+ "ember-loading": "^2.0.0",
52
+ "ember-local-storage": "^2.0.4",
53
+ "ember-simple-auth": "^6.0.0",
54
+ "ember-wormhole": "^0.6.0",
55
+ "socketcluster-client": "^17.1.1"
56
+ },
57
+ "devDependencies": {
58
+ "@babel/eslint-parser": "^7.22.15",
59
+ "@babel/plugin-proposal-decorators": "^7.23.2",
60
+ "@ember/optional-features": "^2.0.0",
61
+ "@ember/test-helpers": "^3.2.0",
62
+ "@embroider/test-setup": "^3.0.2",
63
+ "@glimmer/component": "^1.1.2",
64
+ "@glimmer/tracking": "^1.1.2",
65
+ "broccoli-asset-rev": "^3.0.0",
66
+ "broccoli-funnel": "^3.0.8",
67
+ "broccoli-merge-trees": "^4.2.0",
68
+ "broccoli-persistent-filter": "^3.1.3",
69
+ "concurrently": "^8.2.2",
70
+ "ember-cli": "~5.4.1",
71
+ "ember-cli-clean-css": "^3.0.0",
72
+ "ember-cli-dependency-checker": "^3.3.2",
73
+ "ember-cli-inject-live-reload": "^2.1.0",
74
+ "ember-cli-sri": "^2.1.1",
75
+ "ember-cli-terser": "^4.0.2",
76
+ "ember-data": "^4.12.5",
77
+ "ember-file-upload": "8.4.0",
78
+ "ember-load-initializers": "^2.1.2",
79
+ "ember-page-title": "^8.0.0",
80
+ "ember-qunit": "^8.0.1",
81
+ "ember-resolver": "^11.0.1",
82
+ "ember-source": "~5.4.0",
83
+ "ember-source-channel-url": "^3.0.0",
84
+ "ember-template-lint": "^5.11.2",
85
+ "ember-try": "^3.0.0",
86
+ "eslint": "^8.52.0",
87
+ "eslint-config-prettier": "^9.0.0",
88
+ "eslint-plugin-ember": "^11.11.1",
89
+ "eslint-plugin-n": "^16.2.0",
90
+ "eslint-plugin-prettier": "^5.0.1",
91
+ "eslint-plugin-qunit": "^8.0.1",
92
+ "loader.js": "^4.7.0",
93
+ "prettier": "^3.0.3",
94
+ "qunit": "^2.20.0",
95
+ "qunit-dom": "^2.0.0",
96
+ "resolve": "^1.22.2",
97
+ "stylelint": "^15.11.0",
98
+ "stylelint-config-standard": "^34.0.0",
99
+ "stylelint-prettier": "^4.0.2",
100
+ "webpack": "^5.89.0"
101
+ },
102
+ "engines": {
103
+ "node": ">= 18"
104
+ },
105
+ "ember": {
106
+ "edition": "octane"
107
+ },
108
+ "ember-addon": {
109
+ "configPath": "tests/dummy/config"
110
+ },
111
+ "prettier": {
112
+ "trailingComma": "es5",
113
+ "tabWidth": 4,
114
+ "semi": true,
115
+ "singleQuote": true,
116
+ "printWidth": 190,
117
+ "overrides": [
118
+ {
119
+ "files": "*.hbs",
120
+ "options": {
121
+ "singleQuote": false
122
+ }
123
+ }
124
+ ]
125
+ }
128
126
  }