@skyramp/skyramp 1.3.10 → 1.3.11
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.
- package/package.json +5 -3
- package/scripts/build-pdf-bundle.js +141 -0
- package/scripts/pdf-execution-helpers.js +340 -0
- package/src/classes/MockV2.d.ts +9 -17
- package/src/classes/MockV2.js +20 -22
- package/src/classes/SmartPlaywright.js +328 -2
- package/src/pdfViewer/bundle.d.ts +11 -0
- package/src/pdfViewer/bundle.js +1349 -0
- package/src/pdfViewer/index.d.ts +8 -0
- package/src/pdfViewer/index.js +14 -0
- package/src/pdfViewer/validator.d.ts +25 -0
- package/src/pdfViewer/validator.js +119 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/skyramp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.11",
|
|
4
4
|
"description": "module for leveraging skyramp cli functionality",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint 'src/**/*.js' 'src/**/*.ts' --fix",
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
"src/*.js",
|
|
15
15
|
"src/*.ts",
|
|
16
16
|
"src/classes/*.js",
|
|
17
|
-
"src/classes/*.ts"
|
|
17
|
+
"src/classes/*.ts",
|
|
18
|
+
"src/pdfViewer/*.js",
|
|
19
|
+
"src/pdfViewer/*.ts"
|
|
18
20
|
],
|
|
19
21
|
"main": "src/index.js",
|
|
20
22
|
"types": "src/index.d.ts",
|
|
@@ -32,4 +34,4 @@
|
|
|
32
34
|
"@typescript-eslint/parser": "^6.14.0",
|
|
33
35
|
"eslint": "^8.55.0"
|
|
34
36
|
}
|
|
35
|
-
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds bundle.js from Playwright's pdfJsViewer.ts using TypeScript's transpileModule.
|
|
5
|
+
* This correctly handles all TypeScript syntax without regex-based stripping.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/build-pdf-bundle.js [playwright-root-path]
|
|
9
|
+
* PLAYWRIGHT_ROOT=/path/to/playwright node scripts/build-pdf-bundle.js
|
|
10
|
+
*
|
|
11
|
+
* NOTE: This replaces the regex-based generate-pdf-bundle.js approach which had
|
|
12
|
+
* issues stripping complex TypeScript types and template literal strings.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// Get Playwright root from env var, command line arg, or default relative path
|
|
19
|
+
const PLAYWRIGHT_ROOT = process.env.PLAYWRIGHT_ROOT
|
|
20
|
+
|| process.argv[2]
|
|
21
|
+
|| path.resolve(__dirname, '../../../..');
|
|
22
|
+
|
|
23
|
+
const VIEWER_SOURCE = path.join(PLAYWRIGHT_ROOT, 'packages/injected/src/recorder/skyramp/pdfJsViewer.ts');
|
|
24
|
+
const EXECUTION_HELPERS = path.join(__dirname, 'pdf-execution-helpers.js');
|
|
25
|
+
const OUTPUT_FILE = path.join(__dirname, '../src/pdfViewer/bundle.js');
|
|
26
|
+
|
|
27
|
+
// TypeScript is available in the Playwright repo
|
|
28
|
+
const TS_MODULE = path.join(PLAYWRIGHT_ROOT, 'node_modules/typescript');
|
|
29
|
+
|
|
30
|
+
console.log('[PDF Bundle Builder]');
|
|
31
|
+
console.log('Playwright root:', PLAYWRIGHT_ROOT);
|
|
32
|
+
console.log('Source:', VIEWER_SOURCE);
|
|
33
|
+
console.log('Helpers:', EXECUTION_HELPERS);
|
|
34
|
+
console.log('Output:', OUTPUT_FILE);
|
|
35
|
+
console.log();
|
|
36
|
+
|
|
37
|
+
if (!fs.existsSync(VIEWER_SOURCE)) {
|
|
38
|
+
console.error('Error: pdfJsViewer.ts not found at:', VIEWER_SOURCE);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (!fs.existsSync(EXECUTION_HELPERS)) {
|
|
42
|
+
console.error('Error: pdf-execution-helpers.js not found at:', EXECUTION_HELPERS);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (!fs.existsSync(TS_MODULE)) {
|
|
46
|
+
console.error('Error: typescript not found at:', TS_MODULE);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ts = require(TS_MODULE);
|
|
51
|
+
|
|
52
|
+
// Step 1: Use TypeScript's transpileModule to get clean JS (no regex stripping needed)
|
|
53
|
+
console.log('Transpiling TypeScript...');
|
|
54
|
+
const source = fs.readFileSync(VIEWER_SOURCE, 'utf8');
|
|
55
|
+
const result = ts.transpileModule(source, {
|
|
56
|
+
compilerOptions: {
|
|
57
|
+
target: ts.ScriptTarget.ES2020,
|
|
58
|
+
module: ts.ModuleKind.None,
|
|
59
|
+
removeComments: false,
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const transpiledCode = result.outputText;
|
|
64
|
+
|
|
65
|
+
// Step 2: Extract just the class (strip CommonJS boilerplate added by transpileModule)
|
|
66
|
+
const classStart = transpiledCode.indexOf('class PdfJsViewer');
|
|
67
|
+
const exportsLine = transpiledCode.lastIndexOf('exports.PdfJsViewer = PdfJsViewer;');
|
|
68
|
+
if (classStart === -1 || exportsLine === -1) {
|
|
69
|
+
console.error('Error: Could not locate PdfJsViewer class in transpiled output');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const classCode = transpiledCode.substring(classStart, exportsLine).trim();
|
|
73
|
+
console.log('Class extracted:', classCode.length, 'bytes');
|
|
74
|
+
|
|
75
|
+
// Step 3: Read execution helpers
|
|
76
|
+
const executionHelpers = fs.readFileSync(EXECUTION_HELPERS, 'utf8');
|
|
77
|
+
|
|
78
|
+
// Step 4: Escape for embedding inside the outer template literal in bundle.js.
|
|
79
|
+
// JS template literals process escape sequences, so backslashes must be doubled,
|
|
80
|
+
// backticks must be escaped, and ${ must be escaped to prevent interpolation.
|
|
81
|
+
function escapeForTemplateLiteral(code) {
|
|
82
|
+
return code
|
|
83
|
+
.replace(/\\/g, '\\\\') // \ -> \\
|
|
84
|
+
.replace(/`/g, '\\`') // ` -> \`
|
|
85
|
+
.replace(/\$\{/g, '\\${'); // ${ -> \${
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const classEscaped = escapeForTemplateLiteral(classCode);
|
|
89
|
+
const helpersEscaped = escapeForTemplateLiteral(executionHelpers);
|
|
90
|
+
|
|
91
|
+
// Step 5: Build the bundle
|
|
92
|
+
const bundle = `/**
|
|
93
|
+
* Copyright (c) Skyramp Corporation.
|
|
94
|
+
*
|
|
95
|
+
* Bundled PDF.js viewer for execution-time injection
|
|
96
|
+
* This is a self-contained JavaScript string that can be injected via page.addInitScript()
|
|
97
|
+
*
|
|
98
|
+
* AUTO-GENERATED from Playwright sources by scripts/build-pdf-bundle.js
|
|
99
|
+
* DO NOT EDIT THIS FILE DIRECTLY - edit pdfJsViewer.ts instead and regenerate
|
|
100
|
+
*
|
|
101
|
+
* Source files:
|
|
102
|
+
* - PdfJsViewer class: ${VIEWER_SOURCE}
|
|
103
|
+
* - Execution helpers: ${EXECUTION_HELPERS}
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Self-contained PDF viewer injection script
|
|
108
|
+
* This script will be injected into pages during test execution to replace Chrome's PDF plugin with PDF.js
|
|
109
|
+
*/
|
|
110
|
+
const PDF_VIEWER_INJECTION_SCRIPT = \`
|
|
111
|
+
(function() {
|
|
112
|
+
'use strict';
|
|
113
|
+
|
|
114
|
+
console.log('[PW-PDF] PDF viewer injection script initializing...');
|
|
115
|
+
|
|
116
|
+
// =============================================================================
|
|
117
|
+
// PDF.js Viewer Class (auto-generated from pdfJsViewer.ts)
|
|
118
|
+
// =============================================================================
|
|
119
|
+
|
|
120
|
+
${classEscaped}
|
|
121
|
+
|
|
122
|
+
${helpersEscaped}
|
|
123
|
+
|
|
124
|
+
console.log('[PW-PDF] PDF viewer injection script loaded');
|
|
125
|
+
})();
|
|
126
|
+
\`;
|
|
127
|
+
|
|
128
|
+
module.exports = { PDF_VIEWER_INJECTION_SCRIPT };
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
fs.writeFileSync(OUTPUT_FILE, bundle, 'utf8');
|
|
132
|
+
const stats = fs.statSync(OUTPUT_FILE);
|
|
133
|
+
console.log();
|
|
134
|
+
console.log('Bundle generated successfully!');
|
|
135
|
+
console.log(' Output:', OUTPUT_FILE);
|
|
136
|
+
console.log(' Size:', (stats.size / 1024).toFixed(2), 'KB');
|
|
137
|
+
console.log();
|
|
138
|
+
console.log('Next steps:');
|
|
139
|
+
console.log(' 1. Validate: node -e "const {PDF_VIEWER_INJECTION_SCRIPT}=require(\'./src/pdfViewer/bundle.js\');require(\'fs\').writeFileSync(\'/tmp/t.js\',PDF_VIEWER_INJECTION_SCRIPT)" && node --check /tmp/t.js');
|
|
140
|
+
console.log(' 2. Pack: npm pack');
|
|
141
|
+
console.log(' 3. Install: npm install <path-to-.tgz>');
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Helper Functions (execution-specific)
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fetches a PDF from a URL and converts it to a data URL
|
|
7
|
+
* Uses fetch() which will be intercepted by route handler during execution
|
|
8
|
+
*/
|
|
9
|
+
async function fetchPdfAsDataUrl(pdfUrl) {
|
|
10
|
+
try {
|
|
11
|
+
console.log('[PW-PDF] Fetching PDF:', pdfUrl.substring(0, 100));
|
|
12
|
+
|
|
13
|
+
// During execution, this fetch will be intercepted by Playwright's route handler
|
|
14
|
+
const response = await fetch(pdfUrl);
|
|
15
|
+
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
21
|
+
const base64 = btoa(
|
|
22
|
+
new Uint8Array(arrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), '')
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const dataUrl = `data:application/pdf;base64,${base64}`;
|
|
26
|
+
console.log('[PW-PDF] ✅ PDF fetched successfully');
|
|
27
|
+
|
|
28
|
+
return dataUrl;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('[PW-PDF] ❌ Failed to fetch PDF:', error);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks if the current page itself is a PDF document
|
|
37
|
+
*/
|
|
38
|
+
function isCurrentPagePdf() {
|
|
39
|
+
const url = window.location.href;
|
|
40
|
+
|
|
41
|
+
// Check URL ends with .pdf
|
|
42
|
+
if (url.toLowerCase().endsWith('.pdf')) {
|
|
43
|
+
console.log('[PW-PDF] URL ends with .pdf:', url);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check for PDF plugin embed that takes up the whole page
|
|
48
|
+
const fullPageEmbed = document.querySelector('embed[type="application/pdf"]');
|
|
49
|
+
if (fullPageEmbed && document.body.children.length === 1) {
|
|
50
|
+
console.log('[PW-PDF] Found full-page PDF embed');
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if URL contains .pdf (including S3 URLs with .pdf)
|
|
55
|
+
if (url.toLowerCase().includes('.pdf')) {
|
|
56
|
+
console.log('[PW-PDF] PDF URL detected:', url);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Replaces the entire page with PDF.js viewer when the page itself is a PDF
|
|
65
|
+
*/
|
|
66
|
+
async function replacePdfPage() {
|
|
67
|
+
try {
|
|
68
|
+
// Get PDF URL from current page URL
|
|
69
|
+
const pdfUrl = window.location.href;
|
|
70
|
+
console.log('[PW-PDF] Replacing full-page PDF with PDF.js viewer:', pdfUrl.substring(0, 100));
|
|
71
|
+
|
|
72
|
+
// Fetch PDF
|
|
73
|
+
const dataUrl = await fetchPdfAsDataUrl(pdfUrl);
|
|
74
|
+
if (!dataUrl) {
|
|
75
|
+
throw new Error('Failed to fetch PDF');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Clear the document body
|
|
79
|
+
document.body.innerHTML = '';
|
|
80
|
+
|
|
81
|
+
// Create container for PDF.js viewer
|
|
82
|
+
const container = document.createElement('div');
|
|
83
|
+
container.setAttribute('data-pw-pdf-viewer', 'true');
|
|
84
|
+
container.id = 'pw-pdf-viewer-container';
|
|
85
|
+
container.style.cssText = `
|
|
86
|
+
position: fixed;
|
|
87
|
+
top: 0;
|
|
88
|
+
left: 0;
|
|
89
|
+
width: 100%;
|
|
90
|
+
height: 100%;
|
|
91
|
+
z-index: 2147483647;
|
|
92
|
+
background: #525252;
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
document.body.appendChild(container);
|
|
96
|
+
|
|
97
|
+
// Extract filename from URL
|
|
98
|
+
let filename = 'Document.pdf';
|
|
99
|
+
try {
|
|
100
|
+
const url = new URL(pdfUrl);
|
|
101
|
+
const pathname = url.pathname;
|
|
102
|
+
const lastSlash = pathname.lastIndexOf('/');
|
|
103
|
+
if (lastSlash !== -1) {
|
|
104
|
+
filename = pathname.substring(lastSlash + 1);
|
|
105
|
+
filename = decodeURIComponent(filename);
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.warn('[PW-PDF] Failed to extract filename from URL:', e);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Create PDF.js viewer
|
|
112
|
+
const viewer = new PdfJsViewer(container);
|
|
113
|
+
|
|
114
|
+
// Render the PDF
|
|
115
|
+
await viewer.renderPdf({
|
|
116
|
+
pdfDataUrl: dataUrl,
|
|
117
|
+
filename,
|
|
118
|
+
onReady: () => {
|
|
119
|
+
console.log('[PW-PDF] ✅ Full-page PDF replaced with PDF.js viewer');
|
|
120
|
+
},
|
|
121
|
+
onError: (error) => {
|
|
122
|
+
console.error('[PW-PDF] ❌ Failed to render full-page PDF:', error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return true;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error('[PW-PDF] Error replacing full-page PDF:', error);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Replaces a single PDF embed with PDF.js viewer
|
|
135
|
+
*/
|
|
136
|
+
async function replacePdfEmbed(embed) {
|
|
137
|
+
try {
|
|
138
|
+
// Get PDF URL
|
|
139
|
+
let pdfUrl = embed.getAttribute('src');
|
|
140
|
+
|
|
141
|
+
// If src is about:blank, use the page URL (the page itself is the PDF)
|
|
142
|
+
if (!pdfUrl || pdfUrl === 'about:blank') {
|
|
143
|
+
console.log('[PW-PDF] Embed has src="about:blank", using page URL as PDF URL...');
|
|
144
|
+
pdfUrl = window.location.href;
|
|
145
|
+
|
|
146
|
+
// Verify it looks like a PDF URL
|
|
147
|
+
if (!pdfUrl.includes('.pdf')) {
|
|
148
|
+
console.log('[PW-PDF] Page URL does not appear to be a PDF, skipping');
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log('[PW-PDF] Using page URL as PDF:', pdfUrl.substring(0, 100));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log('[PW-PDF] Replacing PDF embed with PDF.js viewer:', pdfUrl.substring(0, 100));
|
|
156
|
+
|
|
157
|
+
// Fetch PDF
|
|
158
|
+
const dataUrl = await fetchPdfAsDataUrl(pdfUrl);
|
|
159
|
+
if (!dataUrl) {
|
|
160
|
+
throw new Error('Failed to fetch PDF');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Create container for PDF.js viewer
|
|
164
|
+
const container = document.createElement('div');
|
|
165
|
+
container.setAttribute('data-pw-pdf-viewer', 'true');
|
|
166
|
+
container.style.cssText = `
|
|
167
|
+
position: absolute;
|
|
168
|
+
top: ${embed.offsetTop}px;
|
|
169
|
+
left: ${embed.offsetLeft}px;
|
|
170
|
+
width: ${embed.offsetWidth || 800}px;
|
|
171
|
+
height: ${embed.offsetHeight || 600}px;
|
|
172
|
+
z-index: 2147483647;
|
|
173
|
+
background: #525252;
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
// Insert container next to embed
|
|
177
|
+
const parent = embed.parentNode;
|
|
178
|
+
if (!parent) return false;
|
|
179
|
+
|
|
180
|
+
parent.insertBefore(container, embed);
|
|
181
|
+
|
|
182
|
+
// Hide original embed
|
|
183
|
+
embed.style.display = 'none';
|
|
184
|
+
|
|
185
|
+
// Extract filename from URL
|
|
186
|
+
let filename = 'Document.pdf';
|
|
187
|
+
try {
|
|
188
|
+
const url = new URL(pdfUrl);
|
|
189
|
+
const pathname = url.pathname;
|
|
190
|
+
const lastSlash = pathname.lastIndexOf('/');
|
|
191
|
+
if (lastSlash !== -1) {
|
|
192
|
+
filename = pathname.substring(lastSlash + 1);
|
|
193
|
+
filename = decodeURIComponent(filename);
|
|
194
|
+
}
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.warn('[PW-PDF] Failed to extract filename from URL:', e);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Create PDF.js viewer
|
|
200
|
+
const viewer = new PdfJsViewer(container);
|
|
201
|
+
|
|
202
|
+
// Render the PDF
|
|
203
|
+
await viewer.renderPdf({
|
|
204
|
+
pdfDataUrl: dataUrl,
|
|
205
|
+
filename,
|
|
206
|
+
onReady: () => {
|
|
207
|
+
console.log('[PW-PDF] ✅ PDF embed replaced successfully');
|
|
208
|
+
},
|
|
209
|
+
onError: (error) => {
|
|
210
|
+
console.error('[PW-PDF] ❌ Failed to render PDF:', error);
|
|
211
|
+
// Restore original embed on error
|
|
212
|
+
embed.style.display = '';
|
|
213
|
+
if (container.parentNode) {
|
|
214
|
+
container.parentNode.removeChild(container);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return true;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error('[PW-PDF] Error replacing PDF embed:', error);
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Detects and replaces PDF embeds with PDF.js viewers
|
|
228
|
+
*/
|
|
229
|
+
async function detectAndReplacePdfs() {
|
|
230
|
+
console.log('[PW-PDF] Scanning for PDF embeds...');
|
|
231
|
+
|
|
232
|
+
// Check if we've already replaced a full-page PDF (prevent infinite loop)
|
|
233
|
+
if (window.__pwPdfPageReplaced) {
|
|
234
|
+
console.log('[PW-PDF] PDF page already replaced, skipping detection');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check if the current page itself is a PDF document
|
|
239
|
+
const isPdfPage = isCurrentPagePdf();
|
|
240
|
+
if (isPdfPage) {
|
|
241
|
+
console.log('[PW-PDF] Current page is a PDF document, replacing with PDF.js viewer...');
|
|
242
|
+
window.__pwPdfPageReplaced = true;
|
|
243
|
+
await replacePdfPage();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Find all embed and iframe elements
|
|
248
|
+
const embeds = document.querySelectorAll('embed[type="application/pdf"], iframe[src*=".pdf"]');
|
|
249
|
+
|
|
250
|
+
if (embeds.length === 0) {
|
|
251
|
+
console.log('[PW-PDF] No PDF embeds found');
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
console.log(`[PW-PDF] Found ${embeds.length} PDF embed(s)`);
|
|
256
|
+
|
|
257
|
+
for (const embed of embeds) {
|
|
258
|
+
await replacePdfEmbed(embed);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// =============================================================================
|
|
263
|
+
// Window.open() Interception (for headless mode)
|
|
264
|
+
// =============================================================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Sets up window.open() interception to detect PDF URLs
|
|
268
|
+
*/
|
|
269
|
+
function setupPdfPopupInterception() {
|
|
270
|
+
const originalOpen = window.open;
|
|
271
|
+
window.open = function(url, target, features) {
|
|
272
|
+
// Check if URL is a PDF (ends with .pdf or contains PDF indicators)
|
|
273
|
+
const isPdf = url && url.toLowerCase().includes('.pdf');
|
|
274
|
+
|
|
275
|
+
if (isPdf) {
|
|
276
|
+
console.log('[PW-PDF] Intercepted window.open() to PDF:', url);
|
|
277
|
+
|
|
278
|
+
// Let the popup open normally (to about:blank or target URL)
|
|
279
|
+
const popup = originalOpen.call(this, url, target, features);
|
|
280
|
+
|
|
281
|
+
// If popup opened successfully, store PDF URL for it to access
|
|
282
|
+
if (popup && !popup.closed) {
|
|
283
|
+
popup.__pw_pdfUrl = url;
|
|
284
|
+
|
|
285
|
+
// The popup will detect __pw_pdfUrl and render with PDF.js
|
|
286
|
+
setTimeout(() => {
|
|
287
|
+
if (popup.__pw_detectAndReplacePdfs) {
|
|
288
|
+
popup.__pw_detectAndReplacePdfs();
|
|
289
|
+
}
|
|
290
|
+
}, 100);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return popup;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Not a PDF, let window.open() work normally
|
|
297
|
+
return originalOpen.apply(this, arguments);
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// =============================================================================
|
|
302
|
+
// Expose Functions and Classes Globally
|
|
303
|
+
// =============================================================================
|
|
304
|
+
|
|
305
|
+
window.PdfJsViewer = PdfJsViewer; // Expose class for direct instantiation
|
|
306
|
+
window.__pw_detectAndReplacePdfs = detectAndReplacePdfs;
|
|
307
|
+
window.__pw_setupPdfPopupInterception = setupPdfPopupInterception;
|
|
308
|
+
|
|
309
|
+
// =============================================================================
|
|
310
|
+
// Auto-detect PDFs on page load
|
|
311
|
+
// =============================================================================
|
|
312
|
+
|
|
313
|
+
// Auto-detect if current page is a PDF popup that needs rendering
|
|
314
|
+
if (window.__pw_pdfUrl && !window.__pwPdfRendered) {
|
|
315
|
+
window.__pwPdfRendered = true;
|
|
316
|
+
|
|
317
|
+
// Wait for DOM ready
|
|
318
|
+
if (document.readyState === 'loading') {
|
|
319
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
320
|
+
window.__pw_detectAndReplacePdfs();
|
|
321
|
+
});
|
|
322
|
+
} else {
|
|
323
|
+
window.__pw_detectAndReplacePdfs();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Also auto-detect on every page load (for direct navigation to PDFs)
|
|
328
|
+
// This runs after the page has fully loaded
|
|
329
|
+
if (document.readyState === 'loading') {
|
|
330
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
331
|
+
console.log('[PW-PDF] DOMContentLoaded - checking for PDFs...');
|
|
332
|
+
window.__pw_detectAndReplacePdfs();
|
|
333
|
+
});
|
|
334
|
+
} else if (document.readyState === 'interactive' || document.readyState === 'complete') {
|
|
335
|
+
// If DOM is already ready, run detection immediately
|
|
336
|
+
console.log('[PW-PDF] DOM already ready - checking for PDFs...');
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
window.__pw_detectAndReplacePdfs();
|
|
339
|
+
}, 100); // Small delay to let browser plugin initialize
|
|
340
|
+
}
|
package/src/classes/MockV2.d.ts
CHANGED
|
@@ -12,15 +12,10 @@ export class MockV2 {
|
|
|
12
12
|
URL: string;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* The
|
|
15
|
+
* The path to mock (e.g., "/api/v1/products")
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The port number of the service
|
|
21
|
-
*/
|
|
22
|
-
port: number;
|
|
23
|
-
|
|
17
|
+
path: string;
|
|
18
|
+
|
|
24
19
|
/**
|
|
25
20
|
* The HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
26
21
|
*/
|
|
@@ -29,7 +24,7 @@ export class MockV2 {
|
|
|
29
24
|
/**
|
|
30
25
|
* The HTTP status code to return
|
|
31
26
|
*/
|
|
32
|
-
|
|
27
|
+
statusCode: number;
|
|
33
28
|
|
|
34
29
|
/**
|
|
35
30
|
* The response body to return
|
|
@@ -49,8 +44,7 @@ export class MockV2 {
|
|
|
49
44
|
/**
|
|
50
45
|
* Creates a new MockV2 instance
|
|
51
46
|
* @param URL - The URL of the service to mock
|
|
52
|
-
* @param
|
|
53
|
-
* @param port - The port number of the service
|
|
47
|
+
* @param path - The path path to mock (e.g., "/api/v1/products")
|
|
54
48
|
* @param method - The HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
55
49
|
* @param responseStatusCode - The HTTP status code to return
|
|
56
50
|
* @param responseBody - The response body to return
|
|
@@ -59,10 +53,9 @@ export class MockV2 {
|
|
|
59
53
|
*/
|
|
60
54
|
constructor(
|
|
61
55
|
URL: string,
|
|
62
|
-
|
|
63
|
-
port: number,
|
|
56
|
+
path: string,
|
|
64
57
|
method: string,
|
|
65
|
-
|
|
58
|
+
statusCode: number,
|
|
66
59
|
responseBody: string,
|
|
67
60
|
requestBody?: string | null,
|
|
68
61
|
dataOverride?: Record<string, unknown> | null
|
|
@@ -74,8 +67,7 @@ export class MockV2 {
|
|
|
74
67
|
*/
|
|
75
68
|
toDict(): {
|
|
76
69
|
url: string;
|
|
77
|
-
|
|
78
|
-
port: number;
|
|
70
|
+
path: string;
|
|
79
71
|
method: string;
|
|
80
72
|
status_code: number;
|
|
81
73
|
response_body: string;
|
|
@@ -90,4 +82,4 @@ export class MockV2 {
|
|
|
90
82
|
toJSON(): string;
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
export default MockV2;
|
|
85
|
+
export default MockV2;
|
package/src/classes/MockV2.js
CHANGED
|
@@ -5,24 +5,23 @@
|
|
|
5
5
|
class MockV2 {
|
|
6
6
|
/**
|
|
7
7
|
* Represents a mock object configuration for API endpoint mocking.
|
|
8
|
-
* @param {
|
|
9
|
-
* @param {string}
|
|
10
|
-
* @param {
|
|
11
|
-
* @param {string} method - The HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
12
|
-
* @param {number}
|
|
13
|
-
* @param {string} responseBody - The response body to return
|
|
14
|
-
* @param {string} [requestBody] - Optional request body to match
|
|
15
|
-
* @param {Object} [dataOverride] - Optional data override object
|
|
8
|
+
* @param {Object} [options={}] - The options for creating the mock.
|
|
9
|
+
* @param {string} [options.url=''] - The URL of the service to mock
|
|
10
|
+
* @param {string} [options.path=''] - The path to mock (e.g., "/api/v1/products")
|
|
11
|
+
* @param {string} [options.method=''] - The HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
12
|
+
* @param {number} [options.statusCode=201] - The HTTP status code to return
|
|
13
|
+
* @param {string} [options.responseBody=''] - The response body to return
|
|
14
|
+
* @param {string} [options.requestBody=''] - Optional request body to match
|
|
15
|
+
* @param {Object.<string, *>} [options.dataOverride={}] - Optional data override object
|
|
16
16
|
*/
|
|
17
|
-
constructor(
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.dataOverride = dataOverride;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.url = options.url || '';
|
|
19
|
+
this.path = options.path || '';
|
|
20
|
+
this.method = options.method || '';
|
|
21
|
+
this.statusCode = options.statusCode || 201;
|
|
22
|
+
this.responseBody = options.responseBody || '';
|
|
23
|
+
this.requestBody = options.requestBody || '';
|
|
24
|
+
this.dataOverride = options.dataOverride || {};
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
/**
|
|
@@ -31,11 +30,10 @@ class MockV2 {
|
|
|
31
30
|
*/
|
|
32
31
|
toDict() {
|
|
33
32
|
const result = {
|
|
34
|
-
url: this.
|
|
35
|
-
|
|
36
|
-
port: this.port,
|
|
33
|
+
url: this.url,
|
|
34
|
+
path: this.path,
|
|
37
35
|
method: this.method,
|
|
38
|
-
status_code: this.
|
|
36
|
+
status_code: this.statusCode,
|
|
39
37
|
response_body: this.responseBody
|
|
40
38
|
};
|
|
41
39
|
|
|
@@ -59,4 +57,4 @@ class MockV2 {
|
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
module.exports = MockV2;
|
|
60
|
+
module.exports = MockV2;
|