@product7/feedback-sdk 1.0.1 → 1.0.3

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.
@@ -7,64 +7,36 @@ export class APIService {
7
7
  this.sessionExpiry = null;
8
8
  this.userContext = config.userContext || null;
9
9
 
10
- // Construct workspace-specific API URL
11
10
  if (config.apiUrl) {
12
- // If explicitly provided, use it
13
11
  this.baseURL = config.apiUrl;
14
12
  } else if (this.workspace) {
15
- // Construct from workspace: workspace.api.staging.product7.io/api/v1
16
13
  this.baseURL = `https://${this.workspace}.api.staging.product7.io/api/v1`;
17
14
  } else {
18
- // Fallback to default
19
15
  this.baseURL = 'https://api.staging.product7.io/api/v1';
20
16
  }
21
17
 
22
- console.log('[APIService] Using API URL:', this.baseURL);
23
-
24
- // Try to load existing session from localStorage
25
18
  this._loadStoredSession();
26
19
  }
27
20
 
28
21
  async init(userContext = null) {
29
- console.log('[APIService] Starting initialization...');
30
-
31
22
  if (userContext) {
32
23
  this.userContext = userContext;
33
24
  }
34
25
 
35
- // Check if we have a valid session token
36
26
  if (this.isSessionValid()) {
37
- console.log('[APIService] Found valid existing session');
38
27
  return { sessionToken: this.sessionToken };
39
28
  }
40
29
 
41
- // Try to get user context from various sources if not provided
42
- if (!this.userContext) {
43
- console.log(
44
- '[APIService] No user context provided, attempting auto-detection...'
45
- );
46
- this.userContext = this._getUserContextFromStorage();
47
- }
48
-
49
30
  if (!this.userContext || !this.workspace) {
50
31
  const error = `Missing ${!this.workspace ? 'workspace' : 'user context'} for initialization`;
51
- console.error('[APIService]', error);
52
32
  throw new APIError(400, error);
53
33
  }
54
34
 
55
- console.log('[APIService] User context detected:', this.userContext);
56
-
57
35
  const payload = {
58
36
  workspace: this.workspace,
59
37
  user: this.userContext,
60
38
  };
61
39
 
62
- console.log(
63
- '[APIService] Making init request to:',
64
- `${this.baseURL}/widget/init`
65
- );
66
- console.log('[APIService] Payload:', payload);
67
-
68
40
  try {
69
41
  const response = await this._makeRequest('/widget/init', {
70
42
  method: 'POST',
@@ -74,13 +46,8 @@ export class APIService {
74
46
  },
75
47
  });
76
48
 
77
- console.log('[APIService] Init response:', response);
78
-
79
- // Store session token and expiry
80
49
  this.sessionToken = response.session_token;
81
50
  this.sessionExpiry = new Date(Date.now() + response.expires_in * 1000);
82
-
83
- // Store session in localStorage for persistence
84
51
  this._storeSession();
85
52
 
86
53
  return {
@@ -89,7 +56,6 @@ export class APIService {
89
56
  expiresIn: response.expires_in,
90
57
  };
91
58
  } catch (error) {
92
- console.error('[APIService] Init failed:', error);
93
59
  throw new APIError(
94
60
  error.status || 500,
95
61
  `Failed to initialize widget: ${error.message}`,
@@ -99,7 +65,6 @@ export class APIService {
99
65
  }
100
66
 
101
67
  async submitFeedback(feedbackData) {
102
- // Ensure we have a valid session
103
68
  if (!this.isSessionValid()) {
104
69
  await this.init();
105
70
  }
@@ -109,8 +74,7 @@ export class APIService {
109
74
  }
110
75
 
111
76
  const payload = {
112
- board:
113
- feedbackData.board_id || feedbackData.board || feedbackData.boardId,
77
+ board: feedbackData.board_id || feedbackData.board || feedbackData.boardId,
114
78
  title: feedbackData.title,
115
79
  content: feedbackData.content,
116
80
  attachments: feedbackData.attachments || [],
@@ -128,13 +92,10 @@ export class APIService {
128
92
 
129
93
  return response;
130
94
  } catch (error) {
131
- // If session expired, try to reinitialize once
132
95
  if (error.status === 401) {
133
96
  this.sessionToken = null;
134
97
  this.sessionExpiry = null;
135
98
  await this.init();
136
-
137
- // Retry the request with new session
138
99
  return this.submitFeedback(feedbackData);
139
100
  }
140
101
 
@@ -154,12 +115,8 @@ export class APIService {
154
115
 
155
116
  setUserContext(userContext) {
156
117
  this.userContext = userContext;
157
- // Store in localStorage for persistence
158
118
  if (typeof localStorage !== 'undefined') {
159
- localStorage.setItem(
160
- 'feedbackSDK_userContext',
161
- JSON.stringify(userContext)
162
- );
119
+ localStorage.setItem('feedbackSDK_userContext', JSON.stringify(userContext));
163
120
  }
164
121
  }
165
122
 
@@ -172,99 +129,10 @@ export class APIService {
172
129
  this.sessionExpiry = null;
173
130
  if (typeof localStorage !== 'undefined') {
174
131
  localStorage.removeItem('feedbackSDK_session');
132
+ localStorage.removeItem('feedbackSDK_userContext');
175
133
  }
176
134
  }
177
135
 
178
- _getUserContextFromStorage() {
179
- if (typeof localStorage === 'undefined') return null;
180
-
181
- console.log('[APIService] Attempting to detect user from storage...');
182
-
183
- try {
184
- // Try to get from feedbackSDK specific storage first
185
- const stored = localStorage.getItem('feedbackSDK_userContext');
186
- if (stored) {
187
- console.log('[APIService] Found user context in feedbackSDK storage');
188
- return JSON.parse(stored);
189
- }
190
-
191
- // Try to get from window object (if set by parent application)
192
- if (typeof window !== 'undefined' && window.FeedbackSDKUserContext) {
193
- console.log(
194
- '[APIService] Found user context in window.FeedbackSDKUserContext'
195
- );
196
- return window.FeedbackSDKUserContext;
197
- }
198
-
199
- // Check window.currentUser
200
- if (typeof window !== 'undefined' && window.currentUser) {
201
- console.log('[APIService] Found user context in window.currentUser');
202
- return this._mapUserData(window.currentUser);
203
- }
204
-
205
- // Try to extract from existing session storage
206
- const authSources = ['auth', 'currentUser', 'userSession', 'user'];
207
-
208
- for (const key of authSources) {
209
- const sessionData = localStorage.getItem(key);
210
- if (sessionData) {
211
- console.log(`[APIService] Found data in localStorage.${key}`);
212
- const parsed = JSON.parse(sessionData);
213
-
214
- // Handle nested user data (like {user: {...}, token: ...})
215
- const userData = parsed.user || parsed;
216
-
217
- if (userData && (userData.id || userData.user_id || userData.email)) {
218
- console.log('[APIService] Successfully mapped user data');
219
- return this._mapUserData(userData);
220
- }
221
- }
222
- }
223
-
224
- console.log('[APIService] No user context found in any storage location');
225
- return null;
226
- } catch (error) {
227
- console.warn(
228
- '[FeedbackSDK] Failed to load user context from storage:',
229
- error
230
- );
231
- return null;
232
- }
233
- }
234
-
235
- _mapUserData(userData) {
236
- console.log('[APIService] Mapping user data:', userData);
237
-
238
- const mapped = {
239
- user_id: userData.id || userData.user_id || userData.userId,
240
- email: userData.email,
241
- name: userData.name || userData.displayName || userData.full_name,
242
- custom_fields: {},
243
- company: {},
244
- };
245
-
246
- // Map company data if available
247
- if (userData.company || userData.organization) {
248
- const company = userData.company || userData.organization;
249
- mapped.company = {
250
- id: company.id || company.company_id,
251
- name: company.name || company.company_name,
252
- monthly_spend: company.monthly_spend || company.spend,
253
- };
254
- }
255
-
256
- // Map any additional custom fields
257
- const customFieldKeys = ['plan', 'role', 'tier', 'subscription'];
258
- customFieldKeys.forEach((key) => {
259
- if (userData[key]) {
260
- mapped.custom_fields[key] = userData[key];
261
- }
262
- });
263
-
264
- console.log('[APIService] Mapped result:', mapped);
265
- return mapped;
266
- }
267
-
268
136
  _storeSession() {
269
137
  if (typeof localStorage === 'undefined') return;
270
138
 
@@ -276,7 +144,7 @@ export class APIService {
276
144
  };
277
145
  localStorage.setItem('feedbackSDK_session', JSON.stringify(sessionData));
278
146
  } catch (error) {
279
- console.warn('[FeedbackSDK] Failed to store session:', error);
147
+ // Silently fail if localStorage is not available
280
148
  }
281
149
  }
282
150
 
@@ -293,14 +161,12 @@ export class APIService {
293
161
 
294
162
  return this.isSessionValid();
295
163
  } catch (error) {
296
- console.warn('[FeedbackSDK] Failed to load stored session:', error);
297
164
  return false;
298
165
  }
299
166
  }
300
167
 
301
168
  async _makeRequest(endpoint, options = {}) {
302
169
  const url = `${this.baseURL}${endpoint}`;
303
- console.log('[APIService] Making request to:', url);
304
170
 
305
171
  try {
306
172
  const response = await fetch(url, options);
@@ -311,8 +177,7 @@ export class APIService {
311
177
 
312
178
  try {
313
179
  responseData = await response.json();
314
- errorMessage =
315
- responseData.message || responseData.error || errorMessage;
180
+ errorMessage = responseData.message || responseData.error || errorMessage;
316
181
  } catch (e) {
317
182
  errorMessage = (await response.text()) || errorMessage;
318
183
  }
@@ -330,9 +195,7 @@ export class APIService {
330
195
  if (error instanceof APIError) {
331
196
  throw error;
332
197
  }
333
-
334
- // Network or other errors
335
198
  throw new APIError(0, error.message, null);
336
199
  }
337
200
  }
338
- }
201
+ }
@@ -11,7 +11,6 @@ export class FeedbackSDK {
11
11
  this.widgets = new Map();
12
12
  this.eventBus = new EventBus();
13
13
 
14
- // Initialize API service
15
14
  this.apiService = new APIService({
16
15
  apiUrl: this.config.apiUrl,
17
16
  workspace: this.config.workspace,
@@ -27,10 +26,8 @@ export class FeedbackSDK {
27
26
  }
28
27
 
29
28
  try {
30
- // Initialize the API service (this will handle the /widget/init call)
31
29
  const initData = await this.apiService.init(this.config.userContext);
32
30
 
33
- // Merge any server-provided config with local config
34
31
  if (initData.config) {
35
32
  this.config = deepMerge(this.config, initData.config);
36
33
  }
@@ -55,9 +52,7 @@ export class FeedbackSDK {
55
52
 
56
53
  createWidget(type = 'button', options = {}) {
57
54
  if (!this.initialized) {
58
- throw new SDKError(
59
- 'SDK must be initialized before creating widgets. Call init() first.'
60
- );
55
+ throw new SDKError('SDK must be initialized before creating widgets. Call init() first.');
61
56
  }
62
57
 
63
58
  const widgetId = generateId('widget');
@@ -72,7 +67,6 @@ export class FeedbackSDK {
72
67
  try {
73
68
  const widget = WidgetFactory.create(type, widgetOptions);
74
69
  this.widgets.set(widgetId, widget);
75
-
76
70
  this.eventBus.emit('widget:created', { widget, type });
77
71
  return widget;
78
72
  } catch (error) {
@@ -111,7 +105,6 @@ export class FeedbackSDK {
111
105
  const oldConfig = { ...this.config };
112
106
  this.config = this._validateAndMergeConfig(newConfig, this.config);
113
107
 
114
- // Update all existing widgets with new config
115
108
  for (const widget of this.widgets.values()) {
116
109
  widget.handleConfigUpdate(this.config);
117
110
  }
@@ -131,23 +124,17 @@ export class FeedbackSDK {
131
124
  }
132
125
 
133
126
  getUserContext() {
134
- return (
135
- this.config.userContext ||
136
- (this.apiService ? this.apiService.getUserContext() : null)
137
- );
127
+ return this.config.userContext || (this.apiService ? this.apiService.getUserContext() : null);
138
128
  }
139
129
 
140
130
  async reinitialize(newUserContext = null) {
141
- // Clear current session
142
131
  this.apiService.clearSession();
143
132
  this.initialized = false;
144
133
 
145
- // Update user context if provided
146
134
  if (newUserContext) {
147
135
  this.setUserContext(newUserContext);
148
136
  }
149
137
 
150
- // Reinitialize
151
138
  return this.init();
152
139
  }
153
140
 
@@ -191,24 +178,12 @@ export class FeedbackSDK {
191
178
  debug: false,
192
179
  };
193
180
 
194
- const mergedConfig = deepMerge(
195
- deepMerge(defaultConfig, existingConfig),
196
- newConfig
197
- );
198
-
199
- // Validate required config
200
- const requiredFields = ['workspace'];
201
- const missingFields = requiredFields.filter(
202
- (field) => !mergedConfig[field]
203
- );
204
-
205
- if (missingFields.length > 0) {
206
- throw new ConfigError(
207
- `Missing required configuration: ${missingFields.join(', ')}`
208
- );
181
+ const mergedConfig = deepMerge(deepMerge(defaultConfig, existingConfig), newConfig);
182
+
183
+ if (!mergedConfig.workspace) {
184
+ throw new ConfigError('Missing required configuration: workspace');
209
185
  }
210
186
 
211
- // Validate userContext structure if provided
212
187
  if (mergedConfig.userContext) {
213
188
  this._validateUserContext(mergedConfig.userContext);
214
189
  }
@@ -218,12 +193,9 @@ export class FeedbackSDK {
218
193
 
219
194
  _validateUserContext(userContext) {
220
195
  if (!userContext.user_id && !userContext.email) {
221
- throw new ConfigError(
222
- 'User context must include at least user_id or email'
223
- );
196
+ throw new ConfigError('User context must include at least user_id or email');
224
197
  }
225
198
 
226
- // Validate structure matches expected API format
227
199
  const validStructure = {
228
200
  user_id: 'string',
229
201
  email: 'string',
@@ -234,9 +206,7 @@ export class FeedbackSDK {
234
206
 
235
207
  for (const [key, expectedType] of Object.entries(validStructure)) {
236
208
  if (userContext[key] && typeof userContext[key] !== expectedType) {
237
- throw new ConfigError(
238
- `User context field '${key}' must be of type '${expectedType}'`
239
- );
209
+ throw new ConfigError(`User context field '${key}' must be of type '${expectedType}'`);
240
210
  }
241
211
  }
242
212
  }
@@ -247,7 +217,6 @@ export class FeedbackSDK {
247
217
  this.updateConfig = this.updateConfig.bind(this);
248
218
  }
249
219
 
250
- // Static helper methods
251
220
  static create(config) {
252
221
  return new FeedbackSDK(config);
253
222
  }
@@ -258,9 +227,7 @@ export class FeedbackSDK {
258
227
  return sdk;
259
228
  }
260
229
 
261
- // Utility methods for external integrations
262
230
  static extractUserContextFromAuth(authData) {
263
- // Helper method to extract user context from common auth structures
264
231
  if (!authData) return null;
265
232
 
266
233
  return {
@@ -272,14 +239,11 @@ export class FeedbackSDK {
272
239
  plan: authData.plan || authData.subscription?.plan,
273
240
  ...(authData.custom_fields || {}),
274
241
  },
275
- company:
276
- authData.company || authData.organization
277
- ? {
278
- id: authData.company?.id || authData.organization?.id,
279
- name: authData.company?.name || authData.organization?.name,
280
- monthly_spend: authData.company?.monthly_spend,
281
- }
282
- : undefined,
242
+ company: authData.company || authData.organization ? {
243
+ id: authData.company?.id || authData.organization?.id,
244
+ name: authData.company?.name || authData.organization?.name,
245
+ monthly_spend: authData.company?.monthly_spend,
246
+ } : undefined,
283
247
  };
284
248
  }
285
- }
249
+ }
package/src/index.js CHANGED
@@ -17,25 +17,11 @@ import { TabWidget } from './widgets/TabWidget.js';
17
17
  import { WidgetFactory } from './widgets/WidgetFactory.js';
18
18
 
19
19
  function injectStyles() {
20
- console.log('injectStyles called');
21
- console.log('document exists:', typeof document !== 'undefined');
22
- console.log('CSS_STYLES exists:', !!CSS_STYLES);
23
-
24
- if (
25
- typeof document !== 'undefined' &&
26
- !document.querySelector('#feedback-sdk-styles')
27
- ) {
28
- console.log('Injecting CSS...');
20
+ if (typeof document !== 'undefined' && !document.querySelector('#feedback-sdk-styles')) {
29
21
  const style = document.createElement('style');
30
22
  style.id = 'feedback-sdk-styles';
31
23
  style.textContent = CSS_STYLES;
32
24
  document.head.appendChild(style);
33
- console.log(
34
- 'CSS injected, style element created:',
35
- !!document.querySelector('#feedback-sdk-styles')
36
- );
37
- } else {
38
- console.log('CSS already exists or document not ready');
39
25
  }
40
26
  }
41
27
 
@@ -44,11 +30,6 @@ function autoInit() {
44
30
  injectStyles();
45
31
 
46
32
  const config = { ...window.FeedbackSDKConfig };
47
-
48
- if (!config.userContext) {
49
- config.userContext = getUserContextFromEnvironment();
50
- }
51
-
52
33
  const sdk = new FeedbackSDK(config);
53
34
 
54
35
  sdk
@@ -76,30 +57,17 @@ function autoInit() {
76
57
 
77
58
  if (typeof CustomEvent !== 'undefined') {
78
59
  const event = new CustomEvent('FeedbackSDKReady', {
79
- detail: {
80
- sdk,
81
- config: config,
82
- initData: initData,
83
- },
60
+ detail: { sdk, config, initData },
84
61
  });
85
62
  window.dispatchEvent(event);
86
63
  }
87
-
88
- console.log(
89
- '[FeedbackSDK] Successfully initialized with session:',
90
- initData.sessionToken ? 'Yes' : 'No'
91
- );
92
64
  })
93
65
  .catch((error) => {
94
66
  console.error('[FeedbackSDK] Auto-initialization failed:', error);
95
67
 
96
68
  if (typeof CustomEvent !== 'undefined') {
97
69
  const event = new CustomEvent('FeedbackSDKError', {
98
- detail: {
99
- error,
100
- config: config,
101
- phase: 'initialization',
102
- },
70
+ detail: { error, config, phase: 'initialization' },
103
71
  });
104
72
  window.dispatchEvent(event);
105
73
  }
@@ -107,70 +75,6 @@ function autoInit() {
107
75
  }
108
76
  }
109
77
 
110
- function getUserContextFromEnvironment() {
111
- if (typeof window === 'undefined') return null;
112
-
113
- if (window.FeedbackSDKUserContext) {
114
- return window.FeedbackSDKUserContext;
115
- }
116
-
117
- const authSources = [
118
- () => window.auth0?.user,
119
- () => window.firebase?.auth()?.currentUser,
120
- () => window.amplify?.Auth?.currentAuthenticatedUser(),
121
-
122
- () => window.currentUser,
123
- () => window.user,
124
- () => window.userData,
125
-
126
- () => window.app?.user,
127
- () => window.store?.getState?.()?.user,
128
- () => window.App?.currentUser,
129
- ];
130
-
131
- for (const getAuth of authSources) {
132
- try {
133
- const authData = getAuth();
134
- if (authData) {
135
- const userContext = FeedbackSDK.extractUserContextFromAuth(authData);
136
- if (userContext && (userContext.user_id || userContext.email)) {
137
- console.log(
138
- '[FeedbackSDK] Auto-detected user context from',
139
- getAuth.name || 'unknown source'
140
- );
141
- return userContext;
142
- }
143
- }
144
- } catch (error) {
145
- continue;
146
- }
147
- }
148
-
149
- try {
150
- const storedAuth =
151
- localStorage.getItem('auth') ||
152
- localStorage.getItem('user') ||
153
- localStorage.getItem('session');
154
- if (storedAuth) {
155
- const authData = JSON.parse(storedAuth);
156
- const userContext = FeedbackSDK.extractUserContextFromAuth(authData);
157
- if (userContext && (userContext.user_id || userContext.email)) {
158
- console.log(
159
- '[FeedbackSDK] Auto-detected user context from localStorage'
160
- );
161
- return userContext;
162
- }
163
- }
164
- } catch (error) {
165
- // Continue
166
- }
167
-
168
- console.warn(
169
- '[FeedbackSDK] No user context found. Widget initialization may require manual user context setting.'
170
- );
171
- return null;
172
- }
173
-
174
78
  function handleDOMReady() {
175
79
  if (typeof document !== 'undefined') {
176
80
  if (document.readyState === 'loading') {
@@ -278,4 +182,4 @@ export {
278
182
  ValidationError,
279
183
  WidgetError,
280
184
  WidgetFactory,
281
- };
185
+ };
@@ -0,0 +1,42 @@
1
+ declare module '@product7/feedback-sdk' {
2
+ export interface FeedbackConfig {
3
+ workspace: string;
4
+ debug?: boolean;
5
+ boardId?: string;
6
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
7
+ }
8
+
9
+ export interface ButtonWidget {
10
+ mount(container?: string | HTMLElement): this;
11
+ destroy(): void;
12
+ show(): this;
13
+ hide(): this;
14
+ openModal(): void;
15
+ closeModal(): void;
16
+ }
17
+
18
+ export class FeedbackSDK {
19
+ constructor(config: FeedbackConfig);
20
+ init(): Promise<any>;
21
+ createWidget(
22
+ type: 'button',
23
+ options?: Partial<FeedbackConfig>
24
+ ): ButtonWidget;
25
+ readonly initialized: boolean;
26
+ }
27
+
28
+ interface FeedbackSDKExport {
29
+ FeedbackSDK: typeof FeedbackSDK;
30
+ ButtonWidget: any;
31
+ create: (config: FeedbackConfig) => FeedbackSDK;
32
+ getInstance: () => FeedbackSDK | null;
33
+ isReady: () => boolean;
34
+ version: string;
35
+ instance: FeedbackSDK | null;
36
+ onReady: (callback: (sdk: FeedbackSDK) => void) => void;
37
+ onError: (callback: (error: Error) => void) => void;
38
+ }
39
+
40
+ const FeedbackSDKDefault: FeedbackSDKExport;
41
+ export default FeedbackSDKDefault;
42
+ }
@@ -1,12 +0,0 @@
1
- declare module '@product7/feedback-sdk' {
2
- export class FeedbackSDK {
3
- constructor(config: any);
4
- init(): Promise<any>;
5
- createWidget(type: string, options?: any): any;
6
- }
7
-
8
- export class ButtonWidget {
9
- mount(container?: string | HTMLElement): this;
10
- destroy(): void;
11
- }
12
- }