UNPKG

@syngrisi/syngrisi

Version:
2,002 lines (1,973 loc) 61.1 kB
// src/server/models/Check.model.ts import mongoose2, { Schema } from "mongoose"; // src/server/models/plugins/paginate.plugin.ts var paginate = (schema) => { schema.statics.paginate = async function(filter, options) { let sort; if (options.sortBy) { const sortingCriteria = []; let primaryOrder = "desc"; options.sortBy.split(",").forEach((sortOption, index) => { const [key, order] = sortOption.split(":"); if (index === 0) primaryOrder = order || "asc"; sortingCriteria.push((order === "desc" ? "-" : "") + key); }); if (!sortingCriteria.some((s) => s === "_id" || s === "-_id")) { sortingCriteria.push((primaryOrder === "desc" ? "-" : "") + "_id"); } sort = sortingCriteria.join(" "); } else { sort = { _id: -1 }; } const limit = options.limit && parseInt(options.limit.toString(), 10) >= 0 ? parseInt(options.limit.toString(), 10) : 10; const page = options.page && parseInt(options.page.toString(), 10) > 0 ? parseInt(options.page.toString(), 10) : 1; const skip = (page - 1) * limit; const countStrategy = options.countStrategy ?? (filter && Object.keys(filter).length === 0 ? "estimated" : "exact"); const countPromise = countStrategy === "estimated" ? this.estimatedDocumentCount().exec() : this.countDocuments(filter).exec(); let docsPromise = this.find(filter).sort(sort).skip(skip).limit(limit); if (options.populate) { options.populate.split(",").forEach((populateOption) => { docsPromise = docsPromise.populate( populateOption.split(".").reverse().reduce((a, b) => ({ path: b, populate: a })) ); }); } docsPromise = docsPromise.exec(); return Promise.all([countPromise, docsPromise]).then((values) => { const [totalResults, results] = values; const totalPages = Math.ceil(totalResults / limit); const result = { results, page, limit, totalPages, totalResults, timestamp: Number(Date.now() + String(process.hrtime()[1]).slice(3, 6)) }; return Promise.resolve(result); }); }; }; var paginate_plugin_default = paginate; // src/server/models/plugins/toJSON.plugin.ts var deleteAtPath = (obj, path2, index) => { if (index === path2.length - 1) { delete obj[path2[index]]; return; } deleteAtPath(obj[path2[index]], path2, index + 1); }; var toJSON = (schema) => { let transform; if (schema.options.toJSON && schema.options.toJSON.transform) { transform = schema.options.toJSON.transform; } schema.options.toJSON = Object.assign(schema.options.toJSON || {}, { transform(doc, ret, options) { Object.keys(schema.paths).forEach((path2) => { if (schema.paths[path2].options && schema.paths[path2].options.private) { deleteAtPath(ret, path2.split("."), 0); } }); ret.id = ret._id.toString(); delete ret.__v; delete ret.createdAt; delete ret.updatedAt; if (transform) { return transform(doc, ret, options); } } }); }; var toJSON_plugin_default = toJSON; // src/server/utils/isJSON.ts var isJSON = (text) => { if (!text) return false; const isValid = /^[\],:{}\s]*$/.test( text.replace(/\\["\\\/bfnrtu]/g, "@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]").replace(/(?:^|:|,)(?:\s*\[)+/g, "") ); return isValid; }; var isJSON_default = isJSON; // src/server/utils/ApiError.ts var ApiError = class extends Error { statusCode; isOperational; constructor(statusCode, message, isOperational = true, stack = "") { super(message); this.statusCode = statusCode; this.isOperational = isOperational; if (stack) { this.stack = stack; } else { Error.captureStackTrace(this, this.constructor); } } }; var ApiError_default = ApiError; // src/server/utils/deserializeIfJSON.ts import mongoose from "mongoose"; var { EJSON } = mongoose.mongo.BSON; var EXTENDED_JSON_KEYS = [ "$oid", "$date", "$numberInt", "$numberLong", "$numberDouble", "$numberDecimal", "$regularExpression", "$binary", "$timestamp" ]; var containsExtendedJsonMarkers = (text) => EXTENDED_JSON_KEYS.some((marker) => text.includes(`"${marker}"`)); var deserializeIfJSON = (text) => { if (isJSON_default(text)) { if (containsExtendedJsonMarkers(text)) { return EJSON.parse(text) || void 0; } return JSON.parse(text) || void 0; } return text; }; var deserializeIfJSON_default = deserializeIfJSON; // src/server/utils/hash.ts import { createHash, randomUUID } from "crypto"; // src/server/utils/colors.ts var RESET = "\x1B[0m"; var colors = { // Standard colors blue: (s) => `\x1B[34m${s}${RESET}`, green: (s) => `\x1B[32m${s}${RESET}`, red: (s) => `\x1B[31m${s}${RESET}`, yellow: (s) => `\x1B[33m${s}${RESET}`, magenta: (s) => `\x1B[35m${s}${RESET}`, cyan: (s) => `\x1B[36m${s}${RESET}`, // Bright colors gray: (s) => `\x1B[90m${s}${RESET}`, whiteBright: (s) => `\x1B[97m${s}${RESET}`, // Reset code for manual use reset: RESET }; // src/server/models/Check.model.ts var CheckSchema = new Schema({ name: { type: String, required: [true, 'CheckSchema: The "name" field must be required'] }, test: { type: Schema.Types.ObjectId, ref: "VRSTest", required: [true, 'CheckSchema: The "test" field must be required'] }, suite: { type: Schema.Types.ObjectId, ref: "VRSSuite", required: [true, 'CheckSchema: The "suite" field must be required'] }, app: { type: Schema.Types.ObjectId, ref: "VRSApp", required: [true, 'CheckSchema: The "app" field must be required'] }, branch: { type: String }, realBaselineId: { type: Schema.Types.ObjectId, ref: "VRSBaseline" }, baselineId: { type: Schema.Types.ObjectId, ref: "VRSSnapshot" }, actualSnapshotId: { type: Schema.Types.ObjectId, ref: "VRSSnapshot" }, diffId: { type: Schema.Types.ObjectId, ref: "VRSSnapshot" }, createdDate: { type: Date, required: true, default: Date.now }, updatedDate: { type: Date }, status: { type: [{ type: String, enum: { values: ["new", "pending", "approved", "running", "passed", "failed", "aborted"], message: "status is required" } }], default: ["new"] }, browserName: { type: String }, browserVersion: { type: String }, browserFullVersion: { type: String }, viewport: { type: String }, os: { type: String }, domDump: { type: String }, result: { type: String, default: "{}" }, run: { type: Schema.Types.ObjectId }, markedAs: { type: String, enum: ["bug", "accepted"] }, markedDate: { type: Date }, markedById: { type: Schema.Types.ObjectId, ref: "VRSUser" }, markedByUsername: { type: String }, markedBugComment: { type: String }, creatorId: { type: Schema.Types.ObjectId, ref: "VRSUser" }, creatorUsername: { type: String }, failReasons: { type: [String] }, vOffset: { type: String }, topStablePixels: { type: String }, toleranceThreshold: { type: Number, min: 0, max: 100 }, meta: { type: Object } }); CheckSchema.plugin(toJSON_plugin_default); CheckSchema.plugin(paginate_plugin_default); var Check = mongoose2.model("VRSCheck", CheckSchema); // src/server/models/Log.model.ts import mongoose3, { Schema as Schema2 } from "mongoose"; var LogSchema = new Schema2({ timestamp: { type: Date }, level: { type: String }, message: { type: String }, meta: { type: Object }, hostname: { type: Object } }); LogSchema.plugin(toJSON_plugin_default); LogSchema.plugin(paginate_plugin_default); var Log = mongoose3.model("VRSLog", LogSchema); // src/server/models/App.model.ts import mongoose4, { Schema as Schema3 } from "mongoose"; var AppSchema = new Schema3({ name: { type: String, default: "Others", unique: true, required: [true, 'AppSchema: The "name" field must be required'] }, description: { type: String }, version: { type: String }, updatedDate: { type: Date }, createdDate: { type: Date }, meta: { type: Object } }); AppSchema.plugin(paginate_plugin_default); AppSchema.plugin(toJSON_plugin_default); var App = mongoose4.model("VRSApp", AppSchema); // src/server/models/Snapshot.model.ts import mongoose5, { Schema as Schema4 } from "mongoose"; var SnapshotSchema = new Schema4({ name: { type: String, required: [true, 'SnapshotSchema: The "name" field must be required'] }, path: { type: String }, filename: { type: String }, imghash: { type: String, required: [true, 'SnapshotSchema: The "imghash" field must be required'] }, createdDate: { type: Date, default: Date.now }, vOffset: { type: Number }, hOffset: { type: Number } }); SnapshotSchema.plugin(toJSON_plugin_default); SnapshotSchema.plugin(paginate_plugin_default); var Snapshot = mongoose5.model("VRSSnapshot", SnapshotSchema); // src/server/models/AppSettings.model.ts import mongoose6, { Schema as Schema5 } from "mongoose"; var AppSettingsSchema = new Schema5({ name: { type: String, unique: true, required: [true, 'AppSettingsSchema: The "name" field must be required'] }, label: { type: String, required: [true, 'AppSettingsSchema: The "label" field must be required'] }, description: { type: String }, type: { type: String, required: [true, 'AppSettingsSchema: The "type" field must be required'] }, value: { type: Schema5.Types.Mixed, required: [true, 'AppSettingsSchema: The "value" field must be required'] }, env_variable: { type: String }, enabled: { type: Boolean } }); AppSettingsSchema.plugin(toJSON_plugin_default); var AppSettings = mongoose6.model("VRSAppSettings", AppSettingsSchema); // src/server/models/Suite.model.ts import mongoose7, { Schema as Schema6 } from "mongoose"; var SuiteSchema = new Schema6({ name: { type: String, default: "Others", unique: true, required: [true, 'SuiteSchema: The "name" field must be required'] }, tags: { type: [String] }, app: { type: Schema6.Types.ObjectId, ref: "VRSApp", required: [true, 'SuiteSchema: The "app" field must be required'] }, description: { type: String }, updatedDate: { type: Date, default: Date.now }, createdDate: { type: Date }, meta: { type: Object } }); SuiteSchema.plugin(paginate_plugin_default); SuiteSchema.plugin(toJSON_plugin_default); var Suite = mongoose7.model("VRSSuite", SuiteSchema); // src/server/models/Run.model.ts import mongoose8, { Schema as Schema7 } from "mongoose"; var RunSchema = new Schema7({ name: { type: String, required: [true, 'RunSchema: The "name" field must be required'] }, app: { type: Schema7.Types.ObjectId, ref: "VRSApp", required: [true, 'RunSchema: The "app" field must be required'] }, ident: { type: String, unique: true, required: [true, 'RunSchema: The "ident" field must be required'] }, description: { type: String }, updatedDate: { type: Date, default: Date.now }, createdDate: { type: Date }, parameters: { type: [String] }, meta: { type: Object } }); RunSchema.plugin(paginate_plugin_default); RunSchema.plugin(toJSON_plugin_default); var Run = mongoose8.model("VRSRun", RunSchema); // src/server/models/User.model.ts import mongoose9, { Schema as Schema8 } from "mongoose"; import plm from "passport-local-mongoose"; var passportLocalMongoose = plm.default || plm; var UserSchema = new Schema8({ username: { type: String, unique: true, required: [true, 'UserSchema: The "username" field must be required'] }, firstName: { type: String, required: [true, 'UserSchema: The "firstName" field must be required'] }, lastName: { type: String, required: [true, 'UserSchema: The "lastName" field must be required'] }, role: { type: String, enum: ["admin", "reviewer", "user"], required: [true, 'UserSchema: The "role" field must be required'] }, provider: { type: String, default: "local" }, providerId: { type: String }, password: { type: String }, token: { type: String }, apiKey: { type: String }, authSource: { type: String, enum: ["local", "jwt", "ldap", "api"], default: "local" }, createdDate: { type: Date }, updatedDate: { type: Date }, expiration: { type: Date }, meta: { type: Object } }); UserSchema.statics.isEmailTaken = async function(username, excludeUserId) { const user = await this.findOne({ username, _id: { $ne: excludeUserId } }); return !!user; }; UserSchema.plugin(toJSON_plugin_default); UserSchema.plugin(paginate_plugin_default); UserSchema.plugin(passportLocalMongoose, { hashField: "password" }); var User = mongoose9.model("VRSUser", UserSchema); var User_model_default = User; // src/server/models/Baseline.model.ts import mongoose10, { Schema as Schema9 } from "mongoose"; var BaselineSchema = new Schema9({ snapshootId: { type: Schema9.Types.ObjectId, ref: "VRSSnapshot" }, name: { type: String, required: [true, 'VRSBaselineSchema: The "name" field must be required'] }, app: { type: Schema9.Types.ObjectId, ref: "VRSApp", required: [true, 'VRSBaselineSchema: The "app" field must be required'] }, branch: { type: String }, browserName: { type: String }, browserVersion: { type: String }, browserFullVersion: { type: String }, viewport: { type: String }, os: { type: String }, markedAs: { type: String, enum: ["bug", "accepted"] }, lastMarkedDate: { type: Date }, createdDate: { type: Date }, updatedDate: { type: Date }, markedById: { type: Schema9.Types.ObjectId, ref: "VRSUser" }, markedByUsername: { type: String }, ignoreRegions: { type: String }, boundRegions: { type: String }, matchType: { type: String, enum: ["antialiasing", "nothing", "colors"] }, toleranceThreshold: { type: Number, default: 0, min: 0, max: 100 }, meta: { type: Object } }); BaselineSchema.set("autoIndex", false); BaselineSchema.plugin(toJSON_plugin_default); BaselineSchema.plugin(paginate_plugin_default); BaselineSchema.index({ name: 1, app: 1, branch: 1, browserName: 1, viewport: 1, os: 1, snapshootId: 1 }, { unique: true, name: "baseline_ident_snapshot_idx" }); var Baseline = mongoose10.model("VRSBaseline", BaselineSchema); // src/server/models/Test.model.ts import mongoose11, { Schema as Schema10 } from "mongoose"; var TestSchema = new Schema10( { name: { type: String, required: "TestSchema: the test name is empty" }, description: { type: String }, status: { type: String }, browserName: { type: String }, browserVersion: { type: String }, branch: { type: String }, tags: { type: [String] }, viewport: { type: String }, calculatedViewport: { type: String }, os: { type: String }, app: { type: Schema10.Types.ObjectId, ref: "VRSApp", required: [true, 'TestSchema: The "app" field must be required'] }, blinking: { type: Number, default: 0 }, updatedDate: { type: Date }, startDate: { type: Date }, checks: [ { type: mongoose11.Schema.Types.ObjectId, ref: "VRSCheck" } ], suite: { type: Schema10.Types.ObjectId, ref: "VRSSuite" }, run: { type: Schema10.Types.ObjectId, ref: "VRSRun" }, markedAs: { type: String, enum: ["Bug", "Accepted", "Unaccepted", "Partially"] }, creatorId: { type: Schema10.Types.ObjectId, ref: "VRSUser" }, creatorUsername: { type: String }, meta: { type: Object } }, { strictQuery: true } ); TestSchema.plugin(toJSON_plugin_default); TestSchema.plugin(paginate_plugin_default); TestSchema.statics.paginateDistinct = async function(filter, options) { let sort = { _id: -1 }; if (options.sortBy) { sort = {}; options.sortBy.split(",").forEach((sortOption) => { const [key, order] = sortOption.split(":"); sort[key] = order === "desc" ? -1 : 1; }); } let limit = options.limit && parseInt(options.limit.toString(), 10) >= 0 ? parseInt(options.limit.toString(), 10) : 10; limit = limit === 0 ? Number.MAX_SAFE_INTEGER : limit; const page = options.page && parseInt(options.page.toString(), 10) > 0 ? parseInt(options.page.toString(), 10) : 1; const skip = (page - 1) * limit; const groupAggregateObj = { $group: { _id: `$${options.field}` } }; const parsedFilter = typeof filter?.filter === "string" ? deserializeIfJSON_default(filter.filter) || {} : {}; const documentsCount = (await this.aggregate([{ $match: parsedFilter }, groupAggregateObj]).exec()).length; const aggregatedDocs = (await this.aggregate([ { $match: parsedFilter }, groupAggregateObj, { $sort: sort }, { $skip: skip }, { $limit: limit } ])).filter((x) => x._id).map((x) => { const fieldValue = options.field ? x[options.field] : void 0; if (Array.isArray(fieldValue) && fieldValue.length > 0) { return fieldValue[0]; } return { name: x._id }; }); const totalPages = Math.ceil(documentsCount / limit); return { results: aggregatedDocs, page, limit, totalPages, totalResults: documentsCount, timestamp: Date.now() }; }; var Test = mongoose11.model("VRSTest", TestSchema); // src/server/models/Webhook.model.ts import mongoose12, { Schema as Schema11 } from "mongoose"; var WebhookSchema = new Schema11({ url: { type: String, required: [true, 'WebhookSchema: The "url" field must be required'] }, events: { type: [String], default: ["check.updated", "check.created"] }, secret: { type: String }, createdDate: { type: Date, default: Date.now }, meta: { type: Object } }); WebhookSchema.plugin(paginate_plugin_default); WebhookSchema.plugin(toJSON_plugin_default); var Webhook = mongoose12.model("VRSWebhook", WebhookSchema); // src/server/models/ShareToken.model.ts import mongoose13, { Schema as Schema12 } from "mongoose"; var ShareTokenSchema = new Schema12({ checkId: { type: Schema12.Types.ObjectId, ref: "VRSCheck", required: [true, 'ShareTokenSchema: The "checkId" field is required'], index: true }, token: { type: String, required: [true, 'ShareTokenSchema: The "token" field is required'], unique: true, index: true }, createdById: { type: Schema12.Types.ObjectId, ref: "VRSUser", required: [true, 'ShareTokenSchema: The "createdById" field is required'] }, createdByUsername: { type: String, required: [true, 'ShareTokenSchema: The "createdByUsername" field is required'] }, createdDate: { type: Date, required: true, default: Date.now }, isRevoked: { type: Boolean, default: false }, revokedDate: { type: Date }, revokedById: { type: Schema12.Types.ObjectId, ref: "VRSUser" }, revokedByUsername: { type: String } }); ShareTokenSchema.plugin(toJSON_plugin_default); ShareTokenSchema.plugin(paginate_plugin_default); var ShareToken = mongoose13.model("VRSShareToken", ShareTokenSchema); // src/server/models/DomSnapshot.model.ts import mongoose14, { Schema as Schema13 } from "mongoose"; var DomSnapshotSchema = new Schema13({ checkId: { type: Schema13.Types.ObjectId, ref: "VRSCheck", required: [true, 'DomSnapshotSchema: The "checkId" field is required'], index: true }, baselineId: { type: Schema13.Types.ObjectId, ref: "VRSBaseline", index: true }, type: { type: String, enum: ["actual", "baseline"], required: [true, 'DomSnapshotSchema: The "type" field is required'] }, filename: { type: String, required: [true, 'DomSnapshotSchema: The "filename" field is required'] }, hash: { type: String, required: [true, 'DomSnapshotSchema: The "hash" field is required'], index: true }, compressed: { type: Boolean, default: false }, originalSize: { type: Number, required: [true, 'DomSnapshotSchema: The "originalSize" field is required'] }, compressedSize: { type: Number }, createdDate: { type: Date, default: Date.now } }); DomSnapshotSchema.index({ checkId: 1, type: 1 }); DomSnapshotSchema.index({ baselineId: 1, type: 1 }); DomSnapshotSchema.plugin(toJSON_plugin_default); DomSnapshotSchema.plugin(paginate_plugin_default); var DomSnapshot = mongoose14.model("VRSDomSnapshot", DomSnapshotSchema); // src/server/models/PluginSettings.model.ts import mongoose15, { Schema as Schema14 } from "mongoose"; var PluginSettingSchemaDefinition = new Schema14({ key: { type: String, required: true }, label: { type: String, required: true }, description: { type: String }, type: { type: String, enum: ["string", "number", "boolean", "select", "password"], required: true }, defaultValue: { type: Schema14.Types.Mixed }, envVariable: { type: String }, options: [{ value: String, label: String }], required: { type: Boolean, default: false } }, { _id: false }); var PluginSettingsSchema = new Schema14({ pluginName: { type: String, unique: true, required: [true, "PluginSettings: pluginName is required"], index: true }, displayName: { type: String, required: [true, "PluginSettings: displayName is required"] }, description: { type: String }, enabled: { type: Boolean, default: false }, settings: { type: Schema14.Types.Mixed, default: {} }, settingsSchema: { type: [PluginSettingSchemaDefinition], default: [] } }, { timestamps: true }); PluginSettingsSchema.plugin(toJSON_plugin_default); PluginSettingsSchema.statics.getEffectiveConfig = async function(pluginName, envPrefix = "SYNGRISI_PLUGIN_") { const doc = await this.findOne({ pluginName }); const envPluginKey = pluginName.toUpperCase().replace(/-/g, "_"); const envEnabledKey = `${envPrefix}${envPluginKey}_ENABLED`; const envEnabled = process.env[envEnabledKey]?.toLowerCase() === "true"; const dbEnabled = doc?.enabled; const enabled = doc ? dbEnabled : envEnabled; const config2 = {}; const schema = doc?.settingsSchema || []; if (schema.length === 0 && doc?.settings && Object.keys(doc.settings).length > 0) { for (const [key, value] of Object.entries(doc.settings)) { config2[key] = { value, source: "db" }; } return { config: config2, enabled }; } for (const field of schema) { const envKey = field.envVariable || `${envPrefix}${envPluginKey}_${field.key.toUpperCase()}`; const envValue = process.env[envKey]; const dbValue = doc?.settings?.[field.key]; if (dbValue !== void 0) { config2[field.key] = { value: dbValue, source: "db" }; } else if (envValue !== void 0) { let parsedValue = envValue; if (field.type === "boolean") { parsedValue = envValue.toLowerCase() === "true"; } else if (field.type === "number") { parsedValue = parseFloat(envValue); } config2[field.key] = { value: parsedValue, source: "env" }; } else if (field.defaultValue !== void 0) { config2[field.key] = { value: field.defaultValue, source: "default" }; } } return { config: config2, enabled }; }; PluginSettingsSchema.statics.upsertSettings = async function(pluginName, updates) { return this.findOneAndUpdate( { pluginName }, { $set: updates }, { upsert: true, new: true, runValidators: true } ); }; var PluginSettings = mongoose15.model( "VRSPluginSettings", PluginSettingsSchema ); // src/seeds/testUsers.json var testUsers_default = [ { PASSWORD_: "123456aA-", API_KEY_: "123", username: "Test", firstName: "Test", lastName: "Admin", role: "admin", password: "5b8d4960316d1fb0c92498c90da6c397cdf247cae71f01467a88e2b42d7af6f5ac7ca75d3bea6e3e0078111a2e5dfc1611f9a9a8908a5a3af5bcd64c42989608977de192829bdf8ada113a60f8f0704443c659789761865e29a3103dbf0773f5bf31e4685d475ece56afaceb949b6e7467eaa287a02e4142d095bcbf84acaefe47ee080799a28188890d39d3397e285d8b46c9a0efe9517428825b64ee1ebcc96d92c084733db866c767341381b6254aaa1ef36d1bf3d24e3f5b8d8b6b4080589b130e9c90914a3da74e5b6adf5f569bfd77460abae8ae4f87c2a375397a37f09861b9e114cead0cc34fff2d631fd4294260dea17e4fe098940dbee2cb80c62eb3701d40f5b204de776b8252d55e5f567c599b1fbcdae79278d1f375a4c8244a26a3b721dbeec56c8f39b3eb810942d392aae371ea81ded6b820dd4b489566a33c495f5c291ff238d07202d2ff04c52426828e44af98ec056a42d13f4b166ec170083e2fff9efe2b8cfdde529f3bce56b8427cf2d188861808ad07fd13e073b2a804e818b2882c13f559d52420b49f301263a9de34fe22b6df4a82ae70e7e4c29c88479878d2c21fbb810532532e7ad9a28f610b63033520e703f178e7b44d3e101ec0d4339c085ccc8bb290b3cb996c75c2b8deaacba8098b9ec02c7e47542891da3bd887c31cd8e0bdfa56bb844b1703368afe8dc42d668ff2e3374b939b4f", apiKey: "3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2", salt: "c6211751bdc372f491a86bcbd8e4196dc393d14e14e5f17019d5c317afd5bc27" }, { PASSWORD_: "Test-123", API_KEY_: "GV640BD-9CG4RDB-NY8WH76-NJB5599", username: "testreviewer@test.com", firstName: "Test", lastName: "Reviewer", role: "reviewer", password: "152046e9cd34cdcd6cea5e66a9ec3fd262f61056b51e4a82d4fe8d6dacaa71fa8419e4b16bc11dfad9d2d90a8b8f4eb79b8e0dc93bededa7d8524521b122392dde0b4e9ca3bccb23b9bade5eb991d78d98246c009e983af42274f9d701c313ef8c47f23fea7701063a19be77f7d31c7cd537ee8f3bc31e861a81cc88bb2f47424e2903d06c99cf053e3be21d191075c92eaee48e1f964ace6357d7f5fea5412e56b26331f9e67354e3f0e0937f826011ddb0058a6c59021c9b7880b5c0ecdbaef1f8491e78288a00ff5ab5fa3e58528794f1bd84f097e8dccc8a8c3a15f760389d9b57f5313c63f68688ad44b53727d1c6e2078824e3c042ccfc6351772737807f46b7e24e30d8c48c1a1a55a3eb0d465d657004e17060afe4b354a4f3946b3dce8c105a2d521cd2c63161af0c039d41c212165c6133fe3b24d8ccdc82045450ce8e189d03ec7bb948bd0ad22140089d6f63352833b56232d7cad498efb65fb609077b6de73beb8e946fe54d7b417cefef0a07e26cbf33a7c58323f414aeaa4b83118e19bffdb3eb6d4f5150f1901b340c16e71c2683d7c6faede630396c4c92ba12485c6c8e00ecb3be785604ce41fdf42315cf02ad7f96eff2b018b415b1550f50756ace62965cadd4080a3e4bd8e3d70042bb3a5f0ba8ab357bfad9290af6a93236f294b3e7547295227b8d5b33cca35393d4b76e750805249b81b2a186aa", apiKey: "9aa20a336f91ea6c9657c3391934227059d4c28d2c7924813176ac4f6633d9c65baee77ca6f34c0d13e574ec285b54fd23c10b26abefec7df981e2884f91a84c", createdDate: "2022-09-19T15:23:09.360+04:00", updatedDate: null, salt: "09067fc22c1b821db81bc83c33f217cd8a54cb8f370d1da4a12d8be867a66b93" }, { PASSWORD_: "Test-123", API_KEY_: "NTSAEXR-CZ1MFWD-KY0Q3MG-HY6SKD1", username: "testuser@test.com", firstName: "Test", lastName: "User", role: "user", password: "60271a5363308e14af295c0ce34818ce32781296d10908f941d8c9589dced5b193f6e1a1962c4e5e718c00974497baa5a3fd6a797bf2cc91e7f76018711c1a7a35809c04fa92b256987f0835601b789609311c862f4c91220741fc30bd79aff25c0b0565834ca16cd313e45fc1b5846f25cabab695680edd739a85b479fbf51c2f860fd427003b10e5bec0b7bf776bfed7b6dbe33a3ca666e69995f4160ddc54e00075b1844807529f0fd8bf650eab71bf6732fd91018cfaedf950d94fd75cfe2b758c3610fb5ab1b863e119073637b33aeaf781e33ed1c73a050f8b8e540761f205d5b19347039599d647a25f1ba7d1422bf3029f9a35b8eb8633db09afd807d18a6ff22d55862e7aa401538eb705fca451fee132324bc05aff3eab10d6031a5e21ff4c6e3a6cb03c32730e1e8b550ebab0ca39942277782b32f9399a14c2876a2a01acecd70f819cce77407b1fee231d5674dd6dc8d9c56a38e9f50e4271e2d9725d830b6f187009726179cb6d1d67c614461bb294003b23f7979f16dbcc71ad9ab9285d7aa4a5550d15915d34ac50dee6fa7f2b90113a3cbf83a92811df0dee1f97c92328273cb8556136219569286666a1bc49c86c9e0e8175cf564168ada14e9f14c174f45789cc68667cecfda6f5d343f79d12ac4c0934f189dab0c8b6c29d01488a8dd0f37dcbba34ec45a9be8c52230189804beecf52603ce22ec95d", apiKey: "c45d0a87c4eea0f6ace90f1f780761f3e4daa9f11b5bc3732cb9014ad269a4bafa73c7ae6af891dd0fcc3e7ecaee40ca6236948ede1a3eb9c84757cd18c06bc3", createdDate: "2022-09-19T15:22:23.631+04:00", updatedDate: null, salt: "c23b0eafe172a9d1fad3651f02ae256a2f6932faac09fd1f4385c1f406c8c83f" }, { PASSWORD_: "Test-123", API_KEY_: "3APH5SM-X9G4T6G-H3ZZNQ2-55G4REB", username: "testadmin@test.com", firstName: "Test", lastName: "Admin", role: "admin", password: "5046e8420581be0c726c1cade6b6e15fe7cb0c34dad070d3a3030ed12d79d7a56b1217916522cd8813f578d8b24571dab848f22e774abb1db70f4fb725249694afa25c3733decd78fa4d5ab875ec0de43cbbe511ea6b059482f721f96d437622374f715ea316e2f03dc40d7217b1951f6022bdff89b17357b24706306ad38115db2aab3f8a609d172f734cabd7ca5eddcaa772d4cca8b6338c86edd639b91e9fdac3b17be22b3c010eac7c9cac128b675b87ec39fa5b8fea0593137df01eca1678d197d4ed536eb209b83c477a7cd62e9e8e06b8459ebefcd267fc38d7705313303d3b038e31996b66e54415494db34544c617f03740feecc3427f7a92ebcc50e10f45f38a01c6ccbc182bf5b5c48b7eb33432f86d14d6972afcdafa5aab2d82a1b49a559c63a5a719e4fbdaa8eaad0c80cb3bec83f16854b4280b13f1976b24b7ca52d18118eec3546038b8e682aa3005f091927f64905122e41f95e9b729afbd833771e1b802dd962bbda3d5ccd0c8966da7cd902db6a3283d1a631195cdc2efa032ddfb69d924ccb3a4808b21b424dee46abc7f07edfbe2c9e9092d1d6f0da828ca98734909ed8d40802f5403d5e9070c356c594b4758759400b263601237192c6de2fd321fa9e480f2261cac7b3cd3ac2b41289a79f90efa17b890d7ff3f391654e62a7bbc557b9dc03824acdbcdb760e8209c74d29d827fc3c75de5468c", apiKey: "db6e323f7f559a62aa8cd3838b1cdd3c95acc0b064bfd21f6810b2aa6c147ae8e45ebbc49f7e720d31f0bd18d71ab05eaeedd343d1f345c35d18fbf685db91cb", createdDate: "2022-09-19T15:19:19.825+04:00", updatedDate: null, salt: "a7e22101f6886dbdf07fc01d92e633d8b323f5d868a6d1214c7f9a12f266a2b2" } ]; // src/server/lib/logger.ts import winston from "winston"; import "winston-mongodb"; // src/server/utils/formatISOToDateTime.ts function formatISOToDateTime(isoDateString) { const date = new Date(isoDateString); return `${date.toISOString().slice(0, 10)} ${date.toTimeString().slice(0, 8)}`; } var formatISOToDateTime_default = formatISOToDateTime; // src/server/config.ts import fs from "fs"; import dotenv2 from "dotenv"; // package.json var version = "3.5.0"; var gitHead = "12bfda406cbe5aaccf3f17fdab02a9bd1a9d6343"; // src/server/config.ts import crypto2 from "crypto"; import { execSync } from "child_process"; // src/server/envConfig.ts import { cleanEnv, host, num, port, str, bool } from "envalid"; import crypto from "crypto"; import path from "path"; import dotenv from "dotenv"; dotenv.config({ quiet: true }); if (!process.env.NODE_ENV) { process.env.NODE_ENV = "production"; } var env = cleanEnv(process.env, { NODE_ENV: str({ choices: ["development", "production", "test"] }), SYNGRISI_DB_URI: str({ default: "mongodb://127.0.0.1:27017/SyngrisiDb" }), SYNGRISI_APP_PORT: port({ default: 3e3 }), SYNGRISI_IMAGES_PATH: str({ default: path.join(process.cwd(), "./.snapshots-images") }), SYNGRISI_DOM_SNAPSHOTS_PATH: str({ default: "" }), // If empty, uses SYNGRISI_IMAGES_PATH SYNGRISI_TMP_DIR: str({ default: path.join(process.cwd(), ".tmp") }), SYNGRISI_ADMIN_DATA_JOBS_PATH: str({ default: path.join(process.cwd(), ".tmp", "admin-data-jobs") }), SYNGRISI_ADMIN_DATA_JOBS_TTL_MS: num({ default: 24 * 60 * 60 * 1e3 }), SYNGRISI_ADMIN_DATA_MAX_CONCURRENT_JOBS: num({ default: 1 }), SYNGRISI_ADMIN_DATA_UPLOAD_MAX_SIZE_MB: num({ default: 10240 }), SYNGRISI_HTTP_LOG: bool({ default: false }), SYNGRISI_COVERAGE: bool({ default: false }), SYNGRISI_HOSTNAME: host({ default: "localhost" }), SYNGRISI_AUTH: bool({ default: true }), SYNGRISI_TEST_MODE: bool({ default: false }), SYNGRISI_DISABLE_FIRST_RUN: bool({ default: false }), MONGODB_ROOT_USERNAME: str({ default: "" }), MONGODB_ROOT_PASSWORD: str({ default: "" }), LOGLEVEL: str({ choices: ["error", "warn", "info", "verbose", "debug", "silly"], default: "debug" }), // Legacy tests expect 20 rows per page; keep default aligned for e2e SYNGRISI_PAGINATION_SIZE: num({ default: 20 }), SYNGRISI_DISABLE_DEV_CORS: bool({ default: true, devDefault: true }), SYNGRISI_SESSION_STORE_KEY: str({ default: crypto.randomBytes(64).toString("hex") }), SYNGRISI_LOG_LEVEL: str({ default: "debug" }), SYNGRISI_DISABLE_LOGS: bool({ default: false }), SYNGRISI_AUTO_REMOVE_CHECKS_POLL_INTERVAL_MS: num({ default: 10 * 60 * 1e3 }), // 10 minutes SYNGRISI_AUTO_REMOVE_CHECKS_MIN_INTERVAL_MS: num({ default: 24 * 60 * 60 * 1e3 }), SYNGRISI_ENABLE_SCHEDULERS_IN_TEST_MODE: bool({ default: false }), // RCA SYNGRISI_RCA: bool({ default: false }), // trunk features SYNGRISI_TRUNK_FEATURE_AI_SEVERITY: bool({ default: false }), SYNGRISI_AI_KEY: str({ default: "" }), OPENAI_API_BASE_URL: str({ default: "https://api.openai.com/v1" }), OPENAI_API_KEY: str({ default: "" }), SYNGRISI_V8_COVERAGE_ON_EXIT: bool({ default: false }), // Rate Limiting SYNGRISI_RATE_LIMIT_WINDOW_MS: num({ default: 15 * 60 * 1e3 }), // 15 minutes SYNGRISI_RATE_LIMIT_MAX: num({ default: 5e4 }), SYNGRISI_AUTH_RATE_LIMIT_WINDOW_MS: num({ default: 15 * 60 * 1e3 }), // 15 minutes SYNGRISI_AUTH_RATE_LIMIT_MAX: num({ default: 200 }), // Mongo tuneables for tests/CI flake reduction SYNGRISI_MONGO_SOCKET_TIMEOUT_MS: num({ default: 6e4 }), SYNGRISI_MONGO_MAX_POOL_SIZE: num({ default: 20 }), SYNGRISI_MONGO_MIN_POOL_SIZE: num({ default: 2 }), SYNGRISI_MONGO_MAX_IDLE_TIME_MS: num({ default: 3e4 }), SYNGRISI_MONGO_WAIT_QUEUE_TIMEOUT_MS: num({ default: 3e4 }), SYNGRISI_MONGO_SERVER_SELECTION_TIMEOUT_MS: num({ default: 1e4 }), SYNGRISI_MONGO_CONNECT_TIMEOUT_MS: num({ default: 3e4 }), // SSO Configuration SSO_ENABLED: bool({ default: false }), SSO_PROTOCOL: str({ choices: ["", "oauth2", "saml"], default: "" }), SSO_CLIENT_ID: str({ default: "" }), SSO_CLIENT_SECRET: str({ default: "" }), SSO_AUTHORIZATION_URL: str({ default: "" }), SSO_TOKEN_URL: str({ default: "" }), SSO_USERINFO_URL: str({ default: "" }), SSO_CALLBACK_URL: str({ default: "/v1/auth/sso/oauth/callback" }), // SAML specific SSO_ENTRY_POINT: str({ default: "" }), SSO_ISSUER: str({ default: "" }), SSO_CERT: str({ default: "" }), SSO_IDP_ISSUER: str({ default: "" }), SSO_IDP_METADATA_URL: str({ default: "" }), // URL to fetch IdP metadata XML (alternative to manual SSO_ENTRY_POINT/SSO_CERT) // SSO user settings SSO_DEFAULT_ROLE: str({ choices: ["", "user", "admin", "reviewer"], default: "reviewer" }), SSO_AUTO_CREATE_USERS: bool({ default: true }), SSO_ALLOW_ACCOUNT_LINKING: bool({ default: true }), // Plugin System SYNGRISI_PLUGINS_ENABLED: str({ default: "" }), // Comma-separated list of enabled plugins SYNGRISI_PLUGINS_DIR: str({ default: "" }), // Directory for external plugins // Okta Auth Plugin // Deprecated: Use SYNGRISI_PLUGIN_JWT_AUTH_* variables instead OKTA_JWKS_URL: str({ default: "" }), OKTA_ISSUER: str({ default: "" }), OKTA_SERVICE_USER_ROLE: str({ default: "" }), OKTA_AUTH_HEADER: str({ default: "" }), // Custom Check Validator Plugin CHECK_MISMATCH_THRESHOLD: str({ default: "0" }), // Mismatch % below which checks pass CHECK_VALIDATOR_SCRIPT: str({ default: "" }) // Path to custom validation script }); // src/server/data/devices.json var devices_default = [ { os: "ios", os_version: "16", device: "iPhone 14 Pro Max", realMobile: true }, { os: "ios", os_version: "16", device: "iPhone 14 Pro", realMobile: true }, { os: "ios", os_version: "16", device: "iPhone 14 Plus", realMobile: true }, { os: "ios", os_version: "16", device: "iPhone 14", realMobile: true }, { os: "ios", os_version: "16", device: "iPhone 12 Pro Max", realMobile: true }, { os: "ios", os_version: "16", device: "iPhone 12 Pro", realMobile: true }, { os: "ios", os_version: "16", device: "iPhone 12 Mini", realMobile: true }, { os: "ios", os_version: "16", device: "iPhone 11 Pro Max", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone XS", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone 13 Pro Max", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone 13 Pro", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone 13 Mini", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone 13", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone 11 Pro", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone 11", realMobile: true }, { os: "ios", os_version: "14", device: "iPhone XS", realMobile: true }, { os: "ios", os_version: "14", device: "iPhone 12 Pro Max", realMobile: true }, { os: "ios", os_version: "14", device: "iPhone 12 Pro", realMobile: true }, { os: "ios", os_version: "14", device: "iPhone 12 Mini", realMobile: true }, { os: "ios", os_version: "14", device: "iPhone 12", realMobile: true }, { os: "ios", os_version: "14", device: "iPhone 11 Pro Max", realMobile: true }, { os: "ios", os_version: "14", device: "iPhone 11", realMobile: true }, { os: "ios", os_version: "13", device: "iPhone XS", realMobile: true }, { os: "ios", os_version: "13", device: "iPhone 11 Pro Max", realMobile: true }, { os: "ios", os_version: "13", device: "iPhone 11 Pro", realMobile: true }, { os: "ios", os_version: "13", device: "iPhone 11", realMobile: true }, { os: "ios", os_version: "12", device: "iPhone XS", realMobile: true }, { os: "ios", os_version: "12", device: "iPhone XS Max", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone XR", realMobile: true }, { os: "ios", os_version: "12", device: "iPhone XR", realMobile: true }, { os: "ios", os_version: "11", device: "iPhone X", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone 8", realMobile: true }, { os: "ios", os_version: "13", device: "iPhone 8", realMobile: true }, { os: "ios", os_version: "12", device: "iPhone 8", realMobile: true }, { os: "ios", os_version: "11", device: "iPhone 8", realMobile: true }, { os: "ios", os_version: "12", device: "iPhone 8 Plus", realMobile: true }, { os: "ios", os_version: "11", device: "iPhone 8 Plus", realMobile: true }, { os: "ios", os_version: "12", device: "iPhone 7", realMobile: true }, { os: "ios", os_version: "10", device: "iPhone 7", realMobile: true }, { os: "ios", os_version: "12", device: "iPhone 6S", realMobile: true }, { os: "ios", os_version: "11", device: "iPhone 6S", realMobile: true }, { os: "ios", os_version: "11", device: "iPhone 6S Plus", realMobile: true }, { os: "ios", os_version: "11", device: "iPhone 6", realMobile: true }, { os: "ios", os_version: "15", device: "iPhone SE 2022", realMobile: true }, { os: "ios", os_version: "13", device: "iPhone SE 2020", realMobile: true }, { os: "ios", os_version: "11", device: "iPhone SE", realMobile: true }, { os: "ios", os_version: "14", device: "iPad Air 4", realMobile: true }, { os: "ios", os_version: "15", device: "iPad 9th", realMobile: true }, { os: "ios", os_version: "16", device: "iPad Pro 12.9 2022", realMobile: true }, { os: "ios", os_version: "16", device: "iPad Pro 12.9 2020", realMobile: true }, { os: "ios", os_version: "16", device: "iPad Pro 11 2022", realMobile: true }, { os: "ios", os_version: "16", device: "iPad 10th", realMobile: true }, { os: "ios", os_version: "15", device: "iPad Air 5", realMobile: true }, { os: "ios", os_version: "14", device: "iPad Pro 12.9 2021", realMobile: true }, { os: "ios", os_version: "14", device: "iPad Pro 12.9 2020", realMobile: true }, { os: "ios", os_version: "14", device: "iPad Pro 11 2021", realMobile: true }, { os: "ios", os_version: "13", device: "iPad Pro 12.9 2020", realMobile: true }, { os: "ios", os_version: "16", device: "iPad 8th", realMobile: true }, { os: "ios", os_version: "15", device: "iPad Pro 12.9 2018", realMobile: true }, { os: "ios", os_version: "15", device: "iPad Mini 2021", realMobile: true }, { os: "ios", os_version: "14", device: "iPad 8th", realMobile: true }, { os: "ios", os_version: "13", device: "iPad Pro 12.9 2018", realMobile: true }, { os: "ios", os_version: "13", device: "iPad Pro 11 2020", realMobile: true }, { os: "ios", os_version: "13", device: "iPad Mini 2019", realMobile: true }, { os: "ios", os_version: "13", device: "iPad Air 2019", realMobile: true }, { os: "ios", os_version: "13", device: "iPad 7th", realMobile: true }, { os: "ios", os_version: "12", device: "iPad Pro 12.9 2018", realMobile: true }, { os: "ios", os_version: "12", device: "iPad Pro 11 2018", realMobile: true }, { os: "ios", os_version: "12", device: "iPad Mini 2019", realMobile: true }, { os: "ios", os_version: "12", device: "iPad Air 2019", realMobile: true }, { os: "ios", os_version: "11", device: "iPad Pro 9.7 2016", realMobile: true }, { os: "ios", os_version: "11", device: "iPad Pro 12.9 2017", realMobile: true }, { os: "ios", os_version: "11", device: "iPad Mini 4", realMobile: true }, { os: "ios", os_version: "11", device: "iPad 6th", realMobile: true }, { os: "ios", os_version: "11", device: "iPad 5th", realMobile: true }, { os: "android", os_version: "12.0", device: "Samsung Galaxy S22 Ultra", realMobile: true }, { os: "android", os_version: "12.0", device: "Samsung Galaxy S22 Plus", realMobile: true }, { os: "android", os_version: "12.0", device: "Samsung Galaxy S22", realMobile: true }, { os: "android", os_version: "12.0", device: "Samsung Galaxy S21", realMobile: true }, { os: "android", os_version: "11.0", device: "Samsung Galaxy S21 Ultra", realMobile: true }, { os: "android", os_version: "11.0", device: "Samsung Galaxy S21", realMobile: true }, { os: "android", os_version: "11.0", device: "Samsung Galaxy S21 Plus", realMobile: true }, { os: "android", os_version: "10.0", device: "Samsung Galaxy S20", realMobile: true }, { os: "android", os_version: "10.0", device: "Samsung Galaxy S20 Plus", realMobile: true }, { os: "android", os_version: "10.0", device: "Samsung Galaxy S20 Ultra", realMobile: true }, { os: "android", os_version: "11.0", device: "Samsung Galaxy M52", realMobile: true }, { os: "android", os_version: "11.0", device: "Samsung Galaxy M32", realMobile: true }, { os: "android", os_version: "11.0", device: "Samsung Galaxy A52", realMobile: true }, { os: "android", os_version: "10.0", device: "Samsung Galaxy Note 20 Ultra", realMobile: true }, { os: "android", os_version: "10.0", device: "Samsung Galaxy Note 20", realMobile: true }, { os: "android", os_version: "10.0", device: "Samsung Galaxy A51", realMobile: true }, { os: "android", os_version: "10.0", device: "Samsung Galaxy A11", realMobile: true }, { os: "android", os_version: "9.0", device: "Samsung Galaxy S9 Plus", realMobile: true }, { os: "android", os_version: "9.0", device: "Samsung Galaxy S10e", realMobile: true }, { os: "android", os_version: "9.0", device: "Samsung Galaxy S10 Plus", realMobile: true }, { os: "android", os_version: "9.0", device: "Samsung Galaxy S10", realMobile: true }, { os: "android", os_version: "9.0", device: "Samsung Galaxy Note 10 Plus", realMobile: true }, { os: "android", os_version: "9.0", device: "Samsung Galaxy Note 10", realMobile: true }, { os: "android", os_version: "9.0", device: "Samsung Galaxy A10", realMobile: true }, { os: "android", os_version: "8.1", device: "Samsung Galaxy Note 9", realMobile: true }, { os: "android", os_version: "8.1", device: "Samsung Galaxy J7 Prime", realMobile: true }, { os: "android", os_version: "8.0", device: "Samsung Galaxy S9 Plus", realMobile: true }, { os: "android", os_version: "8.0", device: "Samsung Galaxy S9", realMobile: true }, { os: "android", os_version: "7.1", device: "Samsung Galaxy Note 8", realMobile: true }, { os: "android", os_version: "7.1", device: "Samsung Galaxy A8", realMobile: true }, { os: "android", os_version: "7.0", device: "Samsung Galaxy S8 Plus", realMobile: true }, { os: "android", os_version: "7.0", device: "Samsung Galaxy S8", realMobile: true }, { os: "android", os_version: "6.0", device: "Samsung Galaxy S7", realMobile: true }, { os: "android", os_version: "5.0", device: "Samsung Galaxy S6", realMobile: true }, { os: "android", os_version: "13.0", device: "Google Pixel 7 Pro", realMobile: true }, { os: "android", os_version: "13.0", device: "Google Pixel 7", realMobile: true }, { os: "android", os_version: "13.0", device: "Google Pixel 6 Pro", realMobile: true }, { os: "android", os_version: "12.0", device: "Google Pixel 6 Pro", realMobile: true }, { os: "android", os_version: "12.0", device: "Google Pixel 6", realMobile: true }, { os: "android", os_version: "12.0", device: "Google Pixel 5", realMobile: true }, { os: "android", os_version: "11.0", device: "Google Pixel 5", realMobile: true }, { os: "android", os_version: "11.0", device: "Google Pixel 4", realMobile: true }, { os: "android", os_version: "10.0", device: "Google Pixel 4 XL", realMobile: true }, { os: "android", os_version: "10.0", device: "Google Pixel 4", realMobile: true }, { os: "android", os_version: "10.0", device: "Google Pixel 3", realMobile: true }, { os: "android", os_version: "9.0", device: "Google Pixel 3a XL", realMobile: true }, { os: "android", os_version: "9.0", device: "Google Pixel 3a", realMobile: true }, { os: "android", os_version: "9.0", device: "Google Pixel 3 XL", realMobile: true }, { os: "android", os_version: "9.0", device: "Google Pixel 3", realMobile: true }, { os: "android", os_version: "9.0", device: "Google Pixel 2", realMobile: true }, { os: "android", os_version: "8.0", device: "Google Pixel 2", realMobile: true }, { os: "android", os_version: "7.1", device: "Google Pixel", realMobile: true }, { os: "android", os_version: "6.0", device: "Google Nexus 6", realMobile: true }, { os: "android", os_version: "4.4", device: "Google Nexus 5", realMobile: true }, { os: "android", os_version: "11.0", device: "OnePlus 9", realMobile: true }, { os: "android", os_version: "10.0", device: "OnePlus 8", realMobile: true }, { os: "android", os_version: "10.0", device: "OnePlus 7T", realMobile: true }, { os: "android", os_version: "9.0", device: "OnePlus 7", realMobile: true }, { os: "android", os_version: "9.0", device: "OnePlus 6T", realMobile: true }, { os: "android", os_version: "11.0", device: "Xiaomi Redmi Note 11", realMobile: true }, { os: "android", os_version: "10.0", device: "Xiaomi Redmi Note 9", realMobile: true }, { os: "android", os_version: "9.0", device: "Xiaomi Redmi Note 8", realMobile: true }, { os: "android", os_version: "9.0", device: "Xiaomi Redmi Note 7", realMobile: true }, { os: "android", os_version: "11.0", device: "Vivo Y21", realMobile: true }, { os: "android", os_version: "11.0", device: "Vivo V21", realMobile: true }, { os: "android", os_version: "10.0", device: "Vivo Y50", realMobile: true }, { os: "android", os_version