UNPKG

@bigdigital/kiosk-content-sdk

Version:

A Firebase-powered Content Management System SDK optimized for kiosks with offline support, template management, and real-time connection monitoring

291 lines 11.8 kB
import { initializeApp, getApp } from "firebase/app"; import { getFirestore, collection, query, where, getDocs, doc, getDoc } from "firebase/firestore"; import { templateSchema, contentSchema, fieldSchema } from "./types"; // Helper function to find field by id function findFieldById(fields, fieldId) { return fields.find(field => field.id === fieldId); } // Normalize group names to proper camelCase function normalizeGroupName(name) { let words = name.split(/[\s\-_]+/); if (words.length === 1) { words = words[0] .replace(/([a-z0-9])([A-Z])/g, '$1 $2') .replace(/([a-zA-Z])(\d)/g, '$1 $2') .replace(/([a-z])([a-z]*)/gi, (match, first, rest) => `${first}${rest.toLowerCase()}`) .split(/\s+/); } words = words .filter(word => word.length > 0) .map(word => word.toLowerCase()); if (words.length === 0) return ''; return words[0] + words.slice(1) .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(''); } function processTemplateValues(template, rawData, content) { console.log("Raw content being processed:", rawData); console.log("Template being used:", { id: template.id, name: template.name, fields: template.fields, groups: template.groups }); const result = { groups: {}, ungrouped: {} }; if (!rawData) { console.log('No raw data provided'); return result; } // First, try to get values from templateValues if they exist let existingValues = rawData.templateValues || {}; console.log("Existing template values:", existingValues); // Process fields in groups Object.entries(template.groups || {}).forEach(([groupId, group]) => { // Always generate and use the proper camelCase name const normalizedName = normalizeGroupName(group.name); console.log(`Processing group ${group.name} -> ${normalizedName}`); // Store the normalizedName back to the group for consistency group.normalizedName = normalizedName; // Initialize group result.groups[normalizedName] = {}; // Check multiple locations for values in order of precedence const groupValues = existingValues.groups?.[normalizedName] || existingValues.groups?.[group.name] || rawData[normalizedName] || rawData[group.name] || {}; console.log(`Group values found for ${normalizedName}:`, groupValues); // Process fields in this group group.fieldIds.forEach(fieldId => { const field = findFieldById(template.fields, fieldId); if (field) { const normalizedFieldName = field.name.charAt(0).toLowerCase() + field.name.slice(1); // Try to get value from various locations const fieldValue = groupValues[normalizedFieldName] || groupValues[field.name] || rawData[normalizedFieldName] || rawData[field.name] || field.defaultValue || null; console.log(`Field ${field.name} -> ${normalizedFieldName} in group ${normalizedName}:`, { value: fieldValue, source: groupValues[normalizedFieldName] ? 'normalized group' : groupValues[field.name] ? 'original group' : rawData[normalizedFieldName] ? 'normalized root' : rawData[field.name] ? 'original root' : field.defaultValue ? 'default' : 'null' }); result.groups[normalizedName][normalizedFieldName] = fieldValue; } }); }); // Process ungrouped fields const groupedFieldIds = new Set(Object.values(template.groups || {}) .flatMap(group => group.fieldIds)); template.fields.forEach(field => { if (!groupedFieldIds.has(field.id)) { const normalizedFieldName = field.name.charAt(0).toLowerCase() + field.name.slice(1); result.ungrouped[normalizedFieldName] = rawData[normalizedFieldName] || rawData[field.name] || existingValues.ungrouped?.[normalizedFieldName] || existingValues.ungrouped?.[field.name] || field.defaultValue || null; } }); console.log("Final processed template values:", result); return result; } export class KioskClient { constructor(config) { try { this.app = getApp(); } catch { this.app = initializeApp({ apiKey: config.apiKey, authDomain: config.authDomain || `${config.projectId}.firebaseapp.com`, projectId: config.projectId, }); } this.db = getFirestore(this.app); } async getTemplates() { const templatesRef = collection(this.db, "templates"); const snapshot = await getDocs(templatesRef); const templates = snapshot.docs.map(doc => { const data = doc.data(); // Ensure fields is an array if (!Array.isArray(data.fields)) { data.fields = Object.values(data.fields); } // Ensure all groups have normalizedName in camelCase if (data.groups) { Object.values(data.groups).forEach((group) => { group.normalizedName = normalizeGroupName(group.name); }); } return { id: doc.id, ...data }; }); return templates; } async getTemplateById(templateId) { const docRef = doc(this.db, "templates", templateId); const docSnap = await getDoc(docRef); if (!docSnap.exists()) { console.log(`Template ${templateId} not found`); return null; } const data = docSnap.data(); // Ensure fields is an array if (!Array.isArray(data.fields)) { data.fields = Object.values(data.fields); } // Ensure all groups have normalizedName in camelCase if (data.groups) { Object.values(data.groups).forEach((group) => { group.normalizedName = normalizeGroupName(group.name); }); } const template = { id: docSnap.id, ...data }; console.log(`Retrieved template ${templateId}:`, template); return template; } async getPublishedContent() { const templates = await this.getTemplates(); console.log(`Retrieved ${templates.length} templates`); const contentRef = collection(this.db, "contents"); const publishedQuery = query(contentRef, where("published", "==", true)); const snapshot = await getDocs(publishedQuery); console.log(`Found ${snapshot.size} published content items`); return Promise.all(snapshot.docs.map(async (doc) => { const rawData = doc.data(); console.log(`Processing content ${doc.id}:`, { templateId: rawData.templateId, rawValues: rawData }); console.log("Raw content data from Firestore:", rawData); const content = { id: doc.id, ...rawData, templateValues: { groups: {}, ungrouped: {} } }; if (content.templateId) { const template = templates.find(t => t.id === content.templateId); if (template) { const templateValues = processTemplateValues(template, rawData, content); console.log("Final processed template values:", templateValues); content.templateValues = templateValues; console.log(`Processed content ${doc.id} template values:`, content.templateValues); } } return content; })); } async getContentByProject(projectId) { const templates = await this.getTemplates(); const contentRef = collection(this.db, "contents"); const projectQuery = query(contentRef, where("published", "==", true), where("projectIds", "array-contains", projectId)); const snapshot = await getDocs(projectQuery); return Promise.all(snapshot.docs.map(async (doc) => { const rawData = doc.data(); const content = { id: doc.id, ...rawData, templateValues: { groups: {}, ungrouped: {} } }; if (content.templateId) { const template = templates.find(t => t.id === content.templateId); if (template) { content.templateValues = processTemplateValues(template, rawData, content); } } return content; })); } async getContentWithTemplate(contentId) { const docRef = doc(this.db, "contents", contentId); const docSnap = await getDoc(docRef); if (!docSnap.exists()) { throw new Error("Content not found"); } const rawData = docSnap.data(); const content = { id: docSnap.id, ...rawData, templateValues: { groups: {}, ungrouped: {} } }; if (!content.templateId) { return { content, template: null }; } const template = await this.getTemplateById(content.templateId); if (!template) { return { content, template: null }; } content.templateValues = processTemplateValues(template, rawData, content); return { content, template, groupedContent: this.getGroupedTemplateContent(template, content) }; } async getGroupedTemplateContent(template, content) { const templateValues = content.templateValues ?? { groups: {}, ungrouped: {} }; const groups = (template.groupOrder || []) .map(groupId => { const group = template.groups[groupId]; if (!group) return null; const normalizedName = normalizeGroupName(group.name); const fields = group.fieldIds .map(fieldId => findFieldById(template.fields, fieldId)) .filter((field) => field !== undefined); return { group: { ...group, normalizedName, }, fields, values: templateValues.groups[normalizedName] ?? {} }; }) .filter((group) => group !== null); const ungroupedFieldIds = template.fields .filter(field => !Object.values(template.groups) .some(group => group.fieldIds?.includes(field.id))) .map(field => field); return { groups, ungroupedFields: { fields: ungroupedFieldIds, values: templateValues.ungrouped ?? {} } }; } } export function isValidTemplate(template) { const result = templateSchema.safeParse(template); return result.success; } export function isValidContent(content) { const result = contentSchema.safeParse(content); return result.success; } export function validateTemplateField(field) { const result = fieldSchema.safeParse(field); return result.success; } //# sourceMappingURL=client.js.map