react-native-signature-canvas 5.0.2 → 5.1.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # [5.1.0](https://github.com/YanYuanFE/react-native-signature-canvas/compare/v5.0.2...v5.1.0) (2026-07-05)
2
+
3
+ ### Bug Fixes
4
+
5
+ * harden string injection, prop handling, and WebView bridge ([98f158d](https://github.com/YanYuanFE/react-native-signature-canvas/commit/98f158d4f883981052b88d07f1b7c3c703cd0401))
6
+
1
7
  ## [5.0.2](https://github.com/YanYuanFE/react-native-signature-canvas/compare/v5.0.1...v5.0.2) (2025-12-28)
2
8
 
3
9
 
package/h5/js/app.js CHANGED
@@ -5,6 +5,17 @@ export default `
5
5
  canvas = wrapper && wrapper.querySelector("canvas"),
6
6
  signaturePad;
7
7
 
8
+ // Single bridge entry point. Guards the rare window where the RN WebView
9
+ // bridge is missing (injection race / WebView torn down) and surfaces the
10
+ // dropped message instead of swallowing it.
11
+ function postMessage(data) {
12
+ if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {
13
+ window.ReactNativeWebView.postMessage(data);
14
+ } else {
15
+ console.warn("[signature-canvas] RN bridge unavailable, dropped message: " + data);
16
+ }
17
+ }
18
+
8
19
  function resizeCanvas() {
9
20
  if (!canvas || !canvas.getContext || !signaturePad) {
10
21
  return;
@@ -35,8 +46,8 @@ export default `
35
46
  }
36
47
 
37
48
  signaturePad = new SignaturePad(canvas, {
38
- onBegin: () => window.ReactNativeWebView.postMessage("BEGIN"),
39
- onEnd: () => window.ReactNativeWebView.postMessage("END"),
49
+ onBegin: () => postMessage("BEGIN"),
50
+ onEnd: () => postMessage("END"),
40
51
  penColor: '<%penColor%>',
41
52
  backgroundColor: '<%backgroundColor%>',
42
53
  dotSize: <%dotSize%>,
@@ -46,22 +57,26 @@ export default `
46
57
  });
47
58
 
48
59
  // Initial canvas setup
49
- resizeCanvas();
60
+ const observer = new ResizeObserver(() => {
61
+ resizeCanvas();
62
+ });
63
+
64
+ observer.observe(canvas);
50
65
 
51
66
  function clearSignature () {
52
67
  signaturePad.clear();
53
68
  dataURL='';
54
- window.ReactNativeWebView.postMessage("CLEAR");
69
+ postMessage("CLEAR");
55
70
  }
56
71
 
57
72
  function undo() {
58
73
  signaturePad.undo();
59
- window.ReactNativeWebView.postMessage("UNDO");
74
+ postMessage("UNDO");
60
75
  }
61
76
 
62
77
  function redo() {
63
78
  signaturePad.redo();
64
- window.ReactNativeWebView.postMessage("REDO");
79
+ postMessage("REDO");
65
80
  }
66
81
 
67
82
  function changePenColor(color) {
@@ -69,7 +84,7 @@ export default `
69
84
  return;
70
85
  }
71
86
  signaturePad.penColor = color;
72
- window.ReactNativeWebView && window.ReactNativeWebView.postMessage("CHANGE_PEN");
87
+ postMessage("CHANGE_PEN");
73
88
  }
74
89
 
75
90
  function changePenSize(minW, maxW) {
@@ -81,34 +96,34 @@ export default `
81
96
  }
82
97
  signaturePad.minWidth = minW;
83
98
  signaturePad.maxWidth = maxW;
84
- window.ReactNativeWebView && window.ReactNativeWebView.postMessage("CHANGE_PEN_SIZE");
99
+ postMessage("CHANGE_PEN_SIZE");
85
100
  }
86
101
 
87
102
  function getData () {
88
103
  var data = signaturePad.toData();
89
- window.ReactNativeWebView.postMessage(JSON.stringify(data));
104
+ postMessage(JSON.stringify(data));
90
105
  }
91
106
 
92
- function fromData (pointGroups) {
93
- signaturePad.fromData(pointGroups);
94
- window.ReactNativeWebView.postMessage(JSON.stringify(pointGroups));
107
+ function fromData (pointGroups, suppressClear) {
108
+ signaturePad.fromData(pointGroups, suppressClear);
109
+ postMessage(JSON.stringify(pointGroups));
95
110
  }
96
111
 
97
112
  function draw() {
98
113
  signaturePad.draw();
99
- window.ReactNativeWebView.postMessage("DRAW");
114
+ postMessage("DRAW");
100
115
  }
101
116
 
102
117
  function erase() {
103
118
  signaturePad.erase();
104
- window.ReactNativeWebView.postMessage("ERASE");
119
+ postMessage("ERASE");
105
120
  }
106
121
 
107
122
  function cropWhitespace(url) {
108
123
  var myImage = new Image();
109
124
  myImage.crossOrigin = "Anonymous";
110
125
  myImage.onload = function(){
111
- window.ReactNativeWebView.postMessage(removeImageBlanks(myImage));
126
+ postMessage(removeImageBlanks(myImage));
112
127
  }
113
128
  myImage.src = url;
114
129
 
@@ -194,7 +209,7 @@ export default `
194
209
  }
195
210
 
196
211
  if (signaturePad.isEmpty()) {
197
- window.ReactNativeWebView && window.ReactNativeWebView.postMessage("EMPTY");
212
+ postMessage("EMPTY");
198
213
  } else {
199
214
  var imageType = '<%imageType%>' || 'image/png';
200
215
  var url = signaturePad.toDataURL(imageType);
@@ -202,7 +217,7 @@ export default `
202
217
  if (trimWhitespace === true) {
203
218
  cropWhitespace(url);
204
219
  } else {
205
- window.ReactNativeWebView && window.ReactNativeWebView.postMessage(url);
220
+ postMessage(url);
206
221
  }
207
222
 
208
223
  if (autoClear === true && signaturePad) {
@@ -215,7 +230,8 @@ export default `
215
230
 
216
231
  var trimWhitespace = <%trimWhitespace%>;
217
232
 
218
- var dataURL = '<%dataURL%>';
233
+ // <%dataURL%> is injected as an already-quoted JSON string literal
234
+ var dataURL = <%dataURL%>;
219
235
 
220
236
  if (dataURL) signaturePad.fromDataURL(dataURL);
221
237
 
package/index.d.ts CHANGED
@@ -5,9 +5,9 @@ declare module "react-native-signature-canvas" {
5
5
 
6
6
  // Enhanced type definitions with better error handling
7
7
 
8
- type ImageType = "image/png" | "image/jpeg" | "image/svg+xml";
8
+ export type ImageType = "image/png" | "image/jpeg" | "image/svg+xml";
9
9
 
10
- type DataURL = string; // Simplified - should be base64 data URL
10
+ export type DataURL = string; // Simplified - should be base64 data URL
11
11
 
12
12
  type ForwardRef<T, P> = React.ForwardRefExoticComponent<
13
13
  React.PropsWithoutRef<P> & React.RefAttributes<T>
@@ -51,7 +51,6 @@ declare module "react-native-signature-canvas" {
51
51
  onBegin?: EmptyCallback;
52
52
  onEnd?: EmptyCallback;
53
53
  onLoadEnd?: EmptyCallback;
54
- onError?: ErrorCallback; // Added missing error callback
55
54
  overlayHeight?: number;
56
55
  overlayWidth?: number;
57
56
  overlaySrc?: string;
@@ -91,7 +90,4 @@ declare module "react-native-signature-canvas" {
91
90
 
92
91
  const SignatureView: SignatureCanvasComponent;
93
92
  export default SignatureView;
94
-
95
- // Export additional types for external use
96
- export { SignatureViewProps, SignatureViewRef, ImageType, DataURL };
97
93
  }
package/index.js CHANGED
@@ -46,16 +46,11 @@ const styles = StyleSheet.create({
46
46
  },
47
47
  });
48
48
 
49
- const getParamForInjection = (param) => {
50
- switch (typeof param) {
51
- case "string":
52
- return `'${param}'`
53
- case "object":
54
- return JSON.stringify(param)
55
- default:
56
- return param
57
- }
58
- }
49
+ // JSON.stringify produces a valid JS literal with quotes/backslashes/newlines escaped
50
+ const getParamForInjection = (param) =>
51
+ typeof param === "string" || typeof param === "object"
52
+ ? JSON.stringify(param)
53
+ : param;
59
54
 
60
55
  const SignatureView = forwardRef(
61
56
  (
@@ -125,18 +120,21 @@ const SignatureView = forwardRef(
125
120
  // Split source generation for better performance
126
121
  // Include webViewKey to regenerate script when WebView needs remounting
127
122
  const injectedScript = useMemo(() => {
123
+ // String values go through a function replacer so "$&"-style patterns
124
+ // in user input are inserted literally instead of being expanded
128
125
  let script = injectedSignaturePad + injectedApplication;
129
126
  script = script.replace(/<%autoClear%>/g, autoClear);
130
127
  script = script.replace(/<%trimWhitespace%>/g, trimWhitespace);
131
- script = script.replace(/<%imageType%>/g, imageType || "image/png");
132
- // Use currentDataURLRef to get the latest dataURL value
133
- script = script.replace(/<%dataURL%>/g, currentDataURLRef.current || "");
134
- script = script.replace(/<%penColor%>/g, penColor || "black");
135
- script = script.replace(/<%backgroundColor%>/g, backgroundColor || "rgba(255,255,255,0)");
136
- script = script.replace(/<%dotSize%>/g, dotSize || "null");
137
- script = script.replace(/<%minWidth%>/g, minWidth || 0.5);
138
- script = script.replace(/<%maxWidth%>/g, maxWidth || 2.5);
139
- script = script.replace(/<%minDistance%>/g, minDistance || 5);
128
+ script = script.replace(/<%imageType%>/g, () => imageType || "image/png");
129
+ // Use currentDataURLRef to get the latest dataURL value;
130
+ // JSON.stringify emits a quoted JS string literal with escaping
131
+ script = script.replace(/<%dataURL%>/g, () => JSON.stringify(currentDataURLRef.current || ""));
132
+ script = script.replace(/<%penColor%>/g, () => penColor || "black");
133
+ script = script.replace(/<%backgroundColor%>/g, () => backgroundColor || "rgba(255,255,255,0)");
134
+ script = script.replace(/<%dotSize%>/g, dotSize ?? "null");
135
+ script = script.replace(/<%minWidth%>/g, minWidth ?? 0.5);
136
+ script = script.replace(/<%maxWidth%>/g, maxWidth ?? 2.5);
137
+ script = script.replace(/<%minDistance%>/g, minDistance ?? 5);
140
138
  script = script.replace(/<%orientation%>/g, rotated || false);
141
139
  return script;
142
140
  }, [autoClear, trimWhitespace, imageType, penColor, backgroundColor, dotSize, minWidth, maxWidth, minDistance, rotated, webViewKey]);
@@ -144,16 +142,16 @@ const SignatureView = forwardRef(
144
142
  const source = useMemo(() => {
145
143
  const htmlContentValue = customHtml || htmlContent;
146
144
  let html = htmlContentValue(injectedScript);
147
- html = html.replace(/<%bgWidth%>/g, bgWidth || 0);
148
- html = html.replace(/<%bgHeight%>/g, bgHeight || 0);
149
- html = html.replace(/<%bgSrc%>/g, bgSrc || "null");
150
- html = html.replace(/<%overlayWidth%>/g, overlayWidth || 0);
151
- html = html.replace(/<%overlayHeight%>/g, overlayHeight || 0);
152
- html = html.replace(/<%overlaySrc%>/g, overlaySrc || "null");
153
- html = html.replace(/<%style%>/g, webStyle || "");
154
- html = html.replace(/<%description%>/g, descriptionText || "Sign above");
155
- html = html.replace(/<%confirm%>/g, confirmText || "Confirm");
156
- html = html.replace(/<%clear%>/g, clearText || "Clear");
145
+ html = html.replace(/<%bgWidth%>/g, bgWidth ?? 0);
146
+ html = html.replace(/<%bgHeight%>/g, bgHeight ?? 0);
147
+ html = html.replace(/<%bgSrc%>/g, () => bgSrc || "null");
148
+ html = html.replace(/<%overlayWidth%>/g, overlayWidth ?? 0);
149
+ html = html.replace(/<%overlayHeight%>/g, overlayHeight ?? 0);
150
+ html = html.replace(/<%overlaySrc%>/g, () => overlaySrc || "null");
151
+ html = html.replace(/<%style%>/g, () => webStyle || "");
152
+ html = html.replace(/<%description%>/g, () => descriptionText || "Sign above");
153
+ html = html.replace(/<%confirm%>/g, () => confirmText || "Confirm");
154
+ html = html.replace(/<%clear%>/g, () => clearText || "Clear");
157
155
  html = html.replace(/<%orientation%>/g, rotated || false);
158
156
 
159
157
  return { html };
@@ -173,7 +171,7 @@ const SignatureView = forwardRef(
173
171
  // Update dataURL in WebView without full reload
174
172
  if (dataURL) {
175
173
  const script = `
176
- dataURL = '${dataURL}';
174
+ dataURL = ${JSON.stringify(dataURL)};
177
175
  if (signaturePad && signaturePad.isEmpty()) {
178
176
  signaturePad.fromDataURL(dataURL);
179
177
  }
@@ -292,12 +290,12 @@ const SignatureView = forwardRef(
292
290
  executeWebViewMethod('changePenSize', [minW, maxW]);
293
291
  },
294
292
  getData: () => executeWebViewMethod('getData'),
295
- fromData: (pointGroups) => {
293
+ fromData: (pointGroups, suppressClear = false) => {
296
294
  if (!pointGroups) {
297
295
  console.warn('fromData: pointGroups must be an array');
298
296
  return;
299
297
  }
300
- executeWebViewMethod('fromData', [pointGroups, false]);
298
+ executeWebViewMethod('fromData', [pointGroups, suppressClear]);
301
299
  },
302
300
  // New method to set dataURL without causing WebView reload
303
301
  setDataURL: (url) => {
@@ -310,7 +308,7 @@ const SignatureView = forwardRef(
310
308
  return;
311
309
  }
312
310
  const script = `
313
- dataURL = '${url}';
311
+ dataURL = ${JSON.stringify(url)};
314
312
  if (signaturePad) {
315
313
  signaturePad.clear();
316
314
  if (dataURL) {
@@ -397,20 +395,6 @@ const SignatureView = forwardRef(
397
395
  return (
398
396
  <View style={[styles.webBg, style]}>
399
397
  <WebView
400
- // Key for forcing remount when WebView needs reinitialization
401
- key={`signature-webview-${webViewKey}`}
402
- // Core functionality props (cannot be overridden)
403
- ref={webViewRef}
404
- source={source}
405
- onMessage={getSignature}
406
- onError={renderError}
407
- onLoadEnd={handleLoadEnd}
408
- onLoadStart={handleLoadStart}
409
- onLoadProgress={handleLoadProgress}
410
- // Handle iOS WKWebView content process termination (crucial for bottom sheets/modals)
411
- onContentProcessDidTerminate={handleContentProcessDidTerminate}
412
- javaScriptEnabled={true}
413
- useWebKit={true}
414
398
  // Default component props (can be overridden by webviewProps)
415
399
  bounces={false}
416
400
  style={[{ flex: 1 }, webviewContainerStyle]}
@@ -437,6 +421,20 @@ const SignatureView = forwardRef(
437
421
  startInLoadingState={true}
438
422
  // User-provided WebView props (can override defaults but not core functionality)
439
423
  {...webviewProps}
424
+ // Core functionality props (cannot be overridden — placed after the spread)
425
+ // Key for forcing remount when WebView needs reinitialization
426
+ key={`signature-webview-${webViewKey}`}
427
+ ref={webViewRef}
428
+ source={source}
429
+ onMessage={getSignature}
430
+ onError={renderError}
431
+ onLoadEnd={handleLoadEnd}
432
+ onLoadStart={handleLoadStart}
433
+ onLoadProgress={handleLoadProgress}
434
+ // Handle iOS WKWebView content process termination (crucial for bottom sheets/modals)
435
+ onContentProcessDidTerminate={handleContentProcessDidTerminate}
436
+ javaScriptEnabled={true}
437
+ useWebKit={true}
440
438
  />
441
439
  {(loading || hasError) && (
442
440
  <View style={styles.loadingOverlayContainer}>
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "react-native-signature-canvas",
3
- "version": "5.0.2",
3
+ "version": "5.1.0",
4
4
  "description": "A performant, customizable React Native signature canvas with advanced error handling, WebView optimization, and TypeScript support for iOS, Android, and Expo",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "genlog": "conventional-changelog -p angular -i CHANGELOG.md -s",
8
- "postversion": "git push --follow-tags"
7
+ "genlog": "npx -y -p conventional-changelog -p conventional-changelog-angular conventional-changelog -p angular -i CHANGELOG.md",
8
+ "preversion": "npm whoami",
9
+ "version": "npm run genlog && git add CHANGELOG.md",
10
+ "postversion": "git push --follow-tags && npm publish"
9
11
  },
10
12
  "repository": {
11
13
  "type": "git",